├── .dockerignore
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── FEATURE_REQUEST.md
│ ├── SUPPORT_QUESTION.md
│ └── BUG_REPORT.md
├── workflows
│ ├── test.yml
│ ├── goreleaser.yml
│ ├── golangci.yml
│ └── docker-build.yml
├── dependabot.yml
└── PULL_REQUEST_TEMPLATE.md
├── util
└── util.go
├── models
├── alpine
│ ├── types.go
│ └── alpine.go
├── models_test.go
├── util
│ ├── util.go
│ └── util_test.go
├── amazon
│ ├── types.go
│ └── amazon.go
├── fedora
│ ├── types.go
│ └── fedora.go
├── debian
│ ├── debian.go
│ ├── debian_test.go
│ └── types.go
├── oracle
│ ├── oracle.go
│ └── types.go
├── models.go
├── suse
│ └── types.go
├── redhat
│ ├── redhat.go
│ ├── redhat_test.go
│ └── types.go
└── ubuntu
│ ├── ubuntu_test.go
│ ├── types.go
│ └── ubuntu.go
├── commands
├── version.go
├── fetch.go
├── server.go
├── fetch-fedora.go
├── root.go
├── fetch-amazon.go
├── fetch-alpine.go
├── fetch-ubuntu.go
├── fetch-debian.go
├── fetch-oracle.go
├── fetch-redhat.go
├── fetch-suse.go
└── select.go
├── main.go
├── .gitignore
├── fetcher
├── util
│ ├── util.go
│ ├── util_test.go
│ └── fetcher.go
├── oracle
│ └── oracle.go
├── amazon
│ ├── types.go
│ └── amazon.go
├── alpine
│ └── alpine.go
├── suse
│ └── suse.go
├── debian
│ └── debian.go
├── ubuntu
│ └── ubuntu.go
├── fedora
│ ├── types.go
│ └── types_test.go
└── redhat
│ └── redhat.go
├── .revive.toml
├── .goreleaser.yml
├── Dockerfile
├── GNUmakefile
├── log
└── log.go
├── .golangci.yml
├── config
└── config.go
├── go.mod
├── db
├── db.go
└── db_test.go
└── server
└── server.go
/.dockerignore:
--------------------------------------------------------------------------------
1 | .dockerignore
2 | Dockerfile
3 | vendor/
4 | cve.sqlite3
5 | oval.sqlite3*
6 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: kotakanbe
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature Request
3 | labels: enhancement
4 | about: I have a suggestion (and might want to implement myself)!
5 | ---
6 |
7 |
10 |
--------------------------------------------------------------------------------
/util/util.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "maps"
5 | "slices"
6 | )
7 |
8 | // Unique return unique elements
9 | func Unique[T comparable](s []T) []T {
10 | m := map[T]struct{}{}
11 | for _, v := range s {
12 | m[v] = struct{}{}
13 | }
14 | return slices.Collect(maps.Keys(m))
15 | }
16 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/SUPPORT_QUESTION.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Support Question
3 | labels: question
4 | about: If you have a question about Vuls.
5 | ---
6 |
7 |
11 |
--------------------------------------------------------------------------------
/models/alpine/types.go:
--------------------------------------------------------------------------------
1 | package alpine
2 |
3 | // SecDB is a struct of alpine secdb
4 | type SecDB struct {
5 | Distroversion string
6 | Reponame string
7 | Urlprefix string
8 | Apkurl string
9 | Packages []struct {
10 | Pkg struct {
11 | Name string
12 | Secfixes map[string][]string
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on: [pull_request]
4 |
5 | jobs:
6 | build:
7 | name: Build
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Check out code into the Go module directory
11 | uses: actions/checkout@v6
12 | - name: Set up Go 1.x
13 | uses: actions/setup-go@v6
14 | with:
15 | go-version-file: go.mod
16 | - name: Test
17 | run: make test
18 |
--------------------------------------------------------------------------------
/commands/version.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/spf13/cobra"
7 |
8 | "github.com/vulsio/goval-dictionary/config"
9 | )
10 |
11 | func init() {
12 | RootCmd.AddCommand(versionCmd)
13 | }
14 |
15 | var versionCmd = &cobra.Command{
16 | Use: "version",
17 | Short: "Show version",
18 | Long: `Show version`,
19 | Run: func(_ *cobra.Command, _ []string) {
20 | fmt.Printf("goval-dictionary %s %s\n", config.Version, config.Revision)
21 | },
22 | }
23 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "strings"
7 |
8 | "github.com/vulsio/goval-dictionary/commands"
9 | )
10 |
11 | // Name ... Name
12 | const Name string = "goval-dictionary"
13 |
14 | func main() {
15 | if envArgs := os.Getenv("GOVAL_DICTIONARY_ARGS"); 0 < len(envArgs) {
16 | commands.RootCmd.SetArgs(strings.Fields(envArgs))
17 | }
18 |
19 | if err := commands.RootCmd.Execute(); err != nil {
20 | fmt.Fprintln(os.Stderr, err)
21 | os.Exit(1)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 | *.test
24 | *.prof
25 |
26 | .vscode
27 | coverage.out
28 | vendor/
29 | goval-dictionary
30 | *.sqlite3
31 | *.sqlite3-shm
32 | *.sqlite3-wal
33 | *.sqlite3-journal
34 | tags
35 | /dist/
36 |
--------------------------------------------------------------------------------
/fetcher/util/util.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import "regexp"
4 |
5 | // CveIDPattern is regexp matches to `CVE-\d{4}-\d{4,}`
6 | var CveIDPattern = regexp.MustCompile(`CVE-\d{4}-\d{4,}`)
7 |
8 | // UniqueStrings eliminates duplication from []string
9 | func UniqueStrings(s []string) []string {
10 | if len(s) == 0 {
11 | return nil
12 | }
13 | m := make(map[string]struct{}, len(s))
14 | for _, v := range s {
15 | m[v] = struct{}{}
16 | }
17 | uniq := make([]string, 0, len(m))
18 | for v := range m {
19 | uniq = append(uniq, v)
20 | }
21 | return uniq
22 | }
23 |
--------------------------------------------------------------------------------
/models/models_test.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func Test_FetchMeta(t *testing.T) {
8 | var tests = []struct {
9 | in FetchMeta
10 | outdated bool
11 | }{
12 | {
13 | in: FetchMeta{
14 | SchemaVersion: 1,
15 | },
16 | outdated: true,
17 | },
18 | {
19 | in: FetchMeta{
20 | SchemaVersion: LatestSchemaVersion,
21 | },
22 | outdated: false,
23 | },
24 | }
25 |
26 | for i, tt := range tests {
27 | if aout := tt.in.OutDated(); tt.outdated != aout {
28 | t.Errorf("[%d] outdated expected: %#v\n actual: %#v\n", i, tt.outdated, aout)
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/models/util/util.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/inconshreveable/log15"
7 | )
8 |
9 | // ParsedOrDefaultTime returns time.Parse(layout, value), or time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC) if it failed to parse
10 | func ParsedOrDefaultTime(layouts []string, value string) time.Time {
11 | defaultTime := time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC)
12 | if value == "" || value == "unknown" {
13 | return defaultTime
14 | }
15 |
16 | for _, layout := range layouts {
17 | if t, err := time.Parse(layout, value); err == nil {
18 | return t
19 | }
20 | }
21 | log15.Warn("Failed to parse string", "timeformat", layouts, "target string", value)
22 | return defaultTime
23 | }
24 |
--------------------------------------------------------------------------------
/.revive.toml:
--------------------------------------------------------------------------------
1 | ignoreGeneratedHeader = false
2 | severity = "warning"
3 | confidence = 0.8
4 | errorCode = 0
5 | warningCode = 0
6 |
7 | [rule.blank-imports]
8 | [rule.context-as-argument]
9 | [rule.context-keys-type]
10 | [rule.dot-imports]
11 | [rule.error-return]
12 | [rule.error-strings]
13 | [rule.error-naming]
14 | [rule.exported]
15 | [rule.if-return]
16 | [rule.increment-decrement]
17 | [rule.var-naming]
18 | [rule.var-declaration]
19 | [rule.package-comments]
20 | [rule.range]
21 | [rule.receiver-naming]
22 | [rule.time-naming]
23 | [rule.unexported-return]
24 | [rule.indent-error-flow]
25 | [rule.errorf]
26 | [rule.empty-block]
27 | [rule.superfluous-else]
28 | [rule.unused-parameter]
29 | [rule.unreachable-code]
30 | [rule.redefines-builtin-id]
--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | project_name: goval-dictionary
2 | release:
3 | github:
4 | owner: vulsio
5 | name: goval-dictionary
6 | env:
7 | - CGO_ENABLED=0
8 | builds:
9 | - id: goval-dictionary
10 | goos:
11 | - linux
12 | - windows
13 | - darwin
14 | goarch:
15 | - amd64
16 | - arm64
17 | main: .
18 | ldflags: -s -w -X github.com/vulsio/goval-dictionary/config.Version={{.Version}} -X github.com/vulsio/goval-dictionary/config.Revision={{.Commit}}
19 | binary: goval-dictionary
20 | archives:
21 | - name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
22 | format: tar.gz
23 | files:
24 | - LICENSE
25 | - README*
26 | snapshot:
27 | name_template: SNAPSHOT-{{ .Commit }}
28 |
--------------------------------------------------------------------------------
/commands/fetch.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | "github.com/spf13/viper"
6 | )
7 |
8 | // fetchCmd represents the fetch command
9 | var fetchCmd = &cobra.Command{
10 | Use: "fetch",
11 | Short: "Fetch Vulnerability dictionary",
12 | Long: `Fetch Vulnerability dictionary`,
13 | }
14 |
15 | func init() {
16 | RootCmd.AddCommand(fetchCmd)
17 |
18 | fetchCmd.PersistentFlags().Bool("no-details", false, "without vulnerability details")
19 | _ = viper.BindPFlag("no-details", fetchCmd.PersistentFlags().Lookup("no-details"))
20 |
21 | fetchCmd.PersistentFlags().Int("batch-size", 25, "The number of batch size to insert.")
22 | _ = viper.BindPFlag("batch-size", fetchCmd.PersistentFlags().Lookup("batch-size"))
23 | }
24 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:alpine as builder
2 |
3 | RUN apk add --no-cache \
4 | git \
5 | make \
6 | gcc \
7 | musl-dev
8 |
9 | ENV REPOSITORY github.com/vulsio/goval-dictionary
10 | COPY . $GOPATH/src/$REPOSITORY
11 | RUN cd $GOPATH/src/$REPOSITORY && make install
12 |
13 |
14 | FROM alpine:3.22
15 |
16 | LABEL maintainer sadayuki-matsuno
17 |
18 | ENV LOGDIR /var/log/goval-dictionary
19 | ENV WORKDIR /goval-dictionary
20 |
21 | RUN apk add --no-cache ca-certificates \
22 | && mkdir -p $WORKDIR $LOGDIR
23 |
24 | COPY --from=builder /go/bin/goval-dictionary /usr/local/bin/
25 |
26 | VOLUME ["$WORKDIR", "$LOGDIR"]
27 | WORKDIR $WORKDIR
28 | ENV PWD $WORKDIR
29 |
30 | ENTRYPOINT ["goval-dictionary"]
31 | CMD ["--help"]
32 |
--------------------------------------------------------------------------------
/.github/workflows/goreleaser.yml:
--------------------------------------------------------------------------------
1 | name: goreleaser
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 | goreleaser:
10 | runs-on: ubuntu-latest
11 | steps:
12 | -
13 | name: Checkout
14 | uses: actions/checkout@v6
15 | -
16 | name: Unshallow
17 | run: git fetch --prune --unshallow
18 | -
19 | name: Set up Go
20 | uses: actions/setup-go@v6
21 | with:
22 | go-version-file: go.mod
23 | -
24 | name: Run GoReleaser
25 | uses: goreleaser/goreleaser-action@v6
26 | with:
27 | distribution: goreleaser
28 | version: latest
29 | args: release --clean
30 | env:
31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32 |
--------------------------------------------------------------------------------
/fetcher/oracle/oracle.go:
--------------------------------------------------------------------------------
1 | package oracle
2 |
3 | import (
4 | "golang.org/x/xerrors"
5 |
6 | "github.com/vulsio/goval-dictionary/fetcher/util"
7 | )
8 |
9 | func newFetchRequests() (reqs []util.FetchRequest) {
10 | const t = "https://linux.oracle.com/security/oval/com.oracle.elsa-all.xml.bz2"
11 | reqs = append(reqs, util.FetchRequest{
12 | URL: t,
13 | MIMEType: util.MIMETypeBzip2,
14 | })
15 | return
16 | }
17 |
18 | // FetchFiles fetch OVAL from Oracle
19 | func FetchFiles() ([]util.FetchResult, error) {
20 | reqs := newFetchRequests()
21 | if len(reqs) == 0 {
22 | return nil, xerrors.New("There are no versions to fetch")
23 | }
24 | results, err := util.FetchFeedFiles(reqs)
25 | if err != nil {
26 | return nil, xerrors.Errorf("Failed to fetch. err: %w", err)
27 | }
28 | return results, nil
29 | }
30 |
--------------------------------------------------------------------------------
/fetcher/amazon/types.go:
--------------------------------------------------------------------------------
1 | package amazon
2 |
3 | // extrasCatalog is a struct of extras-catalog.json for Amazon Linux 2 Extra Repository
4 | type extrasCatalog struct {
5 | Topics []struct {
6 | N string `json:"n"`
7 | Inst []string `json:"inst,omitempty"`
8 | Versions []string `json:"versions"`
9 | DeprecatedAt string `json:"deprecated-at,omitempty"`
10 | Visible []string `json:"visible,omitempty"`
11 | } `json:"topics"`
12 | }
13 |
14 | // repoMd has repomd data
15 | type repoMd struct {
16 | RepoList []repo `xml:"data"`
17 | }
18 |
19 | // repo has a repo data
20 | type repo struct {
21 | Type string `xml:"type,attr"`
22 | Location location `xml:"location"`
23 | }
24 |
25 | // location has a location of repomd
26 | type location struct {
27 | Href string `xml:"href,attr"`
28 | }
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/BUG_REPORT.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report
3 | labels: bug
4 | about: If something isn't working as expected.
5 | ---
6 |
7 | # What did you do? (required. The issue will be **closed** when not provided.)
8 |
9 |
10 | # What did you expect to happen?
11 |
12 |
13 | # What happened instead?
14 |
15 | * Current Output
16 |
17 | Please re-run the command using ```-debug``` and provide the output below.
18 |
19 | # Steps to reproduce the behaviour
20 |
21 |
22 | # Configuration (**MUST** fill this out):
23 |
24 | * Go version (`go version`):
25 |
26 | * Go environment (`go env`):
27 |
28 | * goval-dictionary environment:
29 |
30 | Hash : ____
31 |
32 | To check the commit hash of HEAD
33 | $ goval-dictionary -v
34 |
35 | or
36 |
37 | $ cd $GOPATH/src/github.com/vulsio/goval-dictionary
38 | $ git rev-parse --short HEAD
39 |
40 | * command:
41 |
42 |
--------------------------------------------------------------------------------
/.github/workflows/golangci.yml:
--------------------------------------------------------------------------------
1 | name: golangci-lint
2 | on:
3 | push:
4 | tags:
5 | - v*
6 | branches:
7 | - master
8 | pull_request:
9 | jobs:
10 | golangci:
11 | name: lint
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Check out code into the Go module directory
15 | uses: actions/checkout@v6
16 | - name: Set up Go 1.x
17 | uses: actions/setup-go@v6
18 | with:
19 | go-version-file: go.mod
20 | - name: golangci-lint
21 | uses: golangci/golangci-lint-action@v9
22 | with:
23 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
24 | version: latest
25 |
26 | # Optional: working directory, useful for monorepos
27 | # working-directory: somedir
28 |
29 | # Optional: golangci-lint command line arguments.
30 | # args: --issues-exit-code=0
31 |
32 | # Optional: show only new issues if it's a pull request. The default value is `false`.
33 | # only-new-issues: true
34 |
--------------------------------------------------------------------------------
/fetcher/util/util_test.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "sort"
5 | "testing"
6 |
7 | "github.com/google/go-cmp/cmp"
8 | )
9 |
10 | func TestCveIDPattern(t *testing.T) {
11 | tests := []struct {
12 | name string
13 | id string
14 | want bool
15 | }{
16 | {
17 | name: "normal",
18 | id: "CVE-2022-0001",
19 | want: true,
20 | },
21 | {
22 | name: "ID_with_5_digits",
23 | id: "CVE-2022-00001",
24 | want: true,
25 | },
26 | {
27 | name: "invalid_cve_id",
28 | id: "CVE-01-0001",
29 | want: false,
30 | },
31 | }
32 | for _, tt := range tests {
33 | t.Run(tt.name, func(t *testing.T) {
34 | got := CveIDPattern.Match([]byte(tt.id))
35 | if got != tt.want {
36 | t.Errorf("got = %v, want = %v", got, tt.want)
37 | }
38 | })
39 | }
40 | }
41 |
42 | func TestUniqueStrings(t *testing.T) {
43 | in := []string{"1", "1", "2", "3", "1", "2"}
44 | got := UniqueStrings(in)
45 | sort.Slice(got, func(i, j int) bool { return got[i] < got[j] })
46 | want := []string{"1", "2", "3"}
47 | if diff := cmp.Diff(got, want); diff != "" {
48 | t.Errorf("(-got +want):\n%s", diff)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/fetcher/alpine/alpine.go:
--------------------------------------------------------------------------------
1 | package alpine
2 |
3 | import (
4 | "fmt"
5 |
6 | "golang.org/x/xerrors"
7 |
8 | "github.com/vulsio/goval-dictionary/fetcher/util"
9 | )
10 |
11 | const community = "https://secdb.alpinelinux.org/v%s/community.yaml"
12 | const main = "https://secdb.alpinelinux.org/v%s/main.yaml"
13 |
14 | func newFetchRequests(target []string) (reqs []util.FetchRequest) {
15 | for _, v := range target {
16 | reqs = append(reqs, util.FetchRequest{
17 | Target: v,
18 | URL: fmt.Sprintf(main, v),
19 | MIMEType: util.MIMETypeYml,
20 | })
21 |
22 | if v != "3.2" {
23 | reqs = append(reqs, util.FetchRequest{
24 | Target: v,
25 | URL: fmt.Sprintf(community, v),
26 | MIMEType: util.MIMETypeYml,
27 | })
28 | }
29 | }
30 | return
31 | }
32 |
33 | // FetchFiles fetch from alpine secdb
34 | // https://secdb.alpinelinux.org/
35 | func FetchFiles(versions []string) ([]util.FetchResult, error) {
36 | reqs := newFetchRequests(versions)
37 | if len(reqs) == 0 {
38 | return nil, xerrors.New("There are no versions to fetch")
39 | }
40 | results, err := util.FetchFeedFiles(reqs)
41 | if err != nil {
42 | return nil, xerrors.Errorf("Failed to fetch. err: %w", err)
43 | }
44 |
45 | return results, nil
46 | }
47 |
--------------------------------------------------------------------------------
/.github/workflows/docker-build.yml:
--------------------------------------------------------------------------------
1 | name: Publish Docker image
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'master'
7 | tags:
8 | - '*'
9 |
10 | jobs:
11 | docker:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v6
16 |
17 | - name: Set up QEMU
18 | uses: docker/setup-qemu-action@v3
19 |
20 | - name: Set up Docker Buildx
21 | uses: docker/setup-buildx-action@v3
22 |
23 | - name: Login to DockerHub
24 | uses: docker/login-action@v3
25 | with:
26 | username: ${{ secrets.DOCKERHUB_USERNAME }}
27 | password: ${{ secrets.DOCKERHUB_TOKEN }}
28 |
29 | -
30 | name: Docker meta
31 | id: meta
32 | uses: docker/metadata-action@v5
33 | with:
34 | images: vuls/goval-dictionary
35 | tags: |
36 | type=ref,event=tag
37 |
38 | - name: Build and push
39 | uses: docker/build-push-action@v6
40 | with:
41 | push: true
42 | context: .
43 | tags: |
44 | vuls/goval-dictionary:latest
45 | ${{ steps.meta.outputs.tags }}
46 | secrets: |
47 | "github_token=${{ secrets.GITHUB_TOKEN }}"
48 | platforms: linux/amd64,linux/arm64
49 |
--------------------------------------------------------------------------------
/fetcher/suse/suse.go:
--------------------------------------------------------------------------------
1 | package suse
2 |
3 | import (
4 | "fmt"
5 |
6 | "golang.org/x/xerrors"
7 |
8 | "github.com/vulsio/goval-dictionary/fetcher/util"
9 | )
10 |
11 | // https://ftp.suse.com/pub/projects/security/oval/opensuse.leap.42.2.xml.gz
12 | // https://ftp.suse.com/pub/projects/security/oval/opensuse.13.2.xml.gz
13 | // https://ftp.suse.com/pub/projects/security/oval/suse.linux.enterprise.desktop.12.xml.gz
14 | // https://ftp.suse.com/pub/projects/security/oval/suse.linux.enterprise.server.12.xml.gz
15 | func newFetchRequests(suseType string, target []string) (reqs []util.FetchRequest) {
16 | const t = "https://ftp.suse.com/pub/projects/security/oval/%s.%s.xml.gz"
17 | for _, v := range target {
18 | reqs = append(reqs, util.FetchRequest{
19 | Target: v,
20 | URL: fmt.Sprintf(t, suseType, v),
21 | MIMEType: util.MIMETypeGzip,
22 | })
23 | }
24 | return
25 | }
26 |
27 | // FetchFiles fetch OVAL from SUSE
28 | func FetchFiles(suseType string, versions []string) ([]util.FetchResult, error) {
29 | reqs := newFetchRequests(suseType, versions)
30 | if len(reqs) == 0 {
31 | return nil, xerrors.New("There are no versions to fetch")
32 | }
33 | results, err := util.FetchFeedFiles(reqs)
34 | if err != nil {
35 | return nil, xerrors.Errorf("Failed to fetch. err: %w", err)
36 | }
37 | return results, nil
38 | }
39 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "gomod" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 | target-branch: "master"
13 | ignore:
14 | - dependency-name: "gorm.io/driver/mysql"
15 | - dependency-name: "gorm.io/driver/postgres"
16 | - dependency-name: "gorm.io/gorm"
17 | groups:
18 | all:
19 | patterns:
20 | - "*"
21 | exclude-patterns:
22 | - github.com/glebarez/sqlite
23 | - package-ecosystem: "github-actions" # See documentation for possible values
24 | directory: "/" # Location of package manifests
25 | schedule:
26 | interval: "weekly"
27 | target-branch: "master"
28 | groups:
29 | all:
30 | patterns:
31 | - "*"
32 | - package-ecosystem: "docker"
33 | directory: "/"
34 | schedule:
35 | interval: "weekly"
36 | target-branch: "master"
37 | groups:
38 | all:
39 | patterns:
40 | - "*"
41 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 | If this Pull Request is work in progress, Add a prefix of “[WIP]” in the title.
3 |
4 | # What did you implement:
5 |
6 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context.
7 |
8 | Fixes # (issue)
9 |
10 | ## Type of change
11 |
12 | Please delete options that are not relevant.
13 |
14 | - [ ] Bug fix (non-breaking change which fixes an issue)
15 | - [ ] New feature (non-breaking change which adds functionality)
16 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
17 | - [ ] This change requires a documentation update
18 |
19 | # How Has This Been Tested?
20 |
21 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce.
22 |
23 | # Checklist:
24 | You don't have to satisfy all of the following.
25 |
26 | - [ ] Write tests
27 | - [ ] Write documentation
28 | - [ ] Check that there aren't other open pull requests for the same issue/feature
29 | - [ ] Format your source code by `make fmt`
30 | - [ ] Pass the test by `make test`
31 | - [ ] Provide verification config / commands
32 | - [ ] Enable "Allow edits from maintainers" for this PR
33 | - [ ] Update the messages below
34 |
35 | ***Is this ready for review?:*** NO
36 |
37 | # Reference
38 |
39 | * https://blog.github.com/2015-01-21-how-to-write-the-perfect-pull-request/
40 |
41 |
--------------------------------------------------------------------------------
/models/util/util_test.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "testing"
5 | "time"
6 | )
7 |
8 | func TestParsedOrDefaultTime(t *testing.T) {
9 | tests := []struct {
10 | name string
11 | in string
12 | layouts []string
13 | want time.Time
14 | }{
15 | {
16 | name: "success to parse",
17 | in: "2021-01-02",
18 | layouts: []string{"2006-01-02"},
19 | want: time.Date(2021, time.January, 2, 0, 0, 0, 0, time.UTC),
20 | },
21 | {
22 | name: "success to parse(multi layout)",
23 | in: "2021-01-02 15:00:00",
24 | layouts: []string{"2006-01-02", "2006-01-02 15:04:05"},
25 | want: time.Date(2021, time.January, 2, 15, 0, 0, 0, time.UTC),
26 | },
27 | {
28 | name: "failed to parse",
29 | in: "2021/01/02",
30 | layouts: []string{"2006-01-02"},
31 | want: time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC),
32 | },
33 | {
34 | name: "empty string",
35 | in: "",
36 | layouts: []string{"2006-01-02"},
37 | want: time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC),
38 | },
39 | {
40 | name: "unknown",
41 | in: "unknown",
42 | layouts: []string{"2006-01-02"},
43 | want: time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC),
44 | },
45 | }
46 | for _, tt := range tests {
47 | t.Run(tt.name, func(t *testing.T) {
48 | if got := ParsedOrDefaultTime(tt.layouts, tt.in); got != tt.want {
49 | t.Errorf("got: %v, want: %v", got, tt.want)
50 | }
51 | })
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/GNUmakefile:
--------------------------------------------------------------------------------
1 | .PHONY: \
2 | all \
3 | build \
4 | install \
5 | lint \
6 | golangci \
7 | vet \
8 | fmt \
9 | mlint \
10 | fmtcheck \
11 | pretest \
12 | test \
13 | unused \
14 | cov \
15 | clean
16 |
17 | SRCS = $(shell git ls-files '*.go')
18 | PKGS = $(shell go list ./...)
19 | VERSION := $(shell git describe --tags --abbrev=0)
20 | REVISION := $(shell git rev-parse --short HEAD)
21 | LDFLAGS := -X 'github.com/vulsio/goval-dictionary/config.Version=$(VERSION)' \
22 | -X 'github.com/vulsio/goval-dictionary/config.Revision=$(REVISION)'
23 | GO := CGO_ENABLED=0 go
24 |
25 | all: build test
26 |
27 | build: main.go
28 | $(GO) build -a -ldflags "$(LDFLAGS)" -o goval-dictionary $<
29 |
30 | install: main.go
31 | $(GO) install -ldflags "$(LDFLAGS)"
32 |
33 | lint:
34 | go install github.com/mgechev/revive@latest
35 | revive -config ./.revive.toml -formatter plain $(PKGS)
36 |
37 | golangci:
38 | go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
39 | golangci-lint run
40 |
41 | vet:
42 | echo $(PKGS) | xargs env $(GO) vet || exit;
43 |
44 | fmt:
45 | gofmt -s -w $(SRCS)
46 |
47 | fmtcheck:
48 | $(foreach file,$(SRCS),gofmt -s -d $(file);)
49 |
50 | pretest: lint vet fmtcheck
51 |
52 | test: pretest
53 | $(GO) test -cover -v ./... || exit;
54 |
55 | cov:
56 | @ go get -v github.com/axw/gocov/gocov
57 | @ go get golang.org/x/tools/cmd/cover
58 | gocov test | gocov report
59 |
60 | clean:
61 | echo $(PKGS) | xargs go clean || exit;
62 | echo $(PKGS) | xargs go clean || exit;
63 |
--------------------------------------------------------------------------------
/fetcher/debian/debian.go:
--------------------------------------------------------------------------------
1 | package debian
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/inconshreveable/log15"
7 | "golang.org/x/xerrors"
8 |
9 | "github.com/vulsio/goval-dictionary/config"
10 | "github.com/vulsio/goval-dictionary/fetcher/util"
11 | )
12 |
13 | // https://www.debian.org/security/oval/
14 | func newFetchRequests(target []string) (reqs []util.FetchRequest) {
15 | const t = "https://www.debian.org/security/oval/oval-definitions-%s.xml.bz2"
16 | for _, v := range target {
17 | var name string
18 | if name = debianName(v); name == "unknown" {
19 | log15.Warn("Skip unknown debian.", "version", v)
20 | continue
21 | }
22 | reqs = append(reqs, util.FetchRequest{
23 | Target: v,
24 | URL: fmt.Sprintf(t, name),
25 | MIMEType: util.MIMETypeBzip2,
26 | })
27 | }
28 | return
29 | }
30 |
31 | func debianName(major string) string {
32 | switch major {
33 | case "7":
34 | return config.Debian7
35 | case "8":
36 | return config.Debian8
37 | case "9":
38 | return config.Debian9
39 | case "10":
40 | return config.Debian10
41 | case "11":
42 | return config.Debian11
43 | case "12":
44 | return config.Debian12
45 | case "13":
46 | return config.Debian13
47 | default:
48 | return "unknown"
49 | }
50 | }
51 |
52 | // FetchFiles fetch OVAL from Debian
53 | func FetchFiles(versions []string) ([]util.FetchResult, error) {
54 | reqs := newFetchRequests(versions)
55 | if len(reqs) == 0 {
56 | return nil, xerrors.New("There are no versions to fetch")
57 | }
58 | results, err := util.FetchFeedFiles(reqs)
59 | if err != nil {
60 | return nil, xerrors.Errorf("Failed to fetch. err: %w", err)
61 | }
62 | return results, nil
63 | }
64 |
--------------------------------------------------------------------------------
/log/log.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "runtime"
7 |
8 | "github.com/inconshreveable/log15"
9 | "golang.org/x/xerrors"
10 | )
11 |
12 | // GetDefaultLogDir returns default log directory
13 | func GetDefaultLogDir() string {
14 | defaultLogDir := "/var/log/goval-dictionary"
15 | if runtime.GOOS == "windows" {
16 | defaultLogDir = filepath.Join(os.Getenv("APPDATA"), "goval-dictionary")
17 | }
18 | return defaultLogDir
19 | }
20 |
21 | // SetLogger set logger
22 | func SetLogger(logToFile bool, logDir string, debug, logJSON bool) error {
23 | stderrHandler := log15.StderrHandler
24 | logFormat := log15.LogfmtFormat()
25 | if logJSON {
26 | logFormat = log15.JsonFormatEx(false, true)
27 | stderrHandler = log15.StreamHandler(os.Stderr, logFormat)
28 | }
29 |
30 | lvlHandler := log15.LvlFilterHandler(log15.LvlInfo, stderrHandler)
31 | if debug {
32 | lvlHandler = log15.LvlFilterHandler(log15.LvlDebug, stderrHandler)
33 | }
34 |
35 | var handler log15.Handler
36 | if logToFile {
37 | if _, err := os.Stat(logDir); err != nil {
38 | if os.IsNotExist(err) {
39 | if err := os.Mkdir(logDir, 0700); err != nil {
40 | return xerrors.Errorf("Failed to create log directory. err: %w", err)
41 | }
42 | } else {
43 | return xerrors.Errorf("Failed to check log directory. err: %w", err)
44 | }
45 | }
46 |
47 | logPath := filepath.Join(logDir, "goval-dictionary.log")
48 | if _, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644); err != nil {
49 | return xerrors.Errorf("Failed to open a log file. err: %w", err)
50 | }
51 | handler = log15.MultiHandler(
52 | log15.Must.FileHandler(logPath, logFormat),
53 | lvlHandler,
54 | )
55 | } else {
56 | handler = lvlHandler
57 | }
58 | log15.Root().SetHandler(handler)
59 | return nil
60 | }
61 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | version: "2"
2 |
3 | linters:
4 | default: none
5 | enable:
6 | - errcheck
7 | - govet
8 | - ineffassign
9 | - misspell
10 | - prealloc
11 | - revive
12 | - staticcheck
13 | settings:
14 | misspell:
15 | ignore-rules:
16 | - criterias
17 | revive: # https://golangci-lint.run/usage/linters/#revive
18 | rules:
19 | - name: blank-imports
20 | - name: context-as-argument
21 | - name: context-keys-type
22 | - name: dot-imports
23 | - name: empty-block
24 | - name: error-naming
25 | - name: error-return
26 | - name: error-strings
27 | - name: errorf
28 | - name: exported
29 | - name: if-return
30 | - name: increment-decrement
31 | - name: indent-error-flow
32 | - name: package-comments
33 | disabled: true
34 | - name: range
35 | - name: receiver-naming
36 | - name: redefines-builtin-id
37 | - name: superfluous-else
38 | - name: time-naming
39 | - name: unexported-return
40 | - name: unreachable-code
41 | - name: unused-parameter
42 | - name: var-declaration
43 | - name: var-naming
44 | arguments:
45 | - [] # AllowList
46 | - [] # DenyList
47 | - - skip-package-name-checks: true
48 | staticcheck: # https://golangci-lint.run/usage/linters/#staticcheck
49 | checks:
50 | - all
51 | - -ST1000 # at least one file in a package should have a package comment
52 | - -ST1005 # error strings should not be capitalized
53 | exclusions:
54 | rules:
55 | - source: "defer .+\\.Close\\(\\)"
56 | linters:
57 | - errcheck
58 |
59 | formatters:
60 | enable:
61 | - goimports
62 |
63 | run:
64 | timeout: 10m
65 |
--------------------------------------------------------------------------------
/models/amazon/types.go:
--------------------------------------------------------------------------------
1 | package amazon
2 |
3 | // Reference has reference information
4 | type Reference struct {
5 | Href string `xml:"href,attr" json:"href,omitempty"`
6 | ID string `xml:"id,attr" json:"id,omitempty"`
7 | Title string `xml:"title,attr" json:"title,omitempty"`
8 | Type string `xml:"type,attr" json:"type,omitempty"`
9 | }
10 |
11 | // Package has affected package information
12 | type Package struct {
13 | Name string `xml:"name,attr" json:"name,omitempty"`
14 | Epoch string `xml:"epoch,attr" json:"epoch,omitempty"`
15 | Version string `xml:"version,attr" json:"version,omitempty"`
16 | Release string `xml:"release,attr" json:"release,omitempty"`
17 | Arch string `xml:"arch,attr" json:"arch,omitempty"`
18 | Filename string `xml:"filename" json:"filename,omitempty"`
19 | }
20 |
21 | // Updated has updated at
22 | type Updated struct {
23 | Date string `xml:"date,attr" json:"date,omitempty"`
24 | }
25 |
26 | // Issued has issued at
27 | type Issued struct {
28 | Date string `xml:"date,attr" json:"date,omitempty"`
29 | }
30 |
31 | // UpdateInfo has detailed data of Updates
32 | type UpdateInfo struct {
33 | ID string `xml:"id" json:"id,omitempty"`
34 | Title string `xml:"title" json:"title,omitempty"`
35 | Issued Issued `xml:"issued" json:"issued,omitempty"`
36 | Updated Updated `xml:"updated" json:"updated,omitempty"`
37 | Severity string `xml:"severity" json:"severity,omitempty"`
38 | Description string `xml:"description" json:"description,omitempty"`
39 | Packages []Package `xml:"pkglist>collection>package" json:"packages,omitempty"`
40 | References []Reference `xml:"references>reference" json:"references,omitempty"`
41 | CVEIDs []string `json:"cveiDs,omitempty"`
42 | Repository string `json:"repository,omitempty"`
43 | }
44 |
45 | // Updates has a list of ALAS
46 | type Updates struct {
47 | UpdateList []UpdateInfo `xml:"update"`
48 | }
49 |
--------------------------------------------------------------------------------
/models/fedora/types.go:
--------------------------------------------------------------------------------
1 | package fedora
2 |
3 | // Reference has reference information
4 | type Reference struct {
5 | Href string `xml:"href,attr" json:"href,omitempty"`
6 | ID string `xml:"id,attr" json:"id,omitempty"`
7 | Title string `xml:"title,attr" json:"title,omitempty"`
8 | Type string `xml:"type,attr" json:"type,omitempty"`
9 | }
10 |
11 | // Package has affected package information
12 | type Package struct {
13 | Name string `xml:"name,attr" json:"name,omitempty"`
14 | Epoch string `xml:"epoch,attr" json:"epoch,omitempty"`
15 | Version string `xml:"version,attr" json:"version,omitempty"`
16 | Release string `xml:"release,attr" json:"release,omitempty"`
17 | Arch string `xml:"arch,attr" json:"arch,omitempty"`
18 | Filename string `xml:"filename" json:"filename,omitempty"`
19 | }
20 |
21 | // Updated has updated at
22 | type Updated struct {
23 | Date string `xml:"date,attr" json:"date,omitempty"`
24 | }
25 |
26 | // Issued has issued at
27 | type Issued struct {
28 | Date string `xml:"date,attr" json:"date,omitempty"`
29 | }
30 |
31 | // UpdateInfo has detailed data of Updates
32 | type UpdateInfo struct {
33 | ID string `xml:"id" json:"id,omitempty"`
34 | Title string `xml:"title" json:"title,omitempty"`
35 | Type string `xml:"type,attr" json:"type,omitempty"`
36 | Issued Issued `xml:"issued" json:"issued,omitempty"`
37 | Updated Updated `xml:"updated" json:"updated,omitempty"`
38 | Severity string `xml:"severity" json:"severity,omitempty"`
39 | Description string `xml:"description" json:"description,omitempty"`
40 | Packages []Package `xml:"pkglist>collection>package" json:"packages,omitempty"`
41 | ModularityLabel string `json:"modularity_label,omitempty"`
42 | References []Reference `xml:"references>reference" json:"references,omitempty"`
43 | CVEIDs []string `json:"cveiDs,omitempty"`
44 | }
45 |
46 | // Updates has a list of Update Info
47 | type Updates struct {
48 | UpdateList []UpdateInfo `xml:"update"`
49 | }
50 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | // Version ... Version
4 | var Version = ""
5 |
6 | // Revision of Git
7 | var Revision string
8 |
9 | const (
10 | // RedHat is
11 | RedHat = "redhat"
12 |
13 | // CentOS is
14 | CentOS = "centos"
15 |
16 | // Debian is
17 | Debian = "debian"
18 |
19 | // Ubuntu is
20 | Ubuntu = "ubuntu"
21 |
22 | // Raspbian is
23 | Raspbian = "raspbian"
24 |
25 | // Ubuntu1404 is Ubuntu Trusty
26 | Ubuntu1404 = "trusty"
27 |
28 | // Ubuntu1604 is Ubuntu Xenial
29 | Ubuntu1604 = "xenial"
30 |
31 | // Ubuntu1804 is Ubuntu Bionic
32 | Ubuntu1804 = "bionic"
33 |
34 | // Ubuntu2004 is Focal Fossa
35 | Ubuntu2004 = "focal"
36 |
37 | // Ubuntu2104 is Hirsute Hippo
38 | Ubuntu2104 = "hirsute"
39 |
40 | // Ubuntu2110 is Impish Indri
41 | Ubuntu2110 = "impish"
42 |
43 | // Ubuntu2204 is Jammy Jellyfish
44 | Ubuntu2204 = "jammy"
45 |
46 | // Ubuntu2210 is Kinetic Kudu
47 | Ubuntu2210 = "kinetic"
48 |
49 | // Ubuntu2304 is Lunar Lobster
50 | Ubuntu2304 = "lunar"
51 |
52 | // Ubuntu2310 is Mantic Minotaur
53 | Ubuntu2310 = "mantic"
54 |
55 | // Ubuntu2404 is Noble Numbat
56 | Ubuntu2404 = "noble"
57 |
58 | // Ubuntu2410 is Oracular Oriole
59 | Ubuntu2410 = "oracular"
60 |
61 | // Ubuntu2504 is Plucky Puffin
62 | Ubuntu2504 = "plucky"
63 |
64 | // Debian7 is wheezy
65 | Debian7 = "wheezy"
66 |
67 | // Debian8 is jessie
68 | Debian8 = "jessie"
69 |
70 | // Debian9 is stretch
71 | Debian9 = "stretch"
72 |
73 | // Debian10 is buster
74 | Debian10 = "buster"
75 |
76 | // Debian11 is bullseye
77 | Debian11 = "bullseye"
78 |
79 | // Debian12 is bookworm
80 | Debian12 = "bookworm"
81 |
82 | // Debian13 is trixie
83 | Debian13 = "trixie"
84 |
85 | // OpenSUSE is
86 | OpenSUSE = "opensuse"
87 |
88 | // OpenSUSELeap is
89 | OpenSUSELeap = "opensuse.leap"
90 |
91 | // SUSEEnterpriseServer is
92 | SUSEEnterpriseServer = "suse.linux.enterprise.server"
93 |
94 | // SUSEEnterpriseDesktop is
95 | SUSEEnterpriseDesktop = "suse.linux.enterprise.desktop"
96 |
97 | // Oracle is
98 | Oracle = "oracle"
99 |
100 | // Alpine is
101 | Alpine = "alpine"
102 |
103 | // Amazon is
104 | Amazon = "amazon"
105 |
106 | // Fedora is
107 | Fedora = "fedora"
108 | )
109 |
--------------------------------------------------------------------------------
/commands/server.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/inconshreveable/log15"
7 | "github.com/spf13/cobra"
8 | "github.com/spf13/viper"
9 | "golang.org/x/xerrors"
10 |
11 | "github.com/vulsio/goval-dictionary/db"
12 | "github.com/vulsio/goval-dictionary/log"
13 | "github.com/vulsio/goval-dictionary/models"
14 | "github.com/vulsio/goval-dictionary/server"
15 | )
16 |
17 | // ServerCmd is Subcommand for OVAL dictionary HTTP Server
18 | var serverCmd = &cobra.Command{
19 | Use: "server",
20 | Short: "Start OVAL dictionary HTTP server",
21 | Long: `Start OVAL dictionary HTTP server`,
22 | RunE: executeServer,
23 | }
24 |
25 | func init() {
26 | RootCmd.AddCommand(serverCmd)
27 |
28 | serverCmd.PersistentFlags().String("bind", "127.0.0.1", "HTTP server bind to IP address")
29 | _ = viper.BindPFlag("bind", serverCmd.PersistentFlags().Lookup("bind"))
30 |
31 | serverCmd.PersistentFlags().String("port", "1324", "HTTP server port number")
32 | _ = viper.BindPFlag("port", serverCmd.PersistentFlags().Lookup("port"))
33 | }
34 |
35 | func executeServer(_ *cobra.Command, _ []string) (err error) {
36 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil {
37 | return xerrors.Errorf("Failed to SetLogger. err: %w", err)
38 | }
39 |
40 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
41 | if err != nil {
42 | if errors.Is(err, db.ErrDBLocked) {
43 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
44 | }
45 | return xerrors.Errorf("Failed to open DB. err: %w", err)
46 | }
47 |
48 | fetchMeta, err := driver.GetFetchMeta()
49 | if err != nil {
50 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
51 | }
52 | if fetchMeta.OutDated() {
53 | return xerrors.Errorf("Failed to start server. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
54 | }
55 |
56 | log15.Info("Starting HTTP Server...")
57 | if err = server.Start(viper.GetBool("log-to-file"), viper.GetString("log-dir"), driver); err != nil {
58 | return xerrors.Errorf("Failed to start server. err: %w", err)
59 | }
60 |
61 | return nil
62 | }
63 |
--------------------------------------------------------------------------------
/models/alpine/alpine.go:
--------------------------------------------------------------------------------
1 | package alpine
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "time"
7 |
8 | "github.com/spf13/viper"
9 |
10 | "github.com/vulsio/goval-dictionary/models"
11 | )
12 |
13 | // ConvertToModel Convert OVAL to models
14 | func ConvertToModel(data *SecDB) (defs []models.Definition) {
15 | cveIDPacks := map[string][]models.Package{}
16 | for _, pack := range data.Packages {
17 | for ver, vulnIDs := range pack.Pkg.Secfixes {
18 | for _, s := range vulnIDs {
19 | cveID := strings.Split(s, " ")[0]
20 | if !strings.HasPrefix(cveID, "CVE") {
21 | continue
22 | }
23 |
24 | if packs, ok := cveIDPacks[cveID]; ok {
25 | packs = append(packs, models.Package{
26 | Name: pack.Pkg.Name,
27 | Version: ver,
28 | })
29 | cveIDPacks[cveID] = packs
30 | } else {
31 | cveIDPacks[cveID] = []models.Package{{
32 | Name: pack.Pkg.Name,
33 | Version: ver,
34 | }}
35 | }
36 | }
37 | }
38 | }
39 |
40 | for cveID, packs := range cveIDPacks {
41 | def := models.Definition{
42 | DefinitionID: fmt.Sprintf("def-%s-%s-%s", data.Reponame, data.Distroversion, cveID),
43 | Title: cveID,
44 | Description: "",
45 | Advisory: models.Advisory{
46 | Severity: "",
47 | Cves: []models.Cve{{CveID: cveID, Href: fmt.Sprintf("https://cve.mitre.org/cgi-bin/cvename.cgi?name=%s", cveID)}},
48 | Bugzillas: []models.Bugzilla{},
49 | AffectedCPEList: []models.Cpe{},
50 | Issued: time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC),
51 | Updated: time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC),
52 | },
53 | Debian: nil,
54 | AffectedPacks: packs,
55 | References: []models.Reference{
56 | {
57 | Source: "CVE",
58 | RefID: cveID,
59 | RefURL: fmt.Sprintf("https://cve.mitre.org/cgi-bin/cvename.cgi?name=%s", cveID),
60 | },
61 | },
62 | }
63 |
64 | if viper.GetBool("no-details") {
65 | def.Title = ""
66 | def.Description = ""
67 | def.Advisory.Severity = ""
68 | def.Advisory.Bugzillas = []models.Bugzilla{}
69 | def.Advisory.AffectedCPEList = []models.Cpe{}
70 | def.Advisory.Issued = time.Time{}
71 | def.Advisory.Updated = time.Time{}
72 | def.References = []models.Reference{}
73 | }
74 |
75 | defs = append(defs, def)
76 | }
77 | return
78 | }
79 |
--------------------------------------------------------------------------------
/models/amazon/amazon.go:
--------------------------------------------------------------------------------
1 | package amazon
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "time"
7 |
8 | "github.com/spf13/viper"
9 |
10 | "github.com/vulsio/goval-dictionary/models"
11 | "github.com/vulsio/goval-dictionary/models/util"
12 | )
13 |
14 | // ConvertToModel Convert OVAL to models
15 | func ConvertToModel(data *Updates) (defs []models.Definition) {
16 | for _, alas := range data.UpdateList {
17 | if strings.Contains(alas.Description, "** REJECT **") {
18 | continue
19 | }
20 |
21 | cves := []models.Cve{}
22 | for _, cveID := range alas.CVEIDs {
23 | cves = append(cves, models.Cve{
24 | CveID: cveID,
25 | Href: fmt.Sprintf("https://cve.mitre.org/cgi-bin/cvename.cgi?name=%s", cveID),
26 | })
27 | }
28 |
29 | packs := []models.Package{}
30 | for _, pack := range alas.Packages {
31 | packs = append(packs, models.Package{
32 | Name: pack.Name,
33 | Version: fmt.Sprintf("%s:%s-%s", pack.Epoch, pack.Version, pack.Release),
34 | Arch: pack.Arch,
35 | })
36 | }
37 |
38 | refs := []models.Reference{}
39 | for _, ref := range alas.References {
40 | refs = append(refs, models.Reference{
41 | Source: ref.Type,
42 | RefID: ref.ID,
43 | RefURL: ref.Href,
44 | })
45 | }
46 |
47 | issuedAt := util.ParsedOrDefaultTime([]string{"2006-01-02 15:04", "2006-01-02 15:04:05"}, alas.Issued.Date)
48 | updatedAt := util.ParsedOrDefaultTime([]string{"2006-01-02 15:04", "2006-01-02 15:04:05"}, alas.Updated.Date)
49 |
50 | def := models.Definition{
51 | DefinitionID: "def-" + alas.ID,
52 | Title: alas.ID,
53 | Description: alas.Description,
54 | Advisory: models.Advisory{
55 | Severity: alas.Severity,
56 | Cves: cves,
57 | Bugzillas: []models.Bugzilla{},
58 | AffectedCPEList: []models.Cpe{},
59 | AffectedRepository: alas.Repository,
60 | Issued: issuedAt,
61 | Updated: updatedAt,
62 | },
63 | Debian: nil,
64 | AffectedPacks: packs,
65 | References: refs,
66 | }
67 |
68 | if viper.GetBool("no-details") {
69 | def.Title = ""
70 | def.Description = ""
71 | def.Advisory.Severity = ""
72 | def.Advisory.Bugzillas = []models.Bugzilla{}
73 | def.Advisory.AffectedCPEList = []models.Cpe{}
74 | def.Advisory.Issued = time.Time{}
75 | def.Advisory.Updated = time.Time{}
76 | def.References = []models.Reference{}
77 | }
78 |
79 | defs = append(defs, def)
80 | }
81 | return
82 | }
83 |
--------------------------------------------------------------------------------
/models/fedora/fedora.go:
--------------------------------------------------------------------------------
1 | package fedora
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "time"
7 |
8 | "github.com/spf13/viper"
9 |
10 | "github.com/vulsio/goval-dictionary/models"
11 | "github.com/vulsio/goval-dictionary/models/util"
12 | )
13 |
14 | // ConvertToModel Convert OVAL to models
15 | func ConvertToModel(data *Updates) (defs []models.Definition) {
16 | for _, update := range data.UpdateList {
17 | if strings.Contains(update.Description, "** REJECT **") {
18 | continue
19 | }
20 |
21 | cves := []models.Cve{}
22 | for _, cveID := range update.CVEIDs {
23 | cves = append(cves, models.Cve{
24 | CveID: cveID,
25 | Href: fmt.Sprintf("https://cve.mitre.org/cgi-bin/cvename.cgi?name=%s", cveID),
26 | })
27 | }
28 |
29 | packs := []models.Package{}
30 | for _, pack := range update.Packages {
31 | packs = append(packs, models.Package{
32 | Name: pack.Name,
33 | Version: fmt.Sprintf("%s:%s-%s", pack.Epoch, pack.Version, pack.Release),
34 | Arch: pack.Arch,
35 | ModularityLabel: update.ModularityLabel,
36 | })
37 | }
38 |
39 | refs := []models.Reference{}
40 | bs := []models.Bugzilla{}
41 | for _, ref := range update.References {
42 | refs = append(refs, models.Reference{
43 | Source: ref.Type,
44 | RefID: ref.ID,
45 | RefURL: ref.Href,
46 | })
47 | if ref.Type == "bugzilla" {
48 | bs = append(bs, models.Bugzilla{
49 | BugzillaID: ref.ID,
50 | URL: ref.Href,
51 | Title: ref.Title,
52 | })
53 | }
54 | }
55 |
56 | issuedAt := util.ParsedOrDefaultTime([]string{"2006-01-02 15:04:05"}, update.Issued.Date)
57 | updatedAt := util.ParsedOrDefaultTime([]string{"2006-01-02 15:04:05"}, update.Updated.Date)
58 | def := models.Definition{
59 | DefinitionID: "def-" + update.ID,
60 | Title: update.ID,
61 | Description: update.Description,
62 | Advisory: models.Advisory{
63 | Severity: update.Severity,
64 | Cves: cves,
65 | Bugzillas: bs,
66 | AffectedCPEList: []models.Cpe{},
67 | Issued: issuedAt,
68 | Updated: updatedAt,
69 | },
70 | Debian: nil,
71 | AffectedPacks: packs,
72 | References: refs,
73 | }
74 |
75 | if viper.GetBool("no-details") {
76 | def.Title = ""
77 | def.Description = ""
78 | def.Advisory.Severity = ""
79 | def.Advisory.Bugzillas = []models.Bugzilla{}
80 | def.Advisory.AffectedCPEList = []models.Cpe{}
81 | def.Advisory.Issued = time.Time{}
82 | def.Advisory.Updated = time.Time{}
83 | def.References = []models.Reference{}
84 | }
85 |
86 | defs = append(defs, def)
87 | }
88 | return
89 | }
90 |
--------------------------------------------------------------------------------
/commands/fetch-fedora.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/inconshreveable/log15"
9 | "github.com/spf13/cobra"
10 | "github.com/spf13/viper"
11 | "golang.org/x/xerrors"
12 |
13 | c "github.com/vulsio/goval-dictionary/config"
14 | "github.com/vulsio/goval-dictionary/db"
15 | fetcher "github.com/vulsio/goval-dictionary/fetcher/fedora"
16 | "github.com/vulsio/goval-dictionary/log"
17 | "github.com/vulsio/goval-dictionary/models"
18 | "github.com/vulsio/goval-dictionary/models/fedora"
19 | "github.com/vulsio/goval-dictionary/util"
20 | )
21 |
22 | // fetchFedoraCmd is Subcommand for fetch Fedora OVAL
23 | var fetchFedoraCmd = &cobra.Command{
24 | Use: "fedora [version]",
25 | Short: "Fetch Vulnerability dictionary from Fedora",
26 | Long: `Fetch Vulnerability dictionary from Fedora`,
27 | Args: cobra.MinimumNArgs(1),
28 | RunE: fetchFedora,
29 | Example: "$ goval-dictionary fetch fedora 37",
30 | }
31 |
32 | func init() {
33 | fetchCmd.AddCommand(fetchFedoraCmd)
34 | }
35 |
36 | func fetchFedora(_ *cobra.Command, args []string) (err error) {
37 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil {
38 | return xerrors.Errorf("Failed to SetLogger. err: %w", err)
39 | }
40 |
41 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
42 | if err != nil {
43 | if errors.Is(err, db.ErrDBLocked) {
44 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
45 | }
46 | return xerrors.Errorf("Failed to open DB. err: %w", err)
47 | }
48 |
49 | fetchMeta, err := driver.GetFetchMeta()
50 | if err != nil {
51 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
52 | }
53 | if fetchMeta.OutDated() {
54 | return xerrors.Errorf("Failed to Insert CVEs into DB. SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
55 | }
56 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion.
57 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
58 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. err: %w", err)
59 | }
60 |
61 | uinfos, err := fetcher.FetchUpdateInfosFedora(util.Unique(args))
62 | if err != nil {
63 | return xerrors.Errorf("Failed to fetch files. err: %w", err)
64 | }
65 |
66 | for k, v := range uinfos {
67 | root := models.Root{
68 | Family: c.Fedora,
69 | OSVersion: k,
70 | Definitions: fedora.ConvertToModel(v),
71 | Timestamp: time.Now(),
72 | }
73 | log15.Info(fmt.Sprintf("%d CVEs for Fedora %s. Inserting to DB", len(root.Definitions), k))
74 | if err := driver.InsertOval(&root); err != nil {
75 | return xerrors.Errorf("Failed to insert OVAL. err: %w", err)
76 | }
77 | log15.Info("Finish", "Updated", len(root.Definitions))
78 | }
79 | return nil
80 | }
81 |
--------------------------------------------------------------------------------
/commands/root.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path/filepath"
7 |
8 | "github.com/inconshreveable/log15"
9 | homedir "github.com/mitchellh/go-homedir"
10 | "github.com/spf13/cobra"
11 | "github.com/spf13/viper"
12 |
13 | "github.com/vulsio/goval-dictionary/log"
14 | )
15 |
16 | var cfgFile string
17 |
18 | // RootCmd represents the base command when called without any subcommands
19 | var RootCmd = &cobra.Command{
20 | Use: "goval-dictionary",
21 | Short: "OVAL(Open Vulnerability and Assessment Language) dictionary",
22 | Long: `OVAL(Open Vulnerability and Assessment Language) dictionary`,
23 | SilenceErrors: true,
24 | SilenceUsage: true,
25 | }
26 |
27 | func init() {
28 | cobra.OnInitialize(initConfig)
29 |
30 | RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.oval.yaml)")
31 |
32 | RootCmd.PersistentFlags().Bool("log-to-file", false, "output log to file")
33 | _ = viper.BindPFlag("log-to-file", RootCmd.PersistentFlags().Lookup("log-to-file"))
34 |
35 | RootCmd.PersistentFlags().String("log-dir", log.GetDefaultLogDir(), "/path/to/log")
36 | _ = viper.BindPFlag("log-dir", RootCmd.PersistentFlags().Lookup("log-dir"))
37 |
38 | RootCmd.PersistentFlags().Bool("log-json", false, "output log as JSON")
39 | _ = viper.BindPFlag("log-json", RootCmd.PersistentFlags().Lookup("log-json"))
40 |
41 | RootCmd.PersistentFlags().Bool("debug", false, "debug mode (default: false)")
42 | _ = viper.BindPFlag("debug", RootCmd.PersistentFlags().Lookup("debug"))
43 |
44 | RootCmd.PersistentFlags().Bool("debug-sql", false, "SQL debug mode")
45 | _ = viper.BindPFlag("debug-sql", RootCmd.PersistentFlags().Lookup("debug-sql"))
46 |
47 | pwd := os.Getenv("PWD")
48 | RootCmd.PersistentFlags().String("dbpath", filepath.Join(pwd, "oval.sqlite3"), "/path/to/sqlite3 or SQL connection string")
49 | _ = viper.BindPFlag("dbpath", RootCmd.PersistentFlags().Lookup("dbpath"))
50 |
51 | RootCmd.PersistentFlags().String("dbtype", "sqlite3", "Database type to store data in (sqlite3, mysql, postgres or redis supported)")
52 | _ = viper.BindPFlag("dbtype", RootCmd.PersistentFlags().Lookup("dbtype"))
53 |
54 | RootCmd.PersistentFlags().String("http-proxy", "", "http://proxy-url:port (default: empty)")
55 | _ = viper.BindPFlag("http-proxy", RootCmd.PersistentFlags().Lookup("http-proxy"))
56 | }
57 |
58 | // initConfig reads in config file and ENV variables if set.
59 | func initConfig() {
60 | if cfgFile != "" {
61 | viper.SetConfigFile(cfgFile)
62 | } else {
63 | // Find home directory.
64 | home, err := homedir.Dir()
65 | if err != nil {
66 | log15.Error("Failed to find home directory.", "err", err)
67 | os.Exit(1)
68 | }
69 |
70 | // Search config in home directory with name ".goval-dictionary" (without extension).
71 | viper.AddConfigPath(home)
72 | viper.SetConfigName(".goval-dictionary")
73 | }
74 |
75 | viper.AutomaticEnv() // read in environment variables that match
76 |
77 | // If a config file is found, read it in.
78 | if err := viper.ReadInConfig(); err == nil {
79 | fmt.Println("Using config file:", viper.ConfigFileUsed())
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/commands/fetch-amazon.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "errors"
5 | "time"
6 |
7 | "github.com/inconshreveable/log15"
8 | "github.com/spf13/cobra"
9 | "github.com/spf13/viper"
10 | "golang.org/x/xerrors"
11 |
12 | c "github.com/vulsio/goval-dictionary/config"
13 | "github.com/vulsio/goval-dictionary/db"
14 | fetcher "github.com/vulsio/goval-dictionary/fetcher/amazon"
15 | "github.com/vulsio/goval-dictionary/log"
16 | "github.com/vulsio/goval-dictionary/models"
17 | "github.com/vulsio/goval-dictionary/models/amazon"
18 | "github.com/vulsio/goval-dictionary/util"
19 | )
20 |
21 | // fetchAmazonCmd is Subcommand for fetch Amazon ALAS RSS
22 | // https://alas.aws.amazon.com/alas.rss
23 | var fetchAmazonCmd = &cobra.Command{
24 | Use: "amazon [version]",
25 | Short: "Fetch Vulnerability dictionary from Amazon ALAS",
26 | Long: `Fetch Vulnerability dictionary from Amazon ALAS`,
27 | Args: cobra.MinimumNArgs(1),
28 | RunE: fetchAmazon,
29 | Example: "$ goval-dictionary fetch amazon 1 2 2022 2023",
30 | }
31 |
32 | func init() {
33 | fetchCmd.AddCommand(fetchAmazonCmd)
34 | }
35 |
36 | func fetchAmazon(_ *cobra.Command, args []string) (err error) {
37 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil {
38 | return xerrors.Errorf("Failed to SetLogger. err: %w", err)
39 | }
40 |
41 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
42 | if err != nil {
43 | if errors.Is(err, db.ErrDBLocked) {
44 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
45 | }
46 | return xerrors.Errorf("Failed to open DB. err: %w", err)
47 | }
48 |
49 | fetchMeta, err := driver.GetFetchMeta()
50 | if err != nil {
51 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
52 | }
53 | if fetchMeta.OutDated() {
54 | return xerrors.Errorf("Failed to Insert CVEs into DB. SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
55 | }
56 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion.
57 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
58 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. err: %w", err)
59 | }
60 |
61 | m, err := fetcher.FetchFiles(util.Unique(args))
62 | if err != nil {
63 | return xerrors.Errorf("Failed to fetch files. err: %w", err)
64 | }
65 | for ver, us := range m {
66 | root := models.Root{
67 | Family: c.Amazon,
68 | OSVersion: ver,
69 | Definitions: amazon.ConvertToModel(us),
70 | Timestamp: time.Now(),
71 | }
72 |
73 | if err := driver.InsertOval(&root); err != nil {
74 | return xerrors.Errorf("Failed to insert OVAL. err: %w", err)
75 | }
76 | log15.Info("Finish", "Updated", len(root.Definitions))
77 | }
78 |
79 | fetchMeta.LastFetchedAt = time.Now()
80 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
81 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. err: %w", err)
82 | }
83 |
84 | return nil
85 | }
86 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/vulsio/goval-dictionary
2 |
3 | go 1.24.0
4 |
5 | require (
6 | github.com/cheggaaa/pb/v3 v3.1.7
7 | github.com/glebarez/sqlite v1.11.0
8 | github.com/go-redis/redis/v8 v8.11.5
9 | github.com/google/go-cmp v0.7.0
10 | github.com/hashicorp/go-version v1.8.0
11 | github.com/inconshreveable/log15 v3.0.0-testing.5+incompatible
12 | github.com/k0kubun/pp v3.0.1+incompatible
13 | github.com/klauspost/compress v1.18.2
14 | github.com/knqyf263/go-rpm-version v0.0.0-20220614171824-631e686d1075
15 | github.com/labstack/echo/v4 v4.13.4
16 | github.com/mitchellh/go-homedir v1.1.0
17 | github.com/spf13/cobra v1.10.2
18 | github.com/spf13/viper v1.21.0
19 | github.com/ulikunitz/xz v0.5.15
20 | golang.org/x/net v0.47.0
21 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2
22 | gopkg.in/yaml.v2 v2.4.0
23 | gorm.io/driver/mysql v1.5.5
24 | gorm.io/driver/postgres v1.5.7
25 | gorm.io/gorm v1.25.7
26 | )
27 |
28 | require (
29 | github.com/VividCortex/ewma v1.2.0 // indirect
30 | github.com/cespare/xxhash/v2 v2.3.0 // indirect
31 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
32 | github.com/dustin/go-humanize v1.0.1 // indirect
33 | github.com/fatih/color v1.18.0 // indirect
34 | github.com/fsnotify/fsnotify v1.9.0 // indirect
35 | github.com/glebarez/go-sqlite v1.21.2 // indirect
36 | github.com/go-sql-driver/mysql v1.7.1 // indirect
37 | github.com/go-stack/stack v1.8.0 // indirect
38 | github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
39 | github.com/google/uuid v1.6.0 // indirect
40 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
41 | github.com/jackc/pgpassfile v1.0.0 // indirect
42 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
43 | github.com/jackc/pgx/v5 v5.5.4 // indirect
44 | github.com/jackc/puddle/v2 v2.2.1 // indirect
45 | github.com/jinzhu/inflection v1.0.0 // indirect
46 | github.com/jinzhu/now v1.1.5 // indirect
47 | github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect
48 | github.com/labstack/gommon v0.4.2 // indirect
49 | github.com/mattn/go-colorable v0.1.14 // indirect
50 | github.com/mattn/go-isatty v0.0.20 // indirect
51 | github.com/mattn/go-runewidth v0.0.16 // indirect
52 | github.com/pelletier/go-toml/v2 v2.2.4 // indirect
53 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
54 | github.com/rivo/uniseg v0.4.7 // indirect
55 | github.com/sagikazarmark/locafero v0.11.0 // indirect
56 | github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
57 | github.com/spf13/afero v1.15.0 // indirect
58 | github.com/spf13/cast v1.10.0 // indirect
59 | github.com/spf13/pflag v1.0.10 // indirect
60 | github.com/subosito/gotenv v1.6.0 // indirect
61 | github.com/valyala/bytebufferpool v1.0.0 // indirect
62 | github.com/valyala/fasttemplate v1.2.2 // indirect
63 | go.yaml.in/yaml/v3 v3.0.4 // indirect
64 | golang.org/x/crypto v0.45.0 // indirect
65 | golang.org/x/sync v0.18.0 // indirect
66 | golang.org/x/sys v0.38.0 // indirect
67 | golang.org/x/term v0.37.0 // indirect
68 | golang.org/x/text v0.31.0 // indirect
69 | golang.org/x/time v0.11.0 // indirect
70 | modernc.org/libc v1.22.5 // indirect
71 | modernc.org/mathutil v1.5.0 // indirect
72 | modernc.org/memory v1.5.0 // indirect
73 | modernc.org/sqlite v1.23.1 // indirect
74 | )
75 |
--------------------------------------------------------------------------------
/fetcher/ubuntu/ubuntu.go:
--------------------------------------------------------------------------------
1 | package ubuntu
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/inconshreveable/log15"
8 | "golang.org/x/xerrors"
9 |
10 | "github.com/vulsio/goval-dictionary/config"
11 | "github.com/vulsio/goval-dictionary/fetcher/util"
12 | )
13 |
14 | func newFetchRequests(target []string) (reqs []util.FetchRequest) {
15 | for _, v := range target {
16 | switch url := getOVALURL(v); url {
17 | case "unknown":
18 | log15.Warn("Skip unknown ubuntu.", "version", v)
19 | case "unsupported":
20 | log15.Warn("Skip unsupported ubuntu version.", "version", v)
21 | log15.Warn("See https://wiki.ubuntu.com/Releases for supported versions")
22 | default:
23 | reqs = append(reqs, util.FetchRequest{
24 | Target: v,
25 | URL: url,
26 | MIMEType: util.MIMETypeBzip2,
27 | })
28 | }
29 | }
30 | return
31 | }
32 |
33 | func getOVALURL(version string) string {
34 | major, minor, ok := strings.Cut(version, ".")
35 | if !ok {
36 | return "unknown"
37 | }
38 |
39 | const main = "https://security-metadata.canonical.com/oval/oci.com.ubuntu.%s.cve.oval.xml.bz2"
40 | switch major {
41 | case "4", "5", "6", "7", "8", "9", "10", "11", "12":
42 | return "unsupported"
43 | case "14":
44 | switch minor {
45 | case "04":
46 | return fmt.Sprintf(main, config.Ubuntu1404)
47 | case "10":
48 | return "unsupported"
49 | default:
50 | return "unknown"
51 | }
52 | case "16":
53 | switch minor {
54 | case "04":
55 | return fmt.Sprintf(main, config.Ubuntu1604)
56 | case "10":
57 | return "unsupported"
58 | default:
59 | return "unknown"
60 | }
61 | case "17":
62 | return "unsupported"
63 | case "18":
64 | switch minor {
65 | case "04":
66 | return fmt.Sprintf(main, config.Ubuntu1804)
67 | case "10":
68 | return "unsupported"
69 | default:
70 | return "unknown"
71 | }
72 | case "19":
73 | return "unsupported"
74 | case "20":
75 | switch minor {
76 | case "04":
77 | return fmt.Sprintf(main, config.Ubuntu2004)
78 | case "10":
79 | return "unsupported"
80 | default:
81 | return "unknown"
82 | }
83 | case "21":
84 | return "unsupported"
85 | case "22":
86 | switch minor {
87 | case "04":
88 | return fmt.Sprintf(main, config.Ubuntu2204)
89 | case "10":
90 | return "unsupported"
91 | default:
92 | return "unknown"
93 | }
94 | case "23":
95 | return "unsupported"
96 | case "24":
97 | switch minor {
98 | case "04":
99 | return fmt.Sprintf(main, config.Ubuntu2404)
100 | case "10":
101 | return fmt.Sprintf(main, config.Ubuntu2410)
102 | default:
103 | return "unknown"
104 | }
105 | case "25":
106 | switch minor {
107 | case "04":
108 | return fmt.Sprintf(main, config.Ubuntu2504)
109 | default:
110 | return "unknown"
111 | }
112 | default:
113 | return "unknown"
114 | }
115 | }
116 |
117 | // FetchFiles fetch OVAL from Ubuntu
118 | func FetchFiles(versions []string) ([]util.FetchResult, error) {
119 | reqs := newFetchRequests(versions)
120 | if len(reqs) == 0 {
121 | return nil, xerrors.New("There are no versions to fetch")
122 | }
123 |
124 | results := make([]util.FetchResult, 0, len(reqs))
125 | for _, req := range reqs {
126 | rs, err := util.FetchFeedFiles([]util.FetchRequest{req})
127 | if err != nil {
128 | return nil, xerrors.Errorf("Failed to fetch. err: %w", err)
129 | }
130 | results = append(results, rs...)
131 | }
132 | return results, nil
133 | }
134 |
--------------------------------------------------------------------------------
/commands/fetch-alpine.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "errors"
5 | "time"
6 |
7 | "golang.org/x/xerrors"
8 | yaml "gopkg.in/yaml.v2"
9 |
10 | "github.com/inconshreveable/log15"
11 | "github.com/spf13/cobra"
12 | "github.com/spf13/viper"
13 |
14 | c "github.com/vulsio/goval-dictionary/config"
15 | "github.com/vulsio/goval-dictionary/db"
16 | fetcher "github.com/vulsio/goval-dictionary/fetcher/alpine"
17 | "github.com/vulsio/goval-dictionary/log"
18 | "github.com/vulsio/goval-dictionary/models"
19 | "github.com/vulsio/goval-dictionary/models/alpine"
20 | "github.com/vulsio/goval-dictionary/util"
21 | )
22 |
23 | // fetchAlpineCmd is Subcommand for fetch Alpine secdb
24 | // https://secdb.alpinelinux.org/
25 | var fetchAlpineCmd = &cobra.Command{
26 | Use: "alpine [version]",
27 | Short: "Fetch Vulnerability dictionary from Alpine secdb",
28 | Long: `Fetch Vulnerability dictionary from Alpine secdb`,
29 | Args: cobra.MinimumNArgs(1),
30 | RunE: fetchAlpine,
31 | Example: "$ goval-dictionary fetch alpine 3.16 3.17",
32 | }
33 |
34 | func init() {
35 | fetchCmd.AddCommand(fetchAlpineCmd)
36 | }
37 |
38 | func fetchAlpine(_ *cobra.Command, args []string) (err error) {
39 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil {
40 | return xerrors.Errorf("Failed to SetLogger. err: %w", err)
41 | }
42 |
43 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
44 | if err != nil {
45 | if errors.Is(err, db.ErrDBLocked) {
46 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
47 | }
48 | return xerrors.Errorf("Failed to open DB. err: %w", err)
49 | }
50 |
51 | fetchMeta, err := driver.GetFetchMeta()
52 | if err != nil {
53 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
54 | }
55 | if fetchMeta.OutDated() {
56 | return xerrors.Errorf("Failed to Insert CVEs into DB. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
57 | }
58 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion.
59 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
60 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. err: %w", err)
61 | }
62 |
63 | results, err := fetcher.FetchFiles(util.Unique(args))
64 | if err != nil {
65 | return xerrors.Errorf("Failed to fetch files. err: %w", err)
66 | }
67 |
68 | osVerDefs := map[string][]models.Definition{}
69 | for _, r := range results {
70 | var secdb alpine.SecDB
71 | if err := yaml.Unmarshal(r.Body, &secdb); err != nil {
72 | return xerrors.Errorf("Failed to unmarshal. err: %w", err)
73 | }
74 | osVerDefs[r.Target] = append(osVerDefs[r.Target], alpine.ConvertToModel(&secdb)...)
75 | }
76 |
77 | for osVer, defs := range osVerDefs {
78 | root := models.Root{
79 | Family: c.Alpine,
80 | OSVersion: osVer,
81 | Definitions: defs,
82 | Timestamp: time.Now(),
83 | }
84 | if err := driver.InsertOval(&root); err != nil {
85 | return xerrors.Errorf("Failed to insert OVAL. err: %w", err)
86 | }
87 | log15.Info("Finish", "Updated", len(root.Definitions))
88 | }
89 |
90 | fetchMeta.LastFetchedAt = time.Now()
91 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
92 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. err: %w", err)
93 | }
94 |
95 | return nil
96 | }
97 |
--------------------------------------------------------------------------------
/models/debian/debian.go:
--------------------------------------------------------------------------------
1 | package debian
2 |
3 | import (
4 | "strings"
5 | "time"
6 |
7 | "github.com/spf13/viper"
8 |
9 | "github.com/vulsio/goval-dictionary/models"
10 | "github.com/vulsio/goval-dictionary/models/util"
11 | )
12 |
13 | type distroPackage struct {
14 | osVer string
15 | pack models.Package
16 | }
17 |
18 | // ConvertToModel Convert OVAL to models
19 | func ConvertToModel(root *Root) (defs []models.Definition) {
20 | for _, ovaldef := range root.Definitions.Definitions {
21 | if strings.Contains(ovaldef.Description, "** REJECT **") {
22 | continue
23 | }
24 |
25 | cves := []models.Cve{}
26 | rs := []models.Reference{}
27 | for _, r := range ovaldef.References {
28 | if r.Source == "CVE" {
29 | cves = append(cves, models.Cve{
30 | CveID: r.RefID,
31 | Href: r.RefURL,
32 | })
33 | }
34 |
35 | rs = append(rs, models.Reference{
36 | Source: r.Source,
37 | RefID: r.RefID,
38 | RefURL: r.RefURL,
39 | })
40 | }
41 |
42 | def := models.Definition{
43 | DefinitionID: ovaldef.ID,
44 | Title: ovaldef.Title,
45 | Description: ovaldef.Description,
46 | Advisory: models.Advisory{
47 | Severity: "",
48 | Cves: cves,
49 | Bugzillas: []models.Bugzilla{},
50 | AffectedCPEList: []models.Cpe{},
51 | Issued: time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC),
52 | Updated: time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC),
53 | },
54 | Debian: &models.Debian{
55 | DSA: ovaldef.Debian.DSA,
56 | MoreInfo: ovaldef.Debian.MoreInfo,
57 | Date: util.ParsedOrDefaultTime([]string{"2006-01-02"}, ovaldef.Debian.Date),
58 | },
59 | AffectedPacks: collectDebianPacks(ovaldef.Criteria),
60 | References: rs,
61 | }
62 |
63 | if viper.GetBool("no-details") {
64 | def.Title = ""
65 | def.Description = ""
66 | def.Advisory.Severity = ""
67 | def.Advisory.Bugzillas = []models.Bugzilla{}
68 | def.Advisory.AffectedCPEList = []models.Cpe{}
69 | def.Advisory.Issued = time.Time{}
70 | def.Advisory.Updated = time.Time{}
71 | def.Debian = nil
72 | def.References = []models.Reference{}
73 | }
74 |
75 | defs = append(defs, def)
76 | }
77 | return
78 | }
79 |
80 | func collectDebianPacks(cri Criteria) []models.Package {
81 | distPacks := walkDebian(cri, "", []distroPackage{})
82 | packs := make([]models.Package, len(distPacks))
83 | for i, distPack := range distPacks {
84 | packs[i] = distPack.pack
85 | }
86 | return packs
87 | }
88 |
89 | func walkDebian(cri Criteria, osVer string, acc []distroPackage) []distroPackage {
90 | for _, c := range cri.Criterions {
91 | if strings.HasPrefix(c.Comment, "Debian ") &&
92 | strings.HasSuffix(c.Comment, " is installed") {
93 | osVer = strings.TrimSuffix(strings.TrimPrefix(c.Comment, "Debian "), " is installed")
94 | }
95 | ss := strings.Split(c.Comment, " DPKG is earlier than ")
96 | if len(ss) != 2 {
97 | continue
98 | }
99 |
100 | // "0" means notyetfixed or erroneous information.
101 | // Not available because "0" includes erroneous info...
102 | if ss[1] == "0" {
103 | continue
104 | }
105 | acc = append(acc, distroPackage{
106 | osVer: osVer,
107 | pack: models.Package{
108 | Name: ss[0],
109 | Version: strings.Split(ss[1], " ")[0],
110 | },
111 | })
112 | }
113 |
114 | if len(cri.Criterias) == 0 {
115 | return acc
116 | }
117 | for _, c := range cri.Criterias {
118 | acc = walkDebian(c, osVer, acc)
119 | }
120 | return acc
121 | }
122 |
--------------------------------------------------------------------------------
/fetcher/fedora/types.go:
--------------------------------------------------------------------------------
1 | package fedora
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "golang.org/x/xerrors"
8 |
9 | models "github.com/vulsio/goval-dictionary/models/fedora"
10 | )
11 |
12 | // repoMd has repomd data
13 | type repoMd struct {
14 | RepoList []repo `xml:"data"`
15 | }
16 |
17 | // repo has a repo data
18 | type repo struct {
19 | Type string `xml:"type,attr"`
20 | Location location `xml:"location"`
21 | }
22 |
23 | // location has a location of repomd
24 | type location struct {
25 | Href string `xml:"href,attr"`
26 | }
27 |
28 | type bugzillaXML struct {
29 | Blocked []string `xml:"bug>blocked" json:"blocked,omitempty"`
30 | Alias string `xml:"bug>alias" json:"alias,omitempty"`
31 | }
32 |
33 | // moduleInfo has a data of modules.yaml
34 | type moduleInfo struct {
35 | Version int `yaml:"version"`
36 | Data struct {
37 | Name string `yaml:"name"`
38 | Stream string `yaml:"stream"`
39 | Version int64 `yaml:"version"`
40 | Context string `yaml:"context"`
41 | Arch string `yaml:"arch"`
42 | Artifacts struct {
43 | Rpms []Rpm `yaml:"rpms"`
44 | } `yaml:"artifacts"`
45 | } `yaml:"data"`
46 | }
47 |
48 | type moduleInfosPerVersion map[string]moduleInfosPerPackage
49 |
50 | type moduleInfosPerPackage map[string]moduleInfo
51 |
52 | // ConvertToUpdateInfoTitle generates file name from data of modules.yaml
53 | func (f moduleInfo) ConvertToUpdateInfoTitle() string {
54 | return fmt.Sprintf("%s-%s-%d.%s", f.Data.Name, f.Data.Stream, f.Data.Version, f.Data.Context)
55 | }
56 |
57 | // ConvertToModularityLabel generates modularity_label from data of modules.yaml
58 | func (f moduleInfo) ConvertToModularityLabel() string {
59 | return fmt.Sprintf("%s:%s:%d:%s", f.Data.Name, f.Data.Stream, f.Data.Version, f.Data.Context)
60 | }
61 |
62 | // Rpm is a package name of data/artifacts/rpms in modules.yaml
63 | type Rpm string
64 |
65 | // NewPackageFromRpm generates Package{} by parsing package name
66 | func (r Rpm) NewPackageFromRpm() (models.Package, error) {
67 | filename := strings.TrimSuffix(string(r), ".rpm")
68 |
69 | archIndex := strings.LastIndex(filename, ".")
70 | if archIndex == -1 {
71 | return models.Package{}, xerrors.Errorf("Failed to parse arch from filename: %s", filename)
72 | }
73 | arch := filename[archIndex+1:]
74 |
75 | relIndex := strings.LastIndex(filename[:archIndex], "-")
76 | if relIndex == -1 {
77 | return models.Package{}, xerrors.Errorf("Failed to parse release from filename: %s", filename)
78 | }
79 | rel := filename[relIndex+1 : archIndex]
80 |
81 | verIndex := strings.LastIndex(filename[:relIndex], "-")
82 | if verIndex == -1 {
83 | return models.Package{}, xerrors.Errorf("Failed to parse version from filename: %s", filename)
84 | }
85 | ver := filename[verIndex+1 : relIndex]
86 |
87 | epochIndex := strings.Index(ver, ":")
88 | var epoch string
89 | if epochIndex == -1 {
90 | epoch = "0"
91 | } else {
92 | epoch = ver[:epochIndex]
93 | ver = ver[epochIndex+1:]
94 | }
95 |
96 | name := filename[:verIndex]
97 | pkg := models.Package{
98 | Name: name,
99 | Epoch: epoch,
100 | Version: ver,
101 | Release: rel,
102 | Arch: arch,
103 | Filename: filename,
104 | }
105 | return pkg, nil
106 | }
107 |
108 | // uniquePackages returns deduplicated []Package by Filename
109 | // If Filename is the same, all other information is considered to be the same
110 | func uniquePackages(pkgs []models.Package) []models.Package {
111 | tmp := make(map[string]models.Package)
112 | ret := []models.Package{}
113 | for _, pkg := range pkgs {
114 | tmp[pkg.Filename] = pkg
115 | }
116 | for _, v := range tmp {
117 | ret = append(ret, v)
118 | }
119 | return ret
120 | }
121 |
122 | func mergeUpdates(source map[string]*models.Updates, target map[string]*models.Updates) map[string]*models.Updates {
123 | for osVer, sourceUpdates := range source {
124 | if targetUpdates, ok := target[osVer]; ok {
125 | source[osVer].UpdateList = append(sourceUpdates.UpdateList, targetUpdates.UpdateList...)
126 | }
127 | }
128 | return source
129 | }
130 |
--------------------------------------------------------------------------------
/db/db.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "strings"
5 | "time"
6 |
7 | "golang.org/x/xerrors"
8 |
9 | c "github.com/vulsio/goval-dictionary/config"
10 | "github.com/vulsio/goval-dictionary/models"
11 | )
12 |
13 | // DB is interface for a database driver
14 | type DB interface {
15 | Name() string
16 | OpenDB(string, string, bool, Option) error
17 | CloseDB() error
18 | MigrateDB() error
19 |
20 | IsGovalDictModelV1() (bool, error)
21 | GetFetchMeta() (*models.FetchMeta, error)
22 | UpsertFetchMeta(*models.FetchMeta) error
23 |
24 | GetByPackName(family string, osVer string, packName string, arch string) ([]models.Definition, error)
25 | GetByCveID(family string, osVer string, cveID string, arch string) ([]models.Definition, error)
26 | GetAdvisories(family string, osVer string) (map[string][]string, error)
27 | InsertOval(*models.Root) error
28 | CountDefs(string, string) (int, error)
29 | GetLastModified(string, string) (time.Time, error)
30 | }
31 |
32 | // Option :
33 | type Option struct {
34 | RedisTimeout time.Duration
35 | }
36 |
37 | // NewDB return DB accessor.
38 | func NewDB(dbType, dbPath string, debugSQL bool, option Option) (driver DB, err error) {
39 | if driver, err = newDB(dbType); err != nil {
40 | return driver, xerrors.Errorf("Failed to new db. err: %w", err)
41 | }
42 |
43 | if err := driver.OpenDB(dbType, dbPath, debugSQL, option); err != nil {
44 | return nil, xerrors.Errorf("Failed to open db. err: %w", err)
45 | }
46 |
47 | isV1, err := driver.IsGovalDictModelV1()
48 | if err != nil {
49 | return nil, xerrors.Errorf("Failed to IsGovalDictModelV1. err: %w", err)
50 | }
51 | if isV1 {
52 | return nil, xerrors.New("Failed to NewDB. Since SchemaVersion is incompatible, delete Database and fetch again.")
53 | }
54 |
55 | if err := driver.MigrateDB(); err != nil {
56 | return driver, xerrors.Errorf("Failed to migrate db. err: %w", err)
57 | }
58 | return driver, nil
59 | }
60 |
61 | func newDB(dbType string) (DB, error) {
62 | switch dbType {
63 | case dialectSqlite3, dialectMysql, dialectPostgreSQL:
64 | return &RDBDriver{name: dbType}, nil
65 | case dialectRedis:
66 | return &RedisDriver{name: dbType}, nil
67 | }
68 | return nil, xerrors.Errorf("Invalid database dialect. dbType: %s", dbType)
69 | }
70 |
71 | func formatFamilyAndOSVer(family, osVer string) (string, string, error) {
72 | switch family {
73 | case c.Debian:
74 | osVer = major(osVer)
75 | case c.Ubuntu:
76 | osVer = majorDotMinor(osVer)
77 | case c.Raspbian:
78 | family = c.Debian
79 | osVer = major(osVer)
80 | case c.RedHat:
81 | osVer = major(osVer)
82 | case c.CentOS:
83 | family = c.RedHat
84 | osVer = major(osVer)
85 | case c.Oracle:
86 | osVer = major(osVer)
87 | case c.Amazon:
88 | osVer = getAmazonLinuxVer(osVer)
89 | case c.Alpine:
90 | osVer = majorDotMinor(osVer)
91 | case c.Fedora:
92 | osVer = major(osVer)
93 | case c.OpenSUSE:
94 | if osVer != "tumbleweed" {
95 | osVer = majorDotMinor(osVer)
96 | }
97 | case c.OpenSUSELeap, c.SUSEEnterpriseDesktop, c.SUSEEnterpriseServer:
98 | osVer = majorDotMinor(osVer)
99 | default:
100 | return "", "", xerrors.Errorf("Failed to detect family. err: unknown os family(%s)", family)
101 | }
102 |
103 | return family, osVer, nil
104 | }
105 |
106 | func major(osVer string) (majorVersion string) {
107 | return strings.Split(osVer, ".")[0]
108 | }
109 |
110 | func majorDotMinor(osVer string) (majorMinorVersion string) {
111 | ss := strings.Split(osVer, ".")
112 | if len(ss) < 3 {
113 | return osVer
114 | }
115 | return strings.Join(ss[:2], ".")
116 | }
117 |
118 | // getAmazonLinuxVer returns AmazonLinux 1, 2, 2022, 2023
119 | func getAmazonLinuxVer(osVersion string) string {
120 | ss := strings.Fields(osVersion)
121 | if ss[0] == "2023" {
122 | return "2023"
123 | }
124 | if ss[0] == "2022" {
125 | return "2022"
126 | }
127 | if ss[0] == "2" {
128 | return "2"
129 | }
130 | return "1"
131 | }
132 |
133 | func filterByRedHatMajor(packs []models.Package, majorVer string) (filtered []models.Package) {
134 | for _, p := range packs {
135 | if p.NotFixedYet ||
136 | strings.Contains(p.Version, ".el"+majorVer) || strings.Contains(p.Version, ".module+el"+majorVer) {
137 | filtered = append(filtered, p)
138 | }
139 | }
140 | return
141 | }
142 |
--------------------------------------------------------------------------------
/commands/fetch-ubuntu.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "encoding/xml"
5 | "errors"
6 | "strings"
7 | "time"
8 |
9 | "github.com/inconshreveable/log15"
10 | "github.com/spf13/cobra"
11 | "github.com/spf13/viper"
12 | "golang.org/x/xerrors"
13 |
14 | c "github.com/vulsio/goval-dictionary/config"
15 | "github.com/vulsio/goval-dictionary/db"
16 | fetcher "github.com/vulsio/goval-dictionary/fetcher/ubuntu"
17 | "github.com/vulsio/goval-dictionary/log"
18 | "github.com/vulsio/goval-dictionary/models"
19 | "github.com/vulsio/goval-dictionary/models/ubuntu"
20 | "github.com/vulsio/goval-dictionary/util"
21 | )
22 |
23 | // fetchUbuntuCmd is Subcommand for fetch Ubuntu OVAL
24 | var fetchUbuntuCmd = &cobra.Command{
25 | Use: "ubuntu [version]",
26 | Short: "Fetch Vulnerability dictionary from Ubuntu",
27 | Long: `Fetch Vulnerability dictionary from Ubuntu`,
28 | Args: cobra.MinimumNArgs(1),
29 | RunE: fetchUbuntu,
30 | Example: "$ goval-dictionary fetch ubuntu 20.04 22.04",
31 | }
32 |
33 | func init() {
34 | fetchCmd.AddCommand(fetchUbuntuCmd)
35 | }
36 |
37 | func fetchUbuntu(_ *cobra.Command, args []string) (err error) {
38 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil {
39 | return xerrors.Errorf("Failed to SetLogger. err: %w", err)
40 | }
41 |
42 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
43 | if err != nil {
44 | if errors.Is(err, db.ErrDBLocked) {
45 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
46 | }
47 | return xerrors.Errorf("Failed to open DB. err: %w", err)
48 | }
49 |
50 | fetchMeta, err := driver.GetFetchMeta()
51 | if err != nil {
52 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
53 | }
54 | if fetchMeta.OutDated() {
55 | return xerrors.Errorf("Failed to Insert CVEs into DB. SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
56 | }
57 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion.
58 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
59 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. err: %w", err)
60 | }
61 |
62 | results, err := fetcher.FetchFiles(util.Unique(args))
63 | if err != nil {
64 | return xerrors.Errorf("Failed to fetch files. err: %w", err)
65 | }
66 |
67 | for _, r := range results {
68 | ovalroot := ubuntu.Root{}
69 | if err = xml.Unmarshal(r.Body, &ovalroot); err != nil {
70 | return xerrors.Errorf("Failed to unmarshal xml. url: %s, err: %w", r.URL, err)
71 | }
72 |
73 | log15.Info("Fetched", "File", r.URL[strings.LastIndex(r.URL, "/")+1:], "Count", len(ovalroot.Definitions.Definitions), "Timestamp", ovalroot.Generator.Timestamp)
74 | ts, err := time.Parse("2006-01-02T15:04:05", ovalroot.Generator.Timestamp)
75 | if err != nil {
76 | return xerrors.Errorf("Failed to parse timestamp. url: %s, timestamp: %s, err: %w", r.URL, ovalroot.Generator.Timestamp, err)
77 | }
78 | if ts.Before(time.Now().AddDate(0, 0, -3)) {
79 | log15.Warn("The fetched OVAL has not been updated for 3 days, the OVAL URL may have changed, please register a GitHub issue.", "GitHub", "https://github.com/vulsio/goval-dictionary/issues", "OVAL", r.URL, "Timestamp", ovalroot.Generator.Timestamp)
80 | }
81 |
82 | defs, err := ubuntu.ConvertToModel(&ovalroot)
83 | if err != nil {
84 | return xerrors.Errorf("Failed to convert from OVAL to goval-dictionary model. err: %w", err)
85 | }
86 | root := models.Root{
87 | Family: c.Ubuntu,
88 | OSVersion: r.Target,
89 | Definitions: defs,
90 | Timestamp: time.Now(),
91 | }
92 |
93 | if err := driver.InsertOval(&root); err != nil {
94 | return xerrors.Errorf("Failed to insert OVAL. err: %w", err)
95 | }
96 | log15.Info("Finish", "Updated", len(root.Definitions))
97 | }
98 |
99 | fetchMeta.LastFetchedAt = time.Now()
100 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
101 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. err: %w", err)
102 | }
103 |
104 | return nil
105 | }
106 |
--------------------------------------------------------------------------------
/commands/fetch-debian.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "bytes"
5 | "encoding/xml"
6 | "errors"
7 | "strings"
8 | "time"
9 |
10 | "github.com/inconshreveable/log15"
11 | "github.com/spf13/cobra"
12 | "github.com/spf13/viper"
13 | "golang.org/x/net/html/charset"
14 | "golang.org/x/xerrors"
15 |
16 | c "github.com/vulsio/goval-dictionary/config"
17 | "github.com/vulsio/goval-dictionary/db"
18 | fetcher "github.com/vulsio/goval-dictionary/fetcher/debian"
19 | "github.com/vulsio/goval-dictionary/log"
20 | "github.com/vulsio/goval-dictionary/models"
21 | "github.com/vulsio/goval-dictionary/models/debian"
22 | "github.com/vulsio/goval-dictionary/util"
23 | )
24 |
25 | // fetchDebianCmd is Subcommand for fetch Debian OVAL
26 | var fetchDebianCmd = &cobra.Command{
27 | Use: "debian [version]",
28 | Short: "Fetch Vulnerability dictionary from Debian",
29 | Long: `Fetch Vulnerability dictionary from Debian`,
30 | Args: cobra.MinimumNArgs(1),
31 | RunE: fetchDebian,
32 | Example: "$ goval-dictionary fetch debian 10 11",
33 | }
34 |
35 | func init() {
36 | fetchCmd.AddCommand(fetchDebianCmd)
37 | }
38 |
39 | func fetchDebian(_ *cobra.Command, args []string) (err error) {
40 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil {
41 | return xerrors.Errorf("Failed to SetLogger. err: %w", err)
42 | }
43 |
44 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
45 | if err != nil {
46 | if errors.Is(err, db.ErrDBLocked) {
47 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
48 | }
49 | return xerrors.Errorf("Failed to open DB. err: %w", err)
50 | }
51 |
52 | fetchMeta, err := driver.GetFetchMeta()
53 | if err != nil {
54 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
55 | }
56 | if fetchMeta.OutDated() {
57 | return xerrors.Errorf("Failed to Insert CVEs into DB. SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
58 | }
59 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion.
60 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
61 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. err: %w", err)
62 | }
63 |
64 | results, err := fetcher.FetchFiles(util.Unique(args))
65 | if err != nil {
66 | return xerrors.Errorf("Failed to fetch files. err: %w", err)
67 | }
68 |
69 | for _, r := range results {
70 | ovalroot := debian.Root{}
71 |
72 | decoder := xml.NewDecoder(bytes.NewReader(r.Body))
73 | decoder.CharsetReader = charset.NewReaderLabel
74 | if err := decoder.Decode(&ovalroot); err != nil {
75 | return xerrors.Errorf("Failed to unmarshal xml. url: %s, err: %w", r.URL, err)
76 | }
77 |
78 | log15.Info("Fetched", "File", r.URL[strings.LastIndex(r.URL, "/")+1:], "Count", len(ovalroot.Definitions.Definitions), "Timestamp", ovalroot.Generator.Timestamp)
79 | ts, err := time.Parse("2006-01-02T15:04:05.999-07:00", ovalroot.Generator.Timestamp)
80 | if err != nil {
81 | return xerrors.Errorf("Failed to parse timestamp. url: %s, timestamp: %s, err: %w", r.URL, ovalroot.Generator.Timestamp, err)
82 | }
83 | if ts.Before(time.Now().AddDate(0, 0, -3)) {
84 | log15.Warn("The fetched OVAL has not been updated for 3 days, the OVAL URL may have changed, please register a GitHub issue.", "GitHub", "https://github.com/vulsio/goval-dictionary/issues", "OVAL", r.URL, "Timestamp", ovalroot.Generator.Timestamp)
85 | }
86 |
87 | root := models.Root{
88 | Family: c.Debian,
89 | OSVersion: r.Target,
90 | Definitions: debian.ConvertToModel(&ovalroot),
91 | Timestamp: time.Now(),
92 | }
93 |
94 | if err := driver.InsertOval(&root); err != nil {
95 | return xerrors.Errorf("Failed to insert OVAL. err: %w", err)
96 | }
97 | log15.Info("Finish", "Updated", len(root.Definitions))
98 | }
99 |
100 | fetchMeta.LastFetchedAt = time.Now()
101 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
102 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. err: %w", err)
103 | }
104 |
105 | return nil
106 | }
107 |
--------------------------------------------------------------------------------
/commands/fetch-oracle.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "encoding/xml"
5 | "errors"
6 | "slices"
7 | "strings"
8 | "time"
9 |
10 | "github.com/inconshreveable/log15"
11 | "github.com/spf13/cobra"
12 | "github.com/spf13/viper"
13 | "golang.org/x/xerrors"
14 |
15 | c "github.com/vulsio/goval-dictionary/config"
16 | "github.com/vulsio/goval-dictionary/db"
17 | fetcher "github.com/vulsio/goval-dictionary/fetcher/oracle"
18 | "github.com/vulsio/goval-dictionary/log"
19 | "github.com/vulsio/goval-dictionary/models"
20 | "github.com/vulsio/goval-dictionary/models/oracle"
21 | )
22 |
23 | // fetchOracleCmd is Subcommand for fetch Oracle OVAL
24 | var fetchOracleCmd = &cobra.Command{
25 | Use: "oracle [version]",
26 | Short: "Fetch Vulnerability dictionary from Oracle",
27 | Long: `Fetch Vulnerability dictionary from Oracle`,
28 | Args: cobra.MinimumNArgs(1),
29 | RunE: fetchOracle,
30 | Example: "$ goval-dictionary fetch oracle 8 9",
31 | }
32 |
33 | func init() {
34 | fetchCmd.AddCommand(fetchOracleCmd)
35 | }
36 |
37 | func fetchOracle(_ *cobra.Command, args []string) (err error) {
38 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil {
39 | return xerrors.Errorf("Failed to SetLogger. err: %w", err)
40 | }
41 |
42 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
43 | if err != nil {
44 | if errors.Is(err, db.ErrDBLocked) {
45 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
46 | }
47 | return xerrors.Errorf("Failed to open DB. err: %w", err)
48 | }
49 |
50 | fetchMeta, err := driver.GetFetchMeta()
51 | if err != nil {
52 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
53 | }
54 | if fetchMeta.OutDated() {
55 | return xerrors.Errorf("Failed to Insert CVEs into DB. SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
56 | }
57 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion.
58 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
59 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. err: %w", err)
60 | }
61 |
62 | results, err := fetcher.FetchFiles()
63 | if err != nil {
64 | return xerrors.Errorf("Failed to fetch files. err: %w", err)
65 | }
66 |
67 | osVerDefs := map[string][]models.Definition{}
68 | for _, r := range results {
69 | ovalroot := oracle.Root{}
70 | if err = xml.Unmarshal(r.Body, &ovalroot); err != nil {
71 | return xerrors.Errorf("Failed to unmarshal xml. url: %s, err: %w", r.URL, err)
72 | }
73 | log15.Info("Fetched", "File", r.URL[strings.LastIndex(r.URL, "/")+1:], "Count", len(ovalroot.Definitions.Definitions), "Timestamp", ovalroot.Generator.Timestamp)
74 | ts, err := time.Parse("2006-01-02T15:04:05", ovalroot.Generator.Timestamp)
75 | if err != nil {
76 | return xerrors.Errorf("Failed to parse timestamp. url: %s, timestamp: %s, err: %w", r.URL, ovalroot.Generator.Timestamp, err)
77 | }
78 | if ts.Before(time.Now().AddDate(0, 0, -3)) {
79 | log15.Warn("The fetched OVAL has not been updated for 3 days, the OVAL URL may have changed, please register a GitHub issue.", "GitHub", "https://github.com/vulsio/goval-dictionary/issues", "OVAL", r.URL, "Timestamp", ovalroot.Generator.Timestamp)
80 | }
81 |
82 | for osVer, defs := range oracle.ConvertToModel(&ovalroot) {
83 | if slices.Contains(args, osVer) {
84 | osVerDefs[osVer] = append(osVerDefs[osVer], defs...)
85 | }
86 | }
87 | }
88 |
89 | for osVer, defs := range osVerDefs {
90 | root := models.Root{
91 | Family: c.Oracle,
92 | OSVersion: osVer,
93 | Definitions: defs,
94 | Timestamp: time.Now(),
95 | }
96 |
97 | if err := driver.InsertOval(&root); err != nil {
98 | return xerrors.Errorf("Failed to insert OVAL. err: %w", err)
99 | }
100 | log15.Info("Finish", "Updated", len(root.Definitions))
101 | }
102 |
103 | fetchMeta.LastFetchedAt = time.Now()
104 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
105 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. err: %w", err)
106 | }
107 |
108 | return nil
109 | }
110 |
--------------------------------------------------------------------------------
/models/oracle/oracle.go:
--------------------------------------------------------------------------------
1 | package oracle
2 |
3 | import (
4 | "strings"
5 | "time"
6 |
7 | "github.com/spf13/viper"
8 |
9 | "github.com/vulsio/goval-dictionary/models"
10 | )
11 |
12 | type distroPackage struct {
13 | osVer string
14 | pack models.Package
15 | }
16 |
17 | // ConvertToModel Convert OVAL to models
18 | func ConvertToModel(root *Root) (defs map[string][]models.Definition) {
19 | osVerDefs := map[string][]models.Definition{}
20 | for _, ovaldef := range root.Definitions.Definitions {
21 | if strings.Contains(ovaldef.Description, "** REJECT **") {
22 | continue
23 | }
24 |
25 | cves := []models.Cve{}
26 | for _, c := range ovaldef.Advisory.Cves {
27 | cves = append(cves, models.Cve{
28 | CveID: c.CveID,
29 | Href: c.Href,
30 | })
31 | }
32 |
33 | rs := []models.Reference{}
34 | for _, r := range ovaldef.References {
35 | rs = append(rs, models.Reference{
36 | Source: r.Source,
37 | RefID: r.RefID,
38 | RefURL: r.RefURL,
39 | })
40 | }
41 |
42 | osVerPacks := map[string][]models.Package{}
43 | for _, distPack := range collectOraclePacks(ovaldef.Criteria) {
44 | osVerPacks[distPack.osVer] = append(osVerPacks[distPack.osVer], distPack.pack)
45 | }
46 |
47 | for osVer, packs := range osVerPacks {
48 | def := models.Definition{
49 | DefinitionID: ovaldef.ID,
50 | Title: strings.TrimSpace(ovaldef.Title),
51 | Description: strings.TrimSpace(ovaldef.Description),
52 | Advisory: models.Advisory{
53 | Severity: ovaldef.Advisory.Severity,
54 | Cves: append([]models.Cve{}, cves...), // If the same slice is used, it will only be stored once in the DB
55 | Bugzillas: []models.Bugzilla{},
56 | AffectedCPEList: []models.Cpe{},
57 | Issued: time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC),
58 | Updated: time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC),
59 | },
60 | Debian: nil,
61 | AffectedPacks: append([]models.Package{}, packs...), // If the same slice is used, it will only be stored once in the DB
62 | References: append([]models.Reference{}, rs...), // If the same slice is used, it will only be stored once in the DB
63 | }
64 |
65 | if viper.GetBool("no-details") {
66 | def.Title = ""
67 | def.Description = ""
68 | def.Advisory.Severity = ""
69 | def.Advisory.Bugzillas = []models.Bugzilla{}
70 | def.Advisory.AffectedCPEList = []models.Cpe{}
71 | def.Advisory.Issued = time.Time{}
72 | def.Advisory.Updated = time.Time{}
73 | def.References = []models.Reference{}
74 | }
75 |
76 | osVerDefs[osVer] = append(osVerDefs[osVer], def)
77 | }
78 | }
79 |
80 | return osVerDefs
81 | }
82 |
83 | func collectOraclePacks(cri Criteria) []distroPackage {
84 | return walkOracle(cri, "", "", "", []distroPackage{})
85 | }
86 |
87 | func walkOracle(cri Criteria, osVer, arch, label string, acc []distroPackage) []distroPackage {
88 | for _, c := range cri.Criterions {
89 | switch {
90 | case strings.HasPrefix(c.Comment, "Oracle Linux ") && strings.HasSuffix(c.Comment, " is installed"): //
91 | osVer = strings.TrimSuffix(strings.TrimPrefix(c.Comment, "Oracle Linux "), " is installed")
92 | case strings.HasPrefix(c.Comment, "Oracle Linux arch is "): //
93 | arch = strings.TrimSpace(strings.TrimPrefix(c.Comment, "Oracle Linux arch is "))
94 | case strings.HasPrefix(c.Comment, "Module ") && strings.HasSuffix(c.Comment, " is enabled"): //
95 | label = strings.TrimSuffix(strings.TrimPrefix(c.Comment, "Module "), " is enabled")
96 | default: // ,
97 | name, evr, ok := strings.Cut(c.Comment, " is earlier than ")
98 | if !ok {
99 | break
100 | }
101 | acc = append(acc, distroPackage{
102 | osVer: osVer,
103 | pack: models.Package{
104 | Name: name,
105 | Version: evr,
106 | Arch: arch,
107 | ModularityLabel: label,
108 | },
109 | })
110 | }
111 | }
112 |
113 | if len(cri.Criterias) == 0 {
114 | return acc
115 | }
116 | for _, c := range cri.Criterias {
117 | acc = walkOracle(c, osVer, arch, label, acc)
118 | }
119 | return acc
120 | }
121 |
--------------------------------------------------------------------------------
/commands/fetch-redhat.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "encoding/xml"
5 | "errors"
6 | "fmt"
7 | "strings"
8 | "time"
9 |
10 | "github.com/inconshreveable/log15"
11 | "github.com/spf13/cobra"
12 | "github.com/spf13/viper"
13 | "golang.org/x/xerrors"
14 |
15 | c "github.com/vulsio/goval-dictionary/config"
16 | "github.com/vulsio/goval-dictionary/db"
17 | fetcher "github.com/vulsio/goval-dictionary/fetcher/redhat"
18 | "github.com/vulsio/goval-dictionary/log"
19 | "github.com/vulsio/goval-dictionary/models"
20 | "github.com/vulsio/goval-dictionary/models/redhat"
21 | "github.com/vulsio/goval-dictionary/util"
22 | )
23 |
24 | // fetchRedHatCmd is Subcommand for fetch RedHat OVAL
25 | var fetchRedHatCmd = &cobra.Command{
26 | Use: "redhat [version]",
27 | Short: "Fetch Vulnerability dictionary from RedHat",
28 | Long: `Fetch Vulnerability dictionary from RedHat`,
29 | Args: cobra.MinimumNArgs(1),
30 | RunE: fetchRedHat,
31 | Example: "$ goval-dictionary fetch redhat 8 9",
32 | }
33 |
34 | func init() {
35 | fetchCmd.AddCommand(fetchRedHatCmd)
36 | }
37 |
38 | func fetchRedHat(_ *cobra.Command, args []string) (err error) {
39 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil {
40 | return xerrors.Errorf("Failed to SetLogger. err: %w", err)
41 | }
42 |
43 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
44 | if err != nil {
45 | if errors.Is(err, db.ErrDBLocked) {
46 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
47 | }
48 | return xerrors.Errorf("Failed to open DB. err: %w", err)
49 | }
50 |
51 | fetchMeta, err := driver.GetFetchMeta()
52 | if err != nil {
53 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
54 | }
55 | if fetchMeta.OutDated() {
56 | return xerrors.Errorf("Failed to Insert CVEs into DB. SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
57 | }
58 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion.
59 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
60 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. err: %w", err)
61 | }
62 |
63 | results, err := fetcher.FetchFiles(util.Unique(args))
64 | if err != nil {
65 | return xerrors.Errorf("Failed to fetch files. err: %w", err)
66 | }
67 |
68 | for v, rs := range results {
69 | m := map[string]redhat.Root{}
70 | for _, r := range rs {
71 | ovalroot := redhat.Root{}
72 | if err = xml.Unmarshal(r.Body, &ovalroot); err != nil {
73 | return xerrors.Errorf("Failed to unmarshal xml. url: %s, err: %w", r.URL, err)
74 | }
75 |
76 | log15.Info("Fetched", "File", r.URL[strings.LastIndex(r.URL, "/")+1:], "Count", len(ovalroot.Definitions.Definitions), "Timestamp", ovalroot.Generator.Timestamp)
77 | ts, err := time.Parse("2006-01-02T15:04:05", ovalroot.Generator.Timestamp)
78 | if err != nil {
79 | return xerrors.Errorf("Failed to parse timestamp. url: %s, timestamp: %s, err: %w", r.URL, ovalroot.Generator.Timestamp, err)
80 | }
81 | if ts.Before(time.Now().AddDate(0, 0, -3)) {
82 | log15.Warn("The fetched OVAL has not been updated for 3 days, the OVAL URL may have changed, please register a GitHub issue.", "GitHub", "https://github.com/vulsio/goval-dictionary/issues", "OVAL", r.URL, "Timestamp", ovalroot.Generator.Timestamp)
83 | }
84 |
85 | m[r.URL[strings.LastIndex(r.URL, "/")+1:]] = ovalroot
86 | }
87 |
88 | roots := make([]redhat.Root, 0, len(m))
89 | for _, k := range []string{fmt.Sprintf("rhel-%s-including-unpatched.oval.xml.bz2", v), fmt.Sprintf("rhel-%s-extras-including-unpatched.oval.xml.bz2", v), fmt.Sprintf("rhel-%s-supplementary.oval.xml.bz2", v), fmt.Sprintf("rhel-%s-els.oval.xml.bz2", v), fmt.Sprintf("com.redhat.rhsa-RHEL%s.xml", v), fmt.Sprintf("com.redhat.rhsa-RHEL%s-ELS.xml", v)} {
90 | roots = append(roots, m[k])
91 | }
92 |
93 | root := models.Root{
94 | Family: c.RedHat,
95 | OSVersion: v,
96 | Definitions: redhat.ConvertToModel(v, roots),
97 | Timestamp: time.Now(),
98 | }
99 |
100 | if err := driver.InsertOval(&root); err != nil {
101 | return xerrors.Errorf("Failed to insert OVAL. err: %w", err)
102 | }
103 | log15.Info("Finish", "Updated", len(root.Definitions))
104 | }
105 |
106 | fetchMeta.LastFetchedAt = time.Now()
107 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
108 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. err: %w", err)
109 | }
110 |
111 | return nil
112 | }
113 |
--------------------------------------------------------------------------------
/server/server.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "net/url"
7 | "os"
8 | "path/filepath"
9 | "strings"
10 |
11 | "github.com/inconshreveable/log15"
12 | "github.com/labstack/echo/v4"
13 | "github.com/labstack/echo/v4/middleware"
14 | "github.com/spf13/viper"
15 | "golang.org/x/xerrors"
16 |
17 | "github.com/vulsio/goval-dictionary/db"
18 | )
19 |
20 | // Start starts CVE dictionary HTTP Server.
21 | func Start(logToFile bool, logDir string, driver db.DB) error {
22 | e := echo.New()
23 | e.Debug = viper.GetBool("debug")
24 |
25 | // Middleware
26 | e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{Output: os.Stderr}))
27 | e.Use(middleware.Recover())
28 |
29 | // setup access logger
30 | if logToFile {
31 | logPath := filepath.Join(logDir, "access.log")
32 | f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
33 | if err != nil {
34 | return xerrors.Errorf("Failed to open a log file. err: %w", err)
35 | }
36 | defer f.Close()
37 | e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{Output: f}))
38 | }
39 |
40 | // Routes
41 | e.GET("/health", health())
42 | e.GET("/packs/:family/:release/:pack/:arch", getByPackName(driver))
43 | e.GET("/packs/:family/:release/:pack", getByPackName(driver))
44 | e.GET("/cves/:family/:release/:id/:arch", getByCveID(driver))
45 | e.GET("/cves/:family/:release/:id", getByCveID(driver))
46 | e.GET("/advisories/:family/:release", getAdvisories(driver))
47 | e.GET("/count/:family/:release", countOvalDefs(driver))
48 | e.GET("/lastmodified/:family/:release", getLastModified(driver))
49 | // e.Post("/cpes", getByPackName(driver))
50 |
51 | bindURL := fmt.Sprintf("%s:%s", viper.GetString("bind"), viper.GetString("port"))
52 | log15.Info("Listening...", "URL", bindURL)
53 | return e.Start(bindURL)
54 | }
55 |
56 | // Handler
57 | func health() echo.HandlerFunc {
58 | return func(c echo.Context) error {
59 | return c.String(http.StatusOK, "")
60 | }
61 | }
62 |
63 | func getByPackName(driver db.DB) echo.HandlerFunc {
64 | return func(c echo.Context) (err error) {
65 | family := strings.ToLower(c.Param("family"))
66 | release := c.Param("release")
67 | pack := c.Param("pack")
68 | arch := c.Param("arch")
69 | decodePack, err := url.QueryUnescape(pack)
70 | if err != nil {
71 | log15.Error(fmt.Sprintf("Failed to Decode Package Name: %s", err))
72 | return c.JSON(http.StatusBadRequest, nil)
73 | }
74 |
75 | log15.Debug("Params", "Family", family, "Release", release, "Pack", pack, "DecodePack", decodePack, "arch", arch)
76 |
77 | defs, err := driver.GetByPackName(family, release, decodePack, arch)
78 | if err != nil {
79 | log15.Error("Failed to get by Package Name.", "err", err)
80 | }
81 | return c.JSON(http.StatusOK, defs)
82 | }
83 | }
84 |
85 | func getByCveID(driver db.DB) echo.HandlerFunc {
86 | return func(c echo.Context) (err error) {
87 | family := strings.ToLower(c.Param("family"))
88 | release := c.Param("release")
89 | cveID := c.Param("id")
90 | arch := c.Param("arch")
91 | log15.Debug("Params", "Family", family, "Release", release, "CveID", cveID, "arch", arch)
92 |
93 | defs, err := driver.GetByCveID(family, release, cveID, arch)
94 | if err != nil {
95 | log15.Error("Failed to get by CveID.", "err", err)
96 | }
97 | return c.JSON(http.StatusOK, defs)
98 | }
99 | }
100 |
101 | func getAdvisories(driver db.DB) echo.HandlerFunc {
102 | return func(c echo.Context) (err error) {
103 | family := strings.ToLower(c.Param("family"))
104 | release := c.Param("release")
105 | log15.Debug("Params", "Family", family, "Release", release)
106 |
107 | m, err := driver.GetAdvisories(family, release)
108 | if err != nil {
109 | log15.Error("Failed to get advisories.", "err", err)
110 | }
111 | return c.JSON(http.StatusOK, m)
112 | }
113 | }
114 |
115 | func countOvalDefs(driver db.DB) echo.HandlerFunc {
116 | return func(c echo.Context) (err error) {
117 | family := strings.ToLower(c.Param("family"))
118 | release := c.Param("release")
119 | log15.Debug("Params", "Family", family, "Release", release)
120 |
121 | count, err := driver.CountDefs(family, release)
122 | if err != nil {
123 | log15.Error("Failed to count OVAL defs.", "err", err)
124 | }
125 | return c.JSON(http.StatusOK, count)
126 | }
127 | }
128 |
129 | func getLastModified(driver db.DB) echo.HandlerFunc {
130 | return func(c echo.Context) (err error) {
131 | family := strings.ToLower(c.Param("family"))
132 | release := c.Param("release")
133 | log15.Debug("Params", "Family", family, "Release", release)
134 |
135 | t, err := driver.GetLastModified(family, release)
136 | if err != nil {
137 | log15.Error(fmt.Sprintf("Failed to GetLastModified: %s", err))
138 | return c.JSON(http.StatusInternalServerError, nil)
139 | }
140 |
141 | return c.JSON(http.StatusOK, t)
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/models/debian/debian_test.go:
--------------------------------------------------------------------------------
1 | package debian
2 |
3 | import (
4 | "encoding/xml"
5 | "reflect"
6 | "testing"
7 |
8 | "github.com/k0kubun/pp"
9 |
10 | "github.com/vulsio/goval-dictionary/models"
11 | )
12 |
13 | func TestWalkDebian(t *testing.T) {
14 | var tests = []struct {
15 | oval string
16 | expected []models.Package
17 | }{
18 | {
19 | oval: `
20 |
21 |
22 |
23 | Debian
24 | 5.3
25 | 2017-04-07T03:47:55.188-04:00
26 |
27 |
28 |
29 |
30 | CVE-2014-0001
31 |
32 | Debian GNU/Linux 7.0
33 | Debian GNU/Linux 8.2
34 | Debian GNU/Linux 9.0
35 | mysql-5.5
36 |
37 |
38 | Buffer overflow in client/mysql.cc in Oracle MySQL and MariaDB before 5.5.35 allows remote database servers to cause a denial of service (crash) and possibly execute arbitrary code via a long server version string.
39 |
40 | 2014-05-03
41 |
42 | DSA-2919
43 | Several issues have been discovered in the MySQL database server. The
44 | vulnerabilities are addressed by upgrading MySQL to the new upstream
45 | version 5.5.37. Please see the MySQL 5.5 Release Notes and Oracle's
46 | Critical Patch Update advisory for further details:
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | `,
89 | expected: []models.Package{
90 | {
91 | Name: "mysql-5.5",
92 | Version: "5.5.37-0+wheezy1",
93 | },
94 | {
95 | Name: "mysql-5.5",
96 | Version: "5.5.37-1",
97 | },
98 | {
99 | Name: "mysql-5.5",
100 | Version: "5.5.37-1",
101 | },
102 | {
103 | Name: "mysql-5.6",
104 | Version: "5.6.37-1",
105 | },
106 | },
107 | },
108 | }
109 |
110 | for i, tt := range tests {
111 | var root *Root
112 | if err := xml.Unmarshal([]byte(tt.oval), &root); err != nil {
113 | t.Errorf("[%d] marshall error", i)
114 | }
115 | c := root.Definitions.Definitions[0].Criteria
116 | actual := collectDebianPacks(c)
117 |
118 | if !reflect.DeepEqual(tt.expected, actual) {
119 | e := pp.Sprintf("%v", tt.expected)
120 | a := pp.Sprintf("%v", actual)
121 | t.Errorf("[%d]: expected: %s\n, actual: %s\n", i, e, a)
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/models/oracle/types.go:
--------------------------------------------------------------------------------
1 | package oracle
2 |
3 | import "encoding/xml"
4 |
5 | // Root : root object
6 | type Root struct {
7 | XMLName xml.Name `xml:"oval_definitions"`
8 | Generator Generator `xml:"generator"`
9 | Definitions Definitions `xml:"definitions"`
10 | Tests Tests `xml:"tests"`
11 | Objects Objects `xml:"objects"`
12 | States States `xml:"states"`
13 | }
14 |
15 | // Generator : >generator
16 | type Generator struct {
17 | XMLName xml.Name `xml:"generator"`
18 | ProductName string `xml:"product_name"`
19 | ProductVersion string `xml:"product_version"`
20 | SchemaVersion string `xml:"schema_version"`
21 | Timestamp string `xml:"timestamp"`
22 | }
23 |
24 | // Definitions : >definitions
25 | type Definitions struct {
26 | XMLName xml.Name `xml:"definitions"`
27 | Definitions []Definition `xml:"definition"`
28 | }
29 |
30 | // Definition : >definitions>definition
31 | type Definition struct {
32 | XMLName xml.Name `xml:"definition"`
33 | ID string `xml:"id,attr"`
34 | Class string `xml:"class,attr"`
35 | Title string `xml:"metadata>title"`
36 | Affecteds []Affected `xml:"metadata>affected"`
37 | References []Reference `xml:"metadata>reference"`
38 | Description string `xml:"metadata>description"`
39 | Advisory Advisory `xml:"metadata>advisory"`
40 | Criteria Criteria `xml:"criteria"`
41 | }
42 |
43 | // Criteria : >definitions>definition>criteria
44 | type Criteria struct {
45 | XMLName xml.Name `xml:"criteria"`
46 | Operator string `xml:"operator,attr"`
47 | Criterias []Criteria `xml:"criteria"`
48 | Criterions []Criterion `xml:"criterion"`
49 | }
50 |
51 | // Criterion : >definitions>definition>criteria>*>criterion
52 | type Criterion struct {
53 | XMLName xml.Name `xml:"criterion"`
54 | TestRef string `xml:"test_ref,attr"`
55 | Comment string `xml:"comment,attr"`
56 | }
57 |
58 | // Affected : >definitions>definition>metadata>affected
59 | type Affected struct {
60 | XMLName xml.Name `xml:"affected"`
61 | Family string `xml:"family,attr"`
62 | Platforms []string `xml:"platform"`
63 | }
64 |
65 | // Reference : >definitions>definition>metadata>reference
66 | type Reference struct {
67 | XMLName xml.Name `xml:"reference"`
68 | Source string `xml:"source,attr"`
69 | RefID string `xml:"ref_id,attr"`
70 | RefURL string `xml:"ref_url,attr"`
71 | }
72 |
73 | // Advisory : >definitions>definition>metadata>advisory
74 | // RedHat and Ubuntu OVAL
75 | type Advisory struct {
76 | XMLName xml.Name `xml:"advisory"`
77 | Severity string `xml:"severity"`
78 | Rights string `xml:"rights"`
79 | Cves []Cve `xml:"cve"`
80 | Issued struct {
81 | Date string `xml:"date,attr"`
82 | } `xml:"issued"`
83 | }
84 |
85 | // Cve : >definitions>definition>metadata>advisory>cve
86 | // RedHat OVAL
87 | type Cve struct {
88 | XMLName xml.Name `xml:"cve"`
89 | CveID string `xml:",chardata"`
90 | Href string `xml:"href,attr"`
91 | }
92 |
93 | // Tests : >tests
94 | type Tests struct {
95 | XMLName xml.Name `xml:"tests"`
96 | RpminfoTest RpminfoTest `xml:"rpminfo_test"`
97 | }
98 |
99 | // RpminfoTest : >tests>rpminfo_test
100 | type RpminfoTest []struct {
101 | ID string `xml:"id,attr"`
102 | Comment string `xml:"comment,attr"`
103 | Check string `xml:"check,attr"`
104 | Object ObjectRef `xml:"object"`
105 | State StateRef `xml:"state"`
106 | }
107 |
108 | // ObjectRef : >tests>rpminfo_test>object-object_ref
109 | type ObjectRef struct {
110 | XMLName xml.Name `xml:"object"`
111 | Text string `xml:",chardata"`
112 | ObjectRef string `xml:"object_ref,attr"`
113 | }
114 |
115 | // StateRef : >tests>rpminfo_test>state-state_ref
116 | type StateRef struct {
117 | XMLName xml.Name `xml:"state"`
118 | Text string `xml:",chardata"`
119 | StateRef string `xml:"state_ref,attr"`
120 | }
121 |
122 | // Objects : >objects
123 | type Objects struct {
124 | XMLName xml.Name `xml:"objects"`
125 | RpminfoObject []RpminfoObject `xml:"rpminfo_object"`
126 | }
127 |
128 | // RpminfoObject : >objects>rpminfo_object
129 | type RpminfoObject struct {
130 | ID string `xml:"id,attr"`
131 | Name string `xml:"name"`
132 | }
133 |
134 | // States : >states
135 | type States struct {
136 | XMLName xml.Name `xml:"states"`
137 | RpminfoState []RpminfoState `xml:"rpminfo_state"`
138 | }
139 |
140 | // RpminfoState : >states>rpminfo_state
141 | type RpminfoState struct {
142 | ID string `xml:"id,attr"`
143 | SignatureKeyid struct {
144 | Text string `xml:",chardata"`
145 | Operation string `xml:"operation,attr"`
146 | } `xml:"signature_keyid"`
147 | Version struct {
148 | Text string `xml:",chardata"`
149 | Operation string `xml:"operation,attr"`
150 | } `xml:"version"`
151 | Arch struct {
152 | Text string `xml:",chardata"`
153 | Operation string `xml:"operation,attr"`
154 | } `xml:"arch"`
155 | Evr struct {
156 | Text string `xml:",chardata"`
157 | Datatype string `xml:"datatype,attr"`
158 | Operation string `xml:"operation,attr"`
159 | } `xml:"evr"`
160 | }
161 |
--------------------------------------------------------------------------------
/fetcher/redhat/redhat.go:
--------------------------------------------------------------------------------
1 | package redhat
2 |
3 | import (
4 | "archive/tar"
5 | "bytes"
6 | "fmt"
7 | "io"
8 | "slices"
9 | "strconv"
10 | "strings"
11 |
12 | "github.com/inconshreveable/log15"
13 | "golang.org/x/xerrors"
14 |
15 | "github.com/vulsio/goval-dictionary/fetcher/util"
16 | )
17 |
18 | // FetchFiles fetch OVAL from RedHat
19 | func FetchFiles(versions []string) (map[string][]util.FetchResult, error) {
20 | results := map[string][]util.FetchResult{}
21 | for _, v := range versions {
22 | switch v {
23 | case "1", "2", "3":
24 | log15.Warn("Skip redhat because no vulnerability information provided.", "version", v)
25 | case "4":
26 | rs, err := fetchOVALv1([]string{fmt.Sprintf("com.redhat.rhsa-RHEL%s.xml", v)})
27 | if err != nil {
28 | return nil, xerrors.Errorf("Failed to fetch OVALv1. err: %w", err)
29 | }
30 | results[v] = rs
31 | case "5":
32 | rs, err := fetchOVALv1([]string{fmt.Sprintf("com.redhat.rhsa-RHEL%s.xml", v), fmt.Sprintf("com.redhat.rhsa-RHEL%s-ELS.xml", v)})
33 | if err != nil {
34 | return nil, xerrors.Errorf("Failed to fetch OVALv1. err: %w", err)
35 | }
36 | results[v] = rs
37 | case "6":
38 | rs, err := fetchOVALv1([]string{fmt.Sprintf("com.redhat.rhsa-RHEL%s.xml", v)})
39 | if err != nil {
40 | return nil, xerrors.Errorf("Failed to fetch OVALv1. err: %w", err)
41 | }
42 | results[v] = rs
43 |
44 | rs, err = fetchOVALv2([]string{fmt.Sprintf("%s-including-unpatched", v), fmt.Sprintf("%s-extras-including-unpatched", v), fmt.Sprintf("%s-supplementary", v), fmt.Sprintf("%s-els", v)})
45 | if err != nil {
46 | return nil, xerrors.Errorf("Failed to fetch OVALv2. err: %w", err)
47 | }
48 | results[v] = append(results[v], rs...)
49 | case "7":
50 | rs, err := fetchOVALv1([]string{fmt.Sprintf("com.redhat.rhsa-RHEL%s.xml", v)})
51 | if err != nil {
52 | return nil, xerrors.Errorf("Failed to fetch OVALv1. err: %w", err)
53 | }
54 | results[v] = rs
55 |
56 | rs, err = fetchOVALv2([]string{fmt.Sprintf("%s-including-unpatched", v), fmt.Sprintf("%s-extras-including-unpatched", v), fmt.Sprintf("%s-supplementary", v)})
57 | if err != nil {
58 | return nil, xerrors.Errorf("Failed to fetch OVALv2. err: %w", err)
59 | }
60 | results[v] = append(results[v], rs...)
61 | case "8", "9":
62 | rs, err := fetchOVALv1([]string{fmt.Sprintf("com.redhat.rhsa-RHEL%s.xml", v)})
63 | if err != nil {
64 | return nil, xerrors.Errorf("Failed to fetch OVALv1. err: %w", err)
65 | }
66 | results[v] = rs
67 |
68 | rs, err = fetchOVALv2([]string{fmt.Sprintf("%s-including-unpatched", v)})
69 | if err != nil {
70 | return nil, xerrors.Errorf("Failed to fetch OVALv2. err: %w", err)
71 | }
72 | results[v] = append(results[v], rs...)
73 | default:
74 | if _, err := strconv.Atoi(v); err != nil {
75 | log15.Warn("Skip unknown redhat.", "version", v)
76 | break
77 | }
78 |
79 | rs, err := fetchOVALv2([]string{fmt.Sprintf("%s-including-unpatched", v)})
80 | if err != nil {
81 | return nil, xerrors.Errorf("Failed to fetch OVALv2. err: %w", err)
82 | }
83 | results[v] = rs
84 | }
85 | }
86 |
87 | if len(results) == 0 {
88 | return nil, xerrors.New("There are no versions to fetch")
89 | }
90 | return results, nil
91 | }
92 |
93 | func fetchOVALv1(names []string) ([]util.FetchResult, error) {
94 | rs, err := util.FetchFeedFiles([]util.FetchRequest{{
95 | Target: "oval_v1_20230706.tar.gz",
96 | URL: "https://access.redhat.com/security/data/archive/oval_v1_20230706.tar.gz",
97 | MIMEType: util.MIMETypeGzip,
98 | }})
99 | if err != nil {
100 | return nil, xerrors.Errorf("Failed to fetch. err: %w", err)
101 | }
102 |
103 | results := make([]util.FetchResult, 0, len(names))
104 |
105 | tr := tar.NewReader(bytes.NewReader(rs[0].Body))
106 | for {
107 | hdr, err := tr.Next()
108 | if err == io.EOF {
109 | break
110 | }
111 | if err != nil {
112 | return nil, xerrors.Errorf("Failed to next tar reader. err: %w", err)
113 | }
114 |
115 | if !slices.Contains(names, hdr.Name) {
116 | continue
117 | }
118 |
119 | bs, err := io.ReadAll(tr)
120 | if err != nil {
121 | return nil, xerrors.Errorf("Failed to read all %s. err: %w", hdr.Name, err)
122 | }
123 | results = append(results, util.FetchResult{
124 | Target: strings.TrimSuffix(strings.TrimPrefix(hdr.Name, "com.redhat.rhsa-RHEL"), ".xml"),
125 | URL: fmt.Sprintf("https://access.redhat.com/security/data/archive/oval_v1_20230706.tar.gz/%s", hdr.Name),
126 | Body: bs,
127 | })
128 | }
129 |
130 | return results, nil
131 | }
132 |
133 | func fetchOVALv2(names []string) ([]util.FetchResult, error) {
134 | reqs := make([]util.FetchRequest, 0, len(names))
135 | for _, n := range names {
136 | reqs = append(reqs, util.FetchRequest{
137 | Target: n,
138 | URL: fmt.Sprintf("https://access.redhat.com/security/data/oval/v2/RHEL%s/rhel-%s.oval.xml.bz2", strings.Split(n, "-")[0], n),
139 | MIMEType: util.MIMETypeBzip2,
140 | })
141 | }
142 |
143 | rs, err := util.FetchFeedFiles(reqs)
144 | if err != nil {
145 | return nil, xerrors.Errorf("Failed to fetch. err: %w", err)
146 | }
147 | return rs, nil
148 | }
149 |
--------------------------------------------------------------------------------
/commands/fetch-suse.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "encoding/xml"
5 | "errors"
6 | "strings"
7 | "time"
8 |
9 | "github.com/inconshreveable/log15"
10 | "github.com/spf13/cobra"
11 | "github.com/spf13/viper"
12 | "golang.org/x/xerrors"
13 |
14 | c "github.com/vulsio/goval-dictionary/config"
15 | "github.com/vulsio/goval-dictionary/db"
16 | fetcher "github.com/vulsio/goval-dictionary/fetcher/suse"
17 | "github.com/vulsio/goval-dictionary/log"
18 | "github.com/vulsio/goval-dictionary/models"
19 | "github.com/vulsio/goval-dictionary/models/suse"
20 | "github.com/vulsio/goval-dictionary/util"
21 | )
22 |
23 | // fetchSUSECmd is Subcommand for fetch SUSE OVAL
24 | var fetchSUSECmd = &cobra.Command{
25 | Use: "suse [version]",
26 | Short: "Fetch Vulnerability dictionary from SUSE",
27 | Long: `Fetch Vulnerability dictionary from SUSE`,
28 | RunE: fetchSUSE,
29 | Example: `$ goval-dictionary fetch suse --suse-type opensuse 13.2 tumbleweed
30 | $ goval-dictionary fetch suse --suse-type opensuse-leap 15.2 15.3
31 | $ goval-dictionary fetch suse --suse-type suse-enterprise-server 12 15
32 | $ goval-dictionary fetch suse --suse-type suse-enterprise-desktop 12 15`,
33 | }
34 |
35 | func init() {
36 | fetchCmd.AddCommand(fetchSUSECmd)
37 |
38 | fetchSUSECmd.PersistentFlags().String("suse-type", "opensuse-leap", "Fetch SUSE Type(choices: opensuse, opensuse-leap, suse-enterprise-server, suse-enterprise-desktop)")
39 | _ = viper.BindPFlag("suse-type", fetchSUSECmd.PersistentFlags().Lookup("suse-type"))
40 | }
41 |
42 | func fetchSUSE(_ *cobra.Command, args []string) (err error) {
43 | if err := log.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil {
44 | return xerrors.Errorf("Failed to SetLogger. err: %w", err)
45 | }
46 |
47 | var suseType string
48 | switch viper.GetString("suse-type") {
49 | case "opensuse":
50 | suseType = c.OpenSUSE
51 | case "opensuse-leap":
52 | suseType = c.OpenSUSELeap
53 | case "suse-enterprise-server":
54 | suseType = c.SUSEEnterpriseServer
55 | case "suse-enterprise-desktop":
56 | suseType = c.SUSEEnterpriseDesktop
57 | default:
58 | return xerrors.Errorf("Specify SUSE type to fetch. Available SUSE Type: opensuse, opensuse-leap, suse-enterprise-server, suse-enterprise-desktop")
59 | }
60 |
61 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
62 | if err != nil {
63 | if errors.Is(err, db.ErrDBLocked) {
64 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
65 | }
66 | return xerrors.Errorf("Failed to open DB. err: %w", err)
67 | }
68 |
69 | fetchMeta, err := driver.GetFetchMeta()
70 | if err != nil {
71 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
72 | }
73 | if fetchMeta.OutDated() {
74 | return xerrors.Errorf("Failed to Insert CVEs into DB. SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
75 | }
76 | // If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion.
77 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
78 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. err: %w", err)
79 | }
80 |
81 | results, err := fetcher.FetchFiles(suseType, util.Unique(args))
82 | if err != nil {
83 | return xerrors.Errorf("Failed to fetch files. err: %w", err)
84 | }
85 |
86 | for _, r := range results {
87 | ovalroot := suse.Root{}
88 | if err = xml.Unmarshal(r.Body, &ovalroot); err != nil {
89 | return xerrors.Errorf("Failed to unmarshal xml. url: %s, err: %w", r.URL, err)
90 | }
91 | filename := r.URL[strings.LastIndex(r.URL, "/")+1:]
92 | log15.Info("Fetched", "File", filename, "Count", len(ovalroot.Definitions.Definitions), "Timestamp", ovalroot.Generator.Timestamp)
93 | ts, err := time.Parse("2006-01-02T15:04:05", ovalroot.Generator.Timestamp)
94 | if err != nil {
95 | return xerrors.Errorf("Failed to parse timestamp. url: %s, timestamp: %s, err: %w", r.URL, ovalroot.Generator.Timestamp, err)
96 | }
97 | if ts.Before(time.Now().AddDate(0, 0, -3)) {
98 | log15.Warn("The fetched OVAL has not been updated for 3 days, the OVAL URL may have changed, please register a GitHub issue.", "GitHub", "https://github.com/vulsio/goval-dictionary/issues", "OVAL", r.URL, "Timestamp", ovalroot.Generator.Timestamp)
99 | }
100 |
101 | osVerDefs, err := suse.ConvertToModel(filename, &ovalroot)
102 | if err != nil {
103 | return xerrors.Errorf("Failed to convert from OVAL to goval-dictionary model. err: %w", err)
104 | }
105 | for osVer, defs := range osVerDefs {
106 | root := models.Root{
107 | Family: suseType,
108 | OSVersion: osVer,
109 | Definitions: defs,
110 | Timestamp: time.Now(),
111 | }
112 | if err := driver.InsertOval(&root); err != nil {
113 | return xerrors.Errorf("Failed to insert OVAL. err: %w", err)
114 | }
115 | log15.Info("Finish", "Updated", len(root.Definitions))
116 | }
117 | }
118 |
119 | fetchMeta.LastFetchedAt = time.Now()
120 | if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
121 | return xerrors.Errorf("Failed to upsert FetchMeta to DB. err: %w", err)
122 | }
123 |
124 | return nil
125 | }
126 |
--------------------------------------------------------------------------------
/models/models.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "time"
5 |
6 | "gorm.io/gorm"
7 | )
8 |
9 | // LatestSchemaVersion manages the Schema version used in the latest goval-dictionary.
10 | const LatestSchemaVersion = 2
11 |
12 | // FetchMeta has DB information
13 | type FetchMeta struct {
14 | gorm.Model `json:"-"`
15 | GovalDictRevision string
16 | SchemaVersion uint
17 | LastFetchedAt time.Time
18 | }
19 |
20 | // OutDated checks whether last fetched feed is out dated
21 | func (f FetchMeta) OutDated() bool {
22 | return f.SchemaVersion != LatestSchemaVersion
23 | }
24 |
25 | // Root is root struct
26 | type Root struct {
27 | ID uint `gorm:"primary_key"`
28 | Family string `gorm:"type:varchar(255)"`
29 | OSVersion string `gorm:"type:varchar(255)"`
30 | Definitions []Definition
31 | Timestamp time.Time
32 | }
33 |
34 | // Definition : >definitions>definition
35 | type Definition struct {
36 | ID uint `gorm:"primary_key" json:"-"`
37 | RootID uint `gorm:"index:idx_definition_root_id" json:"-" xml:"-"`
38 |
39 | DefinitionID string `gorm:"type:varchar(255)"`
40 | Title string `gorm:"type:text"`
41 | Description string // If the type:text, varchar(255) is specified, MySQL overflows and gives an error. No problem in GORMv2. (https://github.com/go-gorm/mysql/tree/15e2cbc6fd072be99215a82292e025dab25e2e16#configuration)
42 | Advisory Advisory
43 | Debian *Debian
44 | AffectedPacks []Package
45 | References []Reference
46 | }
47 |
48 | // Package affected
49 | type Package struct {
50 | ID uint `gorm:"primary_key" json:"-"`
51 | DefinitionID uint `gorm:"index:idx_packages_definition_id" json:"-" xml:"-"`
52 |
53 | Name string `gorm:"index:idx_packages_name"` // If the type:text, varchar(255) is specified, MySQL overflows and gives an error. No problem in GORMv2. (https://github.com/go-gorm/mysql/tree/15e2cbc6fd072be99215a82292e025dab25e2e16#configuration)
54 | Version string `gorm:"type:varchar(255)"` // affected earlier than this version
55 | Arch string `gorm:"type:varchar(255)"` // Used for Amazon Linux, Oracle Linux and Fedora
56 | NotFixedYet bool // Used for RedHat, Ubuntu
57 | ModularityLabel string `gorm:"type:varchar(255)"` // RHEL 8 or later only
58 | }
59 |
60 | // Reference : >definitions>definition>metadata>reference
61 | type Reference struct {
62 | ID uint `gorm:"primary_key" json:"-"`
63 | DefinitionID uint `gorm:"index:idx_reference_definition_id" json:"-" xml:"-"`
64 |
65 | Source string `gorm:"type:varchar(255)"`
66 | RefID string `gorm:"type:varchar(255)"`
67 | RefURL string `gorm:"type:text"`
68 | }
69 |
70 | // Advisory : >definitions>definition>metadata>advisory
71 | type Advisory struct {
72 | ID uint `gorm:"primary_key" json:"-"`
73 | DefinitionID uint `gorm:"index:idx_advisories_definition_id" json:"-" xml:"-"`
74 |
75 | Severity string `gorm:"type:varchar(255)"`
76 | Cves []Cve
77 | Bugzillas []Bugzilla
78 | AffectedResolution []Resolution
79 | AffectedCPEList []Cpe
80 | AffectedRepository string `gorm:"type:varchar(255)"` // Amazon Linux 2 Only
81 | Issued time.Time
82 | Updated time.Time
83 | }
84 |
85 | // Cve : >definitions>definition>metadata>advisory>cve
86 | type Cve struct {
87 | ID uint `gorm:"primary_key" json:"-"`
88 | AdvisoryID uint `gorm:"index:idx_cves_advisory_id" json:"-" xml:"-"`
89 |
90 | CveID string `gorm:"type:varchar(255)"`
91 | Cvss2 string `gorm:"type:varchar(255)"`
92 | Cvss3 string `gorm:"type:varchar(255)"`
93 | Cwe string `gorm:"type:varchar(255)"`
94 | Impact string `gorm:"type:varchar(255)"`
95 | Href string `gorm:"type:varchar(255)"`
96 | Public string `gorm:"type:varchar(255)"`
97 | }
98 |
99 | // Bugzilla : >definitions>definition>metadata>advisory>bugzilla
100 | type Bugzilla struct {
101 | ID uint `gorm:"primary_key" json:"-"`
102 | AdvisoryID uint `gorm:"index:idx_bugzillas_advisory_id" json:"-" xml:"-"`
103 |
104 | BugzillaID string `gorm:"type:varchar(255)"`
105 | URL string `gorm:"type:varchar(255)"`
106 | Title string `gorm:"type:varchar(255)"`
107 | }
108 |
109 | // Resolution : >definitions>definition>metadata>advisory>affected>resolution
110 | type Resolution struct {
111 | ID uint `gorm:"primary_key" json:"-"`
112 | AdvisoryID uint `gorm:"index:idx_resolution_advisory_id" json:"-" xml:"-"`
113 |
114 | State string `gorm:"type:varchar(255)"`
115 | Components []Component
116 | }
117 |
118 | // Component : >definitions>definition>metadata>advisory>affected>resolution>component
119 | type Component struct {
120 | ID uint `gorm:"primary_key" json:"-"`
121 | ResolutionID uint `gorm:"index:idx_component_resolution_id" json:"-" xml:"-"`
122 |
123 | Component string `gorm:"type:varchar(255)"`
124 | }
125 |
126 | // Cpe : >definitions>definition>metadata>advisory>affected_cpe_list
127 | type Cpe struct {
128 | ID uint `gorm:"primary_key" json:"-"`
129 | AdvisoryID uint `gorm:"index:idx_cpes_advisory_id" json:"-" xml:"-"`
130 |
131 | Cpe string `gorm:"type:varchar(255)"`
132 | }
133 |
134 | // Debian : >definitions>definition>metadata>debian
135 | type Debian struct {
136 | ID uint `gorm:"primary_key" json:"-"`
137 | DefinitionID uint `gorm:"index:idx_debian_definition_id" json:"-" xml:"-"`
138 |
139 | DSA string `gorm:"type:varchar(255)"`
140 | MoreInfo string `gorm:"type:text"`
141 |
142 | Date time.Time
143 | }
144 |
--------------------------------------------------------------------------------
/models/suse/types.go:
--------------------------------------------------------------------------------
1 | package suse
2 |
3 | import "encoding/xml"
4 |
5 | // Root : root object
6 | type Root struct {
7 | XMLName xml.Name `xml:"oval_definitions"`
8 | Generator Generator `xml:"generator"`
9 | Definitions Definitions `xml:"definitions"`
10 | Tests Tests `xml:"tests"`
11 | Objects Objects `xml:"objects"`
12 | States States `xml:"states"`
13 | }
14 |
15 | // Generator : >generator
16 | type Generator struct {
17 | XMLName xml.Name `xml:"generator"`
18 | ProductName string `xml:"product_name"`
19 | SchemaVersion string `xml:"schema_version"`
20 | Timestamp string `xml:"timestamp"`
21 | }
22 |
23 | // Definitions : >definitions
24 | type Definitions struct {
25 | XMLName xml.Name `xml:"definitions"`
26 | Definitions []Definition `xml:"definition"`
27 | }
28 |
29 | // Definition : >definitions>definition
30 | type Definition struct {
31 | XMLName xml.Name `xml:"definition"`
32 | ID string `xml:"id,attr"`
33 | Class string `xml:"class,attr"`
34 | Title string `xml:"metadata>title"`
35 | Affecteds []Affected `xml:"metadata>affected"`
36 | References []Reference `xml:"metadata>reference"`
37 | Description string `xml:"metadata>description"`
38 | Advisory Advisory `xml:"metadata>advisory"`
39 | Criteria Criteria `xml:"criteria"`
40 | }
41 |
42 | // Criteria : >definitions>definition>criteria
43 | type Criteria struct {
44 | XMLName xml.Name `xml:"criteria"`
45 | Operator string `xml:"operator,attr"`
46 | Criterias []Criteria `xml:"criteria"`
47 | Criterions []Criterion `xml:"criterion"`
48 | }
49 |
50 | // Criterion : >definitions>definition>criteria>*>criterion
51 | type Criterion struct {
52 | XMLName xml.Name `xml:"criterion"`
53 | TestRef string `xml:"test_ref,attr"`
54 | Comment string `xml:"comment,attr"`
55 | }
56 |
57 | // Affected : >definitions>definition>metadata>affected
58 | type Affected struct {
59 | XMLName xml.Name `xml:"affected"`
60 | Family string `xml:"family,attr"`
61 | Platforms []string `xml:"platform"`
62 | }
63 |
64 | // Reference : >definitions>definition>metadata>reference
65 | type Reference struct {
66 | XMLName xml.Name `xml:"reference"`
67 | Source string `xml:"source,attr"`
68 | RefID string `xml:"ref_id,attr"`
69 | RefURL string `xml:"ref_url,attr"`
70 | }
71 |
72 | // Advisory : >definitions>definition>metadata>advisory
73 | // RedHat and Ubuntu OVAL
74 | type Advisory struct {
75 | XMLName xml.Name `xml:"advisory"`
76 | Severity string `xml:"severity"`
77 | Cves []Cve `xml:"cve"`
78 | Bugzillas []Bugzilla `xml:"bugzilla"`
79 | AffectedCPEList []string `xml:"affected_cpe_list>cpe"`
80 | Issued struct {
81 | Date string `xml:"date,attr"`
82 | } `xml:"issued"`
83 | Updated struct {
84 | Date string `xml:"date,attr"`
85 | } `xml:"updated"`
86 | }
87 |
88 | // Cve : >definitions>definition>metadata>advisory>cve
89 | type Cve struct {
90 | XMLName xml.Name `xml:"cve"`
91 | CveID string `xml:",chardata"`
92 | Cvss3 string `xml:"cvss3,attr"`
93 | Impact string `xml:"impact,attr"`
94 | Href string `xml:"href,attr"`
95 | }
96 |
97 | // Bugzilla : >definitions>definition>metadata>advisory>bugzilla
98 | type Bugzilla struct {
99 | XMLName xml.Name `xml:"bugzilla"`
100 | URL string `xml:"href,attr"`
101 | Title string `xml:",chardata"`
102 | }
103 |
104 | // Tests : >tests
105 | type Tests struct {
106 | XMLName xml.Name `xml:"tests"`
107 | RpminfoTest []RpminfoTest `xml:"rpminfo_test"`
108 | }
109 |
110 | // RpminfoTest : >tests>rpminfo_test
111 | type RpminfoTest struct {
112 | ID string `xml:"id,attr"`
113 | Comment string `xml:"comment,attr"`
114 | Check string `xml:"check,attr"`
115 | Object ObjectRef `xml:"object"`
116 | State StateRef `xml:"state"`
117 | }
118 |
119 | // ObjectRef : >tests>rpminfo_test>object-object_ref
120 | type ObjectRef struct {
121 | XMLName xml.Name `xml:"object"`
122 | Text string `xml:",chardata"`
123 | ObjectRef string `xml:"object_ref,attr"`
124 | }
125 |
126 | // StateRef : >tests>rpminfo_test>state-state_ref
127 | type StateRef struct {
128 | XMLName xml.Name `xml:"state"`
129 | Text string `xml:",chardata"`
130 | StateRef string `xml:"state_ref,attr"`
131 | }
132 |
133 | // Objects : >objects
134 | type Objects struct {
135 | XMLName xml.Name `xml:"objects"`
136 | RpminfoObject []RpminfoObject `xml:"rpminfo_object"`
137 | }
138 |
139 | // RpminfoObject : >objects>rpminfo_object
140 | type RpminfoObject struct {
141 | ID string `xml:"id,attr"`
142 | Name string `xml:"name"`
143 | }
144 |
145 | // States : >states
146 | type States struct {
147 | XMLName xml.Name `xml:"states"`
148 | RpminfoState []RpminfoState `xml:"rpminfo_state"`
149 | }
150 |
151 | // RpminfoState : >states>rpminfo_state
152 | type RpminfoState struct {
153 | ID string `xml:"id,attr"`
154 | SignatureKeyid SignatureKeyid `xml:"signature_keyid"`
155 | Version struct {
156 | Text string `xml:",chardata"`
157 | Operation string `xml:"operation,attr"`
158 | } `xml:"version"`
159 | Arch struct {
160 | Text string `xml:",chardata"`
161 | Datatype string `xml:"datatype,attr"`
162 | Operation string `xml:"operation,attr"`
163 | } `xml:"arch"`
164 | Evr struct {
165 | Text string `xml:",chardata"`
166 | Datatype string `xml:"datatype,attr"`
167 | Operation string `xml:"operation,attr"`
168 | } `xml:"evr"`
169 | }
170 |
171 | // SignatureKeyid : >states>rpminfo_state>signature_keyid
172 | type SignatureKeyid struct {
173 | Text string `xml:",chardata"`
174 | Operation string `xml:"operation,attr"`
175 | }
176 |
--------------------------------------------------------------------------------
/models/redhat/redhat.go:
--------------------------------------------------------------------------------
1 | package redhat
2 |
3 | import (
4 | "fmt"
5 | "maps"
6 | "slices"
7 | "strings"
8 | "time"
9 |
10 | version "github.com/knqyf263/go-rpm-version"
11 | "github.com/spf13/viper"
12 |
13 | "github.com/vulsio/goval-dictionary/models"
14 | "github.com/vulsio/goval-dictionary/models/util"
15 | )
16 |
17 | // ConvertToModel Convert OVAL to models
18 | func ConvertToModel(v string, roots []Root) []models.Definition {
19 | defs := map[string]models.Definition{}
20 | for _, root := range roots {
21 | for _, d := range root.Definitions.Definitions {
22 | if strings.HasPrefix(d.ID, "oval:com.redhat.unaffected:def:") || strings.Contains(d.Description, "** REJECT **") {
23 | continue
24 | }
25 |
26 | var cves = make([]models.Cve, 0, len(d.Advisory.Cves))
27 | for _, c := range d.Advisory.Cves {
28 | cves = append(cves, models.Cve{
29 | CveID: c.CveID,
30 | Cvss2: c.Cvss2,
31 | Cvss3: c.Cvss3,
32 | Cwe: c.Cwe,
33 | Impact: c.Impact,
34 | Href: c.Href,
35 | Public: c.Public,
36 | })
37 | }
38 |
39 | var rs = make([]models.Reference, 0, len(d.References))
40 | for _, r := range d.References {
41 | rs = append(rs, models.Reference{
42 | Source: r.Source,
43 | RefID: r.RefID,
44 | RefURL: r.RefURL,
45 | })
46 | }
47 |
48 | var cpes = make([]models.Cpe, 0, len(d.Advisory.AffectedCPEList))
49 | for _, cpe := range d.Advisory.AffectedCPEList {
50 | cpes = append(cpes, models.Cpe{
51 | Cpe: cpe,
52 | })
53 | }
54 |
55 | var bs = make([]models.Bugzilla, 0, len(d.Advisory.Bugzillas))
56 | for _, b := range d.Advisory.Bugzillas {
57 | bs = append(bs, models.Bugzilla{
58 | BugzillaID: b.ID,
59 | URL: b.URL,
60 | Title: b.Title,
61 | })
62 | }
63 |
64 | var ress = make([]models.Resolution, 0, len(d.Advisory.Affected.Resolution))
65 | for _, r := range d.Advisory.Affected.Resolution {
66 | ress = append(ress, models.Resolution{
67 | State: r.State,
68 | Components: func() []models.Component {
69 | var comps = make([]models.Component, 0, len(r.Component))
70 | for _, c := range r.Component {
71 | comps = append(comps, models.Component{
72 | Component: c,
73 | })
74 | }
75 | return comps
76 | }(),
77 | })
78 | }
79 |
80 | issued := util.ParsedOrDefaultTime([]string{"2006-01-02"}, d.Advisory.Issued.Date)
81 | updated := util.ParsedOrDefaultTime([]string{"2006-01-02"}, d.Advisory.Updated.Date)
82 |
83 | def := models.Definition{
84 | DefinitionID: d.ID,
85 | Title: d.Title,
86 | Description: d.Description,
87 | Advisory: models.Advisory{
88 | Severity: d.Advisory.Severity,
89 | Cves: cves,
90 | Bugzillas: bs,
91 | AffectedResolution: ress,
92 | AffectedCPEList: cpes,
93 | Issued: issued,
94 | Updated: updated,
95 | },
96 | AffectedPacks: collectRedHatPacks(v, d.Criteria),
97 | References: rs,
98 | }
99 |
100 | if viper.GetBool("no-details") {
101 | def.Title = ""
102 | def.Description = ""
103 | def.Advisory.Severity = ""
104 | def.Advisory.AffectedCPEList = []models.Cpe{}
105 | def.Advisory.Bugzillas = []models.Bugzilla{}
106 | def.Advisory.Issued = time.Time{}
107 | def.Advisory.Updated = time.Time{}
108 | def.References = []models.Reference{}
109 | }
110 |
111 | if _, ok := defs[def.DefinitionID]; !ok {
112 | defs[def.DefinitionID] = def
113 | }
114 | }
115 | }
116 | return slices.Collect(maps.Values(defs))
117 | }
118 |
119 | func collectRedHatPacks(v string, cri Criteria) []models.Package {
120 | pkgs := map[string]models.Package{}
121 | for _, p := range walkRedHat(cri, []models.Package{}, "") {
122 | n := p.Name
123 | if p.ModularityLabel != "" {
124 | n = fmt.Sprintf("%s::%s", p.ModularityLabel, p.Name)
125 | }
126 |
127 | if p.NotFixedYet {
128 | pkgs[n] = p
129 | continue
130 | }
131 |
132 | // OVALv1 includes definitions other than the target RHEL version
133 | if !strings.Contains(p.Version, ".el"+v) && !strings.Contains(p.Version, ".module+el"+v) {
134 | continue
135 | }
136 |
137 | // since different versions are defined for the same package, the newer version is adopted
138 | // example: OVALv2: oval:com.redhat.rhsa:def:20111349, oval:com.redhat.rhsa:def:20120451
139 | if base, ok := pkgs[n]; ok {
140 | v1 := version.NewVersion(base.Version)
141 | v2 := version.NewVersion(p.Version)
142 | if v1.GreaterThan(v2) {
143 | p = base
144 | }
145 | }
146 |
147 | pkgs[n] = p
148 | }
149 | return slices.Collect(maps.Values(pkgs))
150 | }
151 |
152 | func walkRedHat(cri Criteria, acc []models.Package, label string) []models.Package {
153 | for _, c := range cri.Criterions {
154 | switch {
155 | case strings.HasPrefix(c.Comment, "Module ") && strings.HasSuffix(c.Comment, " is enabled"):
156 | label = strings.TrimSuffix(strings.TrimPrefix(c.Comment, "Module "), " is enabled")
157 | case strings.Contains(c.Comment, " is earlier than "):
158 | ss := strings.Split(c.Comment, " is earlier than ")
159 | if len(ss) != 2 {
160 | continue
161 | }
162 | acc = append(acc, models.Package{
163 | Name: ss[0],
164 | Version: strings.Split(ss[1], " ")[0],
165 | ModularityLabel: label,
166 | })
167 | case !strings.HasPrefix(c.Comment, "Red Hat Enterprise Linux") && !strings.HasPrefix(c.Comment, "Red Hat CoreOS") && strings.HasSuffix(c.Comment, " is installed"):
168 | acc = append(acc, models.Package{
169 | Name: strings.TrimSuffix(c.Comment, " is installed"),
170 | ModularityLabel: label,
171 | NotFixedYet: true,
172 | })
173 | }
174 | }
175 |
176 | if len(cri.Criterias) == 0 {
177 | return acc
178 | }
179 | for _, c := range cri.Criterias {
180 | acc = walkRedHat(c, acc, label)
181 | }
182 | return acc
183 | }
184 |
--------------------------------------------------------------------------------
/commands/select.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 |
7 | "github.com/k0kubun/pp"
8 | "github.com/spf13/cobra"
9 | "github.com/spf13/viper"
10 | "golang.org/x/xerrors"
11 |
12 | "github.com/vulsio/goval-dictionary/config"
13 | "github.com/vulsio/goval-dictionary/db"
14 | "github.com/vulsio/goval-dictionary/models"
15 | )
16 |
17 | // SelectCmd is Subcommand for fetch RedHat OVAL
18 | var selectCmd = &cobra.Command{
19 | Use: "select",
20 | Short: "Select from DB",
21 | Long: `Select from DB`,
22 | }
23 |
24 | func init() {
25 | RootCmd.AddCommand(selectCmd)
26 |
27 | selectCmd.AddCommand(
28 | &cobra.Command{
29 | Use: "package ()",
30 | Short: "Select OVAL by package name",
31 | Args: cobra.RangeArgs(3, 4),
32 | RunE: func(_ *cobra.Command, args []string) error {
33 | arch := ""
34 | if len(args) == 4 {
35 | switch args[0] {
36 | case config.Amazon, config.Oracle, config.Fedora:
37 | arch = args[3]
38 | default:
39 | return xerrors.Errorf("Family: %s cannot use the Architecture argument.", args[0])
40 | }
41 | }
42 |
43 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
44 | if err != nil {
45 | if errors.Is(err, db.ErrDBLocked) {
46 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
47 | }
48 | return xerrors.Errorf("Failed to open DB. err: %w", err)
49 | }
50 |
51 | fetchMeta, err := driver.GetFetchMeta()
52 | if err != nil {
53 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
54 | }
55 | if fetchMeta.OutDated() {
56 | return xerrors.Errorf("Failed to select command. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
57 | }
58 |
59 | dfs, err := driver.GetByPackName(args[0], args[1], args[2], arch)
60 | if err != nil {
61 | return xerrors.Errorf("Failed to get cve by package. err: %w", err)
62 | }
63 |
64 | for _, d := range dfs {
65 | for _, cve := range d.Advisory.Cves {
66 | fmt.Printf("%s\n", cve.CveID)
67 | for _, pack := range d.AffectedPacks {
68 | fmt.Printf(" %v\n", pack)
69 | }
70 | }
71 | }
72 | fmt.Println("------------------")
73 | pp.ColoringEnabled = false
74 | _, _ = pp.Println(dfs)
75 |
76 | return nil
77 | },
78 | Example: `$ goval-dictionary select package ubuntu 24.04 bash
79 | $ goval-dictionary select package oracle 9 bash x86_64`,
80 | },
81 | &cobra.Command{
82 | Use: "cve-id ()",
83 | Short: "Select OVAL by CVE-ID",
84 | Args: cobra.RangeArgs(3, 4),
85 | RunE: func(_ *cobra.Command, args []string) error {
86 | arch := ""
87 | if len(args) == 4 {
88 | switch args[0] {
89 | case config.Amazon, config.Oracle, config.Fedora:
90 | arch = args[3]
91 | default:
92 | return xerrors.Errorf("Family: %s cannot use the Architecture argument.", args[0])
93 | }
94 | }
95 |
96 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
97 | if err != nil {
98 | if errors.Is(err, db.ErrDBLocked) {
99 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
100 | }
101 | return xerrors.Errorf("Failed to open DB. err: %w", err)
102 | }
103 |
104 | fetchMeta, err := driver.GetFetchMeta()
105 | if err != nil {
106 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
107 | }
108 | if fetchMeta.OutDated() {
109 | return xerrors.Errorf("Failed to select command. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
110 | }
111 |
112 | dfs, err := driver.GetByCveID(args[0], args[1], args[2], arch)
113 | if err != nil {
114 | return xerrors.Errorf("Failed to get cve by cveID. err: %w", err)
115 | }
116 | for _, d := range dfs {
117 | fmt.Printf("%s\n", d.Title)
118 | fmt.Printf("%v\n", d.Advisory.Cves)
119 | }
120 | fmt.Println("------------------")
121 | pp.ColoringEnabled = false
122 | _, _ = pp.Println(dfs)
123 |
124 | return nil
125 | },
126 | Example: `$ goval-dictionary select cve-id ubuntu 24.04 CVE-2024-6387
127 | $ goval-dictionary select cve-id oracle 9 CVE-2024-6387 x86_64`,
128 | },
129 | &cobra.Command{
130 | Use: "advisories ",
131 | Short: "List Advisories and Releated CVE-IDs",
132 | Args: cobra.ExactArgs(2),
133 | RunE: func(_ *cobra.Command, args []string) error {
134 | driver, err := db.NewDB(viper.GetString("dbtype"), viper.GetString("dbpath"), viper.GetBool("debug-sql"), db.Option{})
135 | if err != nil {
136 | if errors.Is(err, db.ErrDBLocked) {
137 | return xerrors.Errorf("Failed to open DB. Close DB connection before fetching. err: %w", err)
138 | }
139 | return xerrors.Errorf("Failed to open DB. err: %w", err)
140 | }
141 |
142 | fetchMeta, err := driver.GetFetchMeta()
143 | if err != nil {
144 | return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
145 | }
146 | if fetchMeta.OutDated() {
147 | return xerrors.Errorf("Failed to select command. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
148 | }
149 |
150 | m, err := driver.GetAdvisories(args[0], args[1])
151 | if err != nil {
152 | return xerrors.Errorf("Failed to get cve by cveID. err: %w", err)
153 | }
154 | pp.ColoringEnabled = false
155 | _, _ = pp.Println(m)
156 |
157 | return nil
158 | },
159 | Example: `$ goval-dictionary select advisories ubuntu 24.04`,
160 | },
161 | )
162 |
163 | }
164 |
--------------------------------------------------------------------------------
/fetcher/util/fetcher.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "bytes"
5 | "compress/bzip2"
6 | "compress/gzip"
7 | "fmt"
8 | "io"
9 | "net/http"
10 | "net/url"
11 | "sync"
12 | "time"
13 |
14 | "github.com/inconshreveable/log15"
15 | "github.com/klauspost/compress/zstd"
16 | "github.com/spf13/viper"
17 | "github.com/ulikunitz/xz"
18 | "golang.org/x/xerrors"
19 |
20 | "github.com/vulsio/goval-dictionary/config"
21 | )
22 |
23 | // MIMEType :
24 | type MIMEType int
25 |
26 | const (
27 | // MIMETypeUnknown :
28 | MIMETypeUnknown MIMEType = iota
29 | // MIMETypeXML :
30 | MIMETypeXML
31 | // MIMETypeTxt :
32 | MIMETypeTxt
33 | // MIMETypeJSON :
34 | MIMETypeJSON
35 | // MIMETypeYml :
36 | MIMETypeYml
37 | // MIMETypeHTML :
38 | MIMETypeHTML
39 | // MIMETypeBzip2 :
40 | MIMETypeBzip2
41 | // MIMETypeXz :
42 | MIMETypeXz
43 | // MIMETypeGzip :
44 | MIMETypeGzip
45 | // MIMETypeZst :
46 | MIMETypeZst
47 | )
48 |
49 | func (m MIMEType) String() string {
50 | switch m {
51 | case MIMETypeXML:
52 | return "xml"
53 | case MIMETypeTxt:
54 | return "txt"
55 | case MIMETypeJSON:
56 | return "json"
57 | case MIMETypeYml:
58 | return "yml"
59 | case MIMETypeHTML:
60 | return "html"
61 | case MIMETypeBzip2:
62 | return "bzip2"
63 | case MIMETypeXz:
64 | return "xz"
65 | case MIMETypeGzip:
66 | return "gz"
67 | case MIMETypeZst:
68 | return "zst"
69 | default:
70 | return "Unknown"
71 | }
72 | }
73 |
74 | // FetchRequest has url, mimetype and fetch option
75 | type FetchRequest struct {
76 | Target string
77 | URL string
78 | MIMEType MIMEType
79 | LogSuppressed bool
80 | }
81 |
82 | // FetchResult has url and OVAL definitions
83 | type FetchResult struct {
84 | Target string
85 | URL string
86 | Body []byte
87 | LogSuppressed bool
88 | }
89 |
90 | // genWorkers generate workers
91 | func genWorkers(num int) chan<- func() {
92 | tasks := make(chan func())
93 | for i := 0; i < num; i++ {
94 | go func() {
95 | for f := range tasks {
96 | f()
97 | }
98 | }()
99 | }
100 | return tasks
101 | }
102 |
103 | // FetchFeedFiles :
104 | func FetchFeedFiles(reqs []FetchRequest) (results []FetchResult, err error) {
105 | reqChan := make(chan FetchRequest, len(reqs))
106 | resChan := make(chan FetchResult, len(reqs))
107 | errChan := make(chan error, len(reqs))
108 | defer close(reqChan)
109 | defer close(resChan)
110 | defer close(errChan)
111 |
112 | for _, r := range reqs {
113 | if !r.LogSuppressed {
114 | log15.Info("Fetching... ", "URL", r.URL)
115 | }
116 | }
117 |
118 | go func() {
119 | for _, r := range reqs {
120 | reqChan <- r
121 | }
122 | }()
123 |
124 | concurrency := len(reqs)
125 | tasks := genWorkers(concurrency)
126 | wg := new(sync.WaitGroup)
127 | for range reqs {
128 | wg.Add(1)
129 | tasks <- func() {
130 | req := <-reqChan
131 | body, err := fetchFileWithUA(req)
132 | wg.Done()
133 | if err != nil {
134 | errChan <- err
135 | return
136 | }
137 | resChan <- FetchResult{
138 | Target: req.Target,
139 | URL: req.URL,
140 | Body: body,
141 | LogSuppressed: req.LogSuppressed,
142 | }
143 | }
144 | }
145 | wg.Wait()
146 |
147 | errs := []error{}
148 | timeout := time.After(10 * 60 * time.Second)
149 | for range reqs {
150 | select {
151 | case res := <-resChan:
152 | results = append(results, res)
153 | case err := <-errChan:
154 | errs = append(errs, err)
155 | case <-timeout:
156 | return results, fmt.Errorf("Timeout Fetching")
157 | }
158 | }
159 | if 0 < len(errs) {
160 | return results, fmt.Errorf("%s", errs)
161 | }
162 | return results, nil
163 | }
164 |
165 | func fetchFileWithUA(req FetchRequest) (body []byte, err error) {
166 | var proxyURL *url.URL
167 | var resp *http.Response
168 |
169 | httpClient := &http.Client{}
170 | httpProxy := viper.GetString("http-proxy")
171 | if httpProxy != "" {
172 | if proxyURL, err = url.Parse(httpProxy); err != nil {
173 | return nil, xerrors.Errorf("Failed to parse proxy url. err: %w", err)
174 | }
175 | httpClient = &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}}
176 | }
177 |
178 | httpreq, err := http.NewRequest("GET", req.URL, nil)
179 | if err != nil {
180 | return nil, xerrors.Errorf("Failed to download. err: %w", err)
181 | }
182 |
183 | httpreq.Header.Set("User-Agent", fmt.Sprintf("goval-dictionary/%s.%s", config.Version, config.Revision))
184 | resp, err = httpClient.Do(httpreq)
185 | if err != nil {
186 | return nil, xerrors.Errorf("Failed to download. err: %w", err)
187 | }
188 | defer resp.Body.Close()
189 |
190 | if resp.StatusCode != 200 {
191 | return nil, fmt.Errorf("Failed to HTTP GET. url: %s, response: %+v", req.URL, resp)
192 | }
193 |
194 | buf := bytes.Buffer{}
195 | if _, err := io.Copy(&buf, resp.Body); err != nil {
196 | return nil, err
197 | }
198 |
199 | var b bytes.Buffer
200 | switch req.MIMEType {
201 | case MIMETypeXML, MIMETypeTxt, MIMETypeJSON, MIMETypeYml, MIMETypeHTML:
202 | b = buf
203 | case MIMETypeBzip2:
204 | if _, err := b.ReadFrom(bzip2.NewReader(bytes.NewReader(buf.Bytes()))); err != nil {
205 | return nil, xerrors.Errorf("Failed to open bzip2 file. err: %w", err)
206 | }
207 | case MIMETypeXz:
208 | r, err := xz.NewReader(bytes.NewReader(buf.Bytes()))
209 | if err != nil {
210 | return nil, xerrors.Errorf("Failed to open xz file. err: %w", err)
211 | }
212 | if _, err = b.ReadFrom(r); err != nil {
213 | return nil, xerrors.Errorf("Failed to read xz file. err: %w", err)
214 | }
215 | case MIMETypeGzip:
216 | r, err := gzip.NewReader(bytes.NewReader(buf.Bytes()))
217 | if err != nil {
218 | return nil, xerrors.Errorf("Failed to open gzip file. err: %w", err)
219 | }
220 | if _, err = b.ReadFrom(r); err != nil {
221 | return nil, xerrors.Errorf("Failed to read gzip file. err: %w", err)
222 | }
223 | case MIMETypeZst:
224 | r, err := zstd.NewReader(bytes.NewReader(buf.Bytes()))
225 | if err != nil {
226 | return nil, xerrors.Errorf("Failed to open zstd file. err: %w", err)
227 | }
228 | if _, err = b.ReadFrom(r); err != nil {
229 | return nil, xerrors.Errorf("Failed to read zstd file. err: %w", err)
230 | }
231 | default:
232 | return nil, xerrors.Errorf("unexpected request MIME Type. expected: %q, actual: %q", []MIMEType{MIMETypeXML, MIMETypeTxt, MIMETypeJSON, MIMETypeYml, MIMETypeHTML, MIMETypeBzip2, MIMETypeXz, MIMETypeGzip, MIMETypeZst}, req.MIMEType)
233 | }
234 |
235 | return b.Bytes(), nil
236 | }
237 |
--------------------------------------------------------------------------------
/models/ubuntu/ubuntu_test.go:
--------------------------------------------------------------------------------
1 | package ubuntu
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 |
7 | "github.com/k0kubun/pp"
8 |
9 | "github.com/vulsio/goval-dictionary/models"
10 | )
11 |
12 | func TestCollectUbuntuPacks(t *testing.T) {
13 | var tests = []struct {
14 | cri Criteria
15 | tests map[string]dpkgInfoTest
16 | expected []models.Package
17 | }{
18 | {
19 | cri: Criteria{
20 | Criterions: []Criterion{
21 | {
22 | TestRef: "oval:com.ubuntu.jammy:tst:200224390000040",
23 | Comment: "gcc-snapshot package in jammy, is related to the CVE in some way and has been fixed (note: '20140405-0ubuntu1').",
24 | },
25 | },
26 | },
27 | tests: map[string]dpkgInfoTest{
28 | "oval:com.ubuntu.jammy:tst:200224390000040": {
29 | Name: "gcc-snapshot",
30 | FixedVersion: "0:20140405-0ubuntu1",
31 | },
32 | },
33 | expected: []models.Package{},
34 | },
35 | {
36 | cri: Criteria{
37 | Criterions: []Criterion{
38 | {
39 | TestRef: "oval:com.ubuntu.jammy:tst:2018128860000050",
40 | Comment: "gcc-snapshot: while related to the CVE in some way, a decision has been made to ignore this issue.",
41 | },
42 | },
43 | },
44 | tests: map[string]dpkgInfoTest{
45 | "oval:com.ubuntu.jammy:tst:2018128860000050": {Name: "gcc-snapshot"},
46 | },
47 | expected: []models.Package{
48 | {
49 | Name: "gcc-snapshot",
50 | NotFixedYet: true,
51 | },
52 | },
53 | },
54 | {
55 | cri: Criteria{
56 | Criterions: []Criterion{
57 | {
58 | TestRef: "oval:com.ubuntu.jammy:tst:200224390000000",
59 | Comment: "gcc-arm-none-eabi package in jammy is affected and may need fixing.",
60 | },
61 | },
62 | },
63 | tests: map[string]dpkgInfoTest{
64 | "oval:com.ubuntu.jammy:tst:200224390000000": {Name: "gcc-arm-none-eabi"},
65 | },
66 | expected: []models.Package{},
67 | },
68 | {
69 | cri: Criteria{
70 | Criterions: []Criterion{
71 | {
72 | TestRef: "oval:com.ubuntu.jammy:tst:200224390000000",
73 | Comment: "gcc-arm-none-eabi package in jammy is affected and needs fixing.",
74 | },
75 | },
76 | },
77 | tests: map[string]dpkgInfoTest{
78 | "oval:com.ubuntu.jammy:tst:200224390000000": {Name: "gcc-arm-none-eabi"},
79 | },
80 | expected: []models.Package{
81 | {
82 | Name: "gcc-arm-none-eabi",
83 | NotFixedYet: true,
84 | },
85 | },
86 | },
87 | {
88 | cri: Criteria{
89 | Criterions: []Criterion{
90 | {
91 | TestRef: "oval:com.ubuntu.jammy:tst:2018128860000050",
92 | Comment: "gcc-snapshot package in jammy is affected, but a decision has been made to defer addressing it.",
93 | },
94 | },
95 | },
96 | tests: map[string]dpkgInfoTest{
97 | "oval:com.ubuntu.jammy:tst:2018128860000050": {Name: "gcc-snapshot"},
98 | },
99 | expected: []models.Package{
100 | {
101 | Name: "gcc-snapshot",
102 | NotFixedYet: true,
103 | },
104 | },
105 | },
106 | {
107 | cri: Criteria{
108 | Criterions: []Criterion{
109 | {
110 | TestRef: "oval:com.ubuntu.xenial:tst:2020143720000010",
111 | Comment: "grub2-unsigned package in xenial is affected. An update containing the fix has been completed and is pending publication (note: '2.04-1ubuntu42').",
112 | },
113 | },
114 | },
115 | tests: map[string]dpkgInfoTest{
116 | "oval:com.ubuntu.xenial:tst:2020143720000010": {
117 | Name: "grub2-unsigned",
118 | FixedVersion: "0:2.04-1ubuntu42",
119 | },
120 | },
121 | expected: []models.Package{
122 | {
123 | Name: "grub2-unsigned",
124 | NotFixedYet: true,
125 | },
126 | },
127 | },
128 | {
129 | cri: Criteria{
130 | Criterions: []Criterion{
131 | {
132 | TestRef: "oval:com.ubuntu.jammy:tst:2021285440000000",
133 | Comment: "subversion package in jammy was vulnerable but has been fixed (note: '1.14.1-3ubuntu0.22.04.1').",
134 | },
135 | },
136 | },
137 | tests: map[string]dpkgInfoTest{
138 | "oval:com.ubuntu.jammy:tst:2021285440000000": {
139 | Name: "subversion",
140 | FixedVersion: "0:1.14.1-3ubuntu0.22.04.1",
141 | },
142 | },
143 | expected: []models.Package{
144 | {
145 | Name: "subversion",
146 | Version: "0:1.14.1-3ubuntu0.22.04.1",
147 | NotFixedYet: false,
148 | },
149 | },
150 | },
151 | {
152 | cri: Criteria{
153 | Criterions: []Criterion{
154 | {
155 | TestRef: "oval:com.ubuntu.jammy:tst:2021299550000000",
156 | Comment: "firefox package in jammy was vulnerable and has been fixed, but no release version available for it.",
157 | },
158 | },
159 | },
160 | tests: map[string]dpkgInfoTest{
161 | "oval:com.ubuntu.jammy:tst:2021299550000000": {Name: "firefox"},
162 | },
163 | expected: []models.Package{
164 | {
165 | Name: "firefox",
166 | Version: "",
167 | NotFixedYet: false,
168 | },
169 | },
170 | },
171 | {
172 | cri: Criteria{
173 | Criterions: []Criterion{
174 | {
175 | TestRef: "oval:com.ubuntu.jammy:tst:2016107230000000",
176 | Comment: "Is kernel linux running",
177 | },
178 | {
179 | TestRef: "oval:com.ubuntu.jammy:tst:2018121260000030",
180 | Comment: "kernel version comparison",
181 | },
182 | },
183 | },
184 | tests: map[string]dpkgInfoTest{},
185 | expected: []models.Package{},
186 | },
187 | {
188 | cri: Criteria{
189 | Criterias: []Criteria{
190 | {
191 | Criterions: []Criterion{
192 | {
193 | TestRef: "oval:com.ubuntu.jammy:tst:200901660000010",
194 | Comment: "poppler package in jammy was vulnerable but has been fixed (note: '0.10.5-1ubuntu2').",
195 | },
196 | },
197 | },
198 | },
199 | },
200 | tests: map[string]dpkgInfoTest{
201 | "oval:com.ubuntu.jammy:tst:200901660000010": {
202 | Name: "poppler",
203 | FixedVersion: "0:0.10.5-1ubuntu2",
204 | },
205 | },
206 | expected: []models.Package{
207 | {
208 | Name: "poppler",
209 | Version: "0:0.10.5-1ubuntu2",
210 | NotFixedYet: false,
211 | },
212 | },
213 | },
214 | }
215 |
216 | for i, tt := range tests {
217 | if actual := collectUbuntuPacks(tt.cri, tt.tests); !reflect.DeepEqual(tt.expected, actual) {
218 | e := pp.Sprintf("%v", tt.expected)
219 | a := pp.Sprintf("%v", actual)
220 | t.Errorf("[%d]: expected: %s\n, actual: %s\n", i, e, a)
221 | }
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/models/ubuntu/types.go:
--------------------------------------------------------------------------------
1 | package ubuntu
2 |
3 | import "encoding/xml"
4 |
5 | // Root : root object
6 | type Root struct {
7 | XMLName xml.Name `xml:"oval_definitions"`
8 | Generator Generator `xml:"generator"`
9 | Definitions Definitions `xml:"definitions"`
10 | Tests Tests `xml:"tests"`
11 | Objects Objects `xml:"objects"`
12 | States States `xml:"states"`
13 | Variables Variables `xml:"variables"`
14 | }
15 |
16 | // Generator : >generator
17 | type Generator struct {
18 | XMLName xml.Name `xml:"generator"`
19 | ProductName string `xml:"product_name"`
20 | ProductVersion string `xml:"product_version"`
21 | SchemaVersion string `xml:"schema_version"`
22 | Timestamp string `xml:"timestamp"`
23 | }
24 |
25 | // Definitions : >definitions
26 | type Definitions struct {
27 | XMLName xml.Name `xml:"definitions"`
28 | Definitions []Definition `xml:"definition"`
29 | }
30 |
31 | // Definition : >definitions>definition
32 | type Definition struct {
33 | XMLName xml.Name `xml:"definition"`
34 | ID string `xml:"id,attr"`
35 | Class string `xml:"class,attr"`
36 | Title string `xml:"metadata>title"`
37 | Affecteds []Affected `xml:"metadata>affected"`
38 | References []Reference `xml:"metadata>reference"`
39 | Description string `xml:"metadata>description"`
40 | Advisory Advisory `xml:"metadata>advisory"`
41 | Notes struct {
42 | Text string `xml:",chardata"`
43 | Note string `xml:"note"`
44 | } `xml:"notes"`
45 | Criteria Criteria `xml:"criteria"`
46 | }
47 |
48 | // Criteria : >definitions>definition>criteria
49 | type Criteria struct {
50 | XMLName xml.Name `xml:"criteria"`
51 | Operator string `xml:"operator,attr"`
52 | Criterias []Criteria `xml:"criteria"`
53 | Criterions []Criterion `xml:"criterion"`
54 | }
55 |
56 | // Criterion : >definitions>definition>criteria>*>criterion
57 | type Criterion struct {
58 | XMLName xml.Name `xml:"criterion"`
59 | TestRef string `xml:"test_ref,attr"`
60 | Comment string `xml:"comment,attr"`
61 | }
62 |
63 | // Affected : >definitions>definition>metadata>affected
64 | type Affected struct {
65 | XMLName xml.Name `xml:"affected"`
66 | Family string `xml:"family,attr"`
67 | Platforms []string `xml:"platform"`
68 | }
69 |
70 | // Reference : >definitions>definition>metadata>reference
71 | type Reference struct {
72 | XMLName xml.Name `xml:"reference"`
73 | Source string `xml:"source,attr"`
74 | RefID string `xml:"ref_id,attr"`
75 | RefURL string `xml:"ref_url,attr"`
76 | }
77 |
78 | // Advisory : >definitions>definition>metadata>advisory
79 | type Advisory struct {
80 | XMLName xml.Name `xml:"advisory"`
81 | Severity string `xml:"severity"`
82 | Rights string `xml:"rights"`
83 | PublicDate string `xml:"public_date"`
84 | Refs []Ref `xml:"ref"`
85 | Bugs []Bug `xml:"bug"`
86 | }
87 |
88 | // Ref : >definitions>definition>metadata>advisory>ref
89 | type Ref struct {
90 | XMLName xml.Name `xml:"ref"`
91 | URL string `xml:",chardata"`
92 | }
93 |
94 | // Bug : >definitions>definition>metadata>advisory>bug
95 | type Bug struct {
96 | XMLName xml.Name `xml:"bug"`
97 | URL string `xml:",chardata"`
98 | }
99 |
100 | // Tests : >tests
101 | type Tests struct {
102 | XMLName xml.Name `xml:"tests"`
103 | Textfilecontent54Test []Textfilecontent54Test `xml:"textfilecontent54_test"`
104 | }
105 |
106 | // Textfilecontent54Test : >tests>textfilecontent54_test
107 | type Textfilecontent54Test struct {
108 | ID string `xml:"id,attr"`
109 | Check string `xml:"check,attr"`
110 | CheckExistence string `xml:"check_existence,attr"`
111 | Comment string `xml:"comment,attr"`
112 | Object ObjectRef `xml:"object"`
113 | State StateRef `xml:"state"`
114 | }
115 |
116 | // ObjectRef : >tests>textfilecontent54_test>object-object_ref
117 | type ObjectRef struct {
118 | XMLName xml.Name `xml:"object"`
119 | Text string `xml:",chardata"`
120 | ObjectRef string `xml:"object_ref,attr"`
121 | }
122 |
123 | // StateRef : >tests>textfilecontent54_test>state-state_ref
124 | type StateRef struct {
125 | XMLName xml.Name `xml:"state"`
126 | Text string `xml:",chardata"`
127 | StateRef string `xml:"state_ref,attr"`
128 | }
129 |
130 | // Objects : >objects
131 | type Objects struct {
132 | XMLName xml.Name `xml:"objects"`
133 | Textfilecontent54Object []Textfilecontent54Object `xml:"textfilecontent54_object"`
134 | }
135 |
136 | // Textfilecontent54Object : >objects>textfilecontent54_object
137 | type Textfilecontent54Object struct {
138 | ID string `xml:"id,attr"`
139 | Comment string `xml:"comment,attr"`
140 | Path string `xml:"path"`
141 | Filename string `xml:"filename"`
142 | Pattern struct {
143 | Text string `xml:",chardata"`
144 | Operation string `xml:"operation,attr"`
145 | Datatype string `xml:"datatype,attr"`
146 | VarRef string `xml:"var_ref,attr"`
147 | VarCheck string `xml:"var_check,attr"`
148 | } `xml:"pattern"`
149 | Instance struct {
150 | Text string `xml:",chardata"`
151 | Operation string `xml:"operation,attr"`
152 | Datatype string `xml:"datatype,attr"`
153 | } `xml:"instance"`
154 | }
155 |
156 | // States : >states
157 | type States struct {
158 | XMLName xml.Name `xml:"states"`
159 | Textfilecontent54State []Textfilecontent54State `xml:"textfilecontent54_state"`
160 | }
161 |
162 | // Textfilecontent54State : >states>textfilecontent54_state
163 | type Textfilecontent54State struct {
164 | ID string `xml:"id,attr"`
165 | Comment string `xml:"comment,attr"`
166 | Subexpression struct {
167 | Text string `xml:",chardata"`
168 | Datatype string `xml:"datatype,attr"`
169 | Operation string `xml:"operation,attr"`
170 | } `xml:"subexpression"`
171 | }
172 |
173 | // Variables : >variables
174 | type Variables struct {
175 | XMLName xml.Name `xml:"variables"`
176 | ConstantVariable []ConstantVariable `xml:"constant_variable"`
177 | }
178 |
179 | // ConstantVariable : >variables>constant_variable
180 | type ConstantVariable struct {
181 | Text string `xml:",chardata"`
182 | ID string `xml:"id,attr"`
183 | Version string `xml:"version,attr"`
184 | Datatype string `xml:"datatype,attr"`
185 | Comment string `xml:"comment,attr"`
186 | Value []string `xml:"value"`
187 | }
188 |
189 | type dpkgInfoTest struct {
190 | Name string
191 | FixedVersion string
192 | }
193 |
--------------------------------------------------------------------------------
/models/debian/types.go:
--------------------------------------------------------------------------------
1 | package debian
2 |
3 | import "encoding/xml"
4 |
5 | // Root : root object
6 | type Root struct {
7 | XMLName xml.Name `xml:"oval_definitions"`
8 | Generator Generator `xml:"generator"`
9 | Definitions Definitions `xml:"definitions"`
10 | Tests Tests `xml:"tests"`
11 | Objects Objects `xml:"objects"`
12 | States States `xml:"states"`
13 | }
14 |
15 | // Generator : >generator
16 | type Generator struct {
17 | XMLName xml.Name `xml:"generator"`
18 | ProductName string `xml:"product_name"`
19 | SchemaVersion string `xml:"schema_version"`
20 | Timestamp string `xml:"timestamp"`
21 | }
22 |
23 | // Definitions : >definitions
24 | type Definitions struct {
25 | XMLName xml.Name `xml:"definitions"`
26 | Definitions []Definition `xml:"definition"`
27 | }
28 |
29 | // Definition : >definitions>definition
30 | type Definition struct {
31 | XMLName xml.Name `xml:"definition"`
32 | ID string `xml:"id,attr"`
33 | Class string `xml:"class,attr"`
34 | Title string `xml:"metadata>title"`
35 | Affecteds []Affected `xml:"metadata>affected"`
36 | References []Reference `xml:"metadata>reference"`
37 | Description string `xml:"metadata>description"`
38 | Debian Debian `xml:"metadata>debian"`
39 | Criteria Criteria `xml:"criteria"`
40 | }
41 |
42 | // Criteria : >definitions>definition>criteria
43 | type Criteria struct {
44 | XMLName xml.Name `xml:"criteria"`
45 | Operator string `xml:"operator,attr"`
46 | Criterias []Criteria `xml:"criteria"`
47 | Criterions []Criterion `xml:"criterion"`
48 | }
49 |
50 | // Criterion : >definitions>definition>criteria>*>criterion
51 | type Criterion struct {
52 | XMLName xml.Name `xml:"criterion"`
53 | TestRef string `xml:"test_ref,attr"`
54 | Comment string `xml:"comment,attr"`
55 | }
56 |
57 | // Affected : >definitions>definition>metadata>affected
58 | type Affected struct {
59 | XMLName xml.Name `xml:"affected"`
60 | Family string `xml:"family,attr"`
61 | Platforms []string `xml:"platform"`
62 | Products []string `xml:"product"`
63 | }
64 |
65 | // Reference : >definitions>definition>metadata>reference
66 | type Reference struct {
67 | XMLName xml.Name `xml:"reference"`
68 | Source string `xml:"source,attr"`
69 | RefID string `xml:"ref_id,attr"`
70 | RefURL string `xml:"ref_url,attr"`
71 | }
72 |
73 | // Debian : >definitions>definition>metadata>debian
74 | type Debian struct {
75 | XMLName xml.Name `xml:"debian"`
76 | DSA string `xml:"dsa"`
77 | MoreInfo string `xml:"moreinfo"`
78 | Date string `xml:"date"`
79 | }
80 |
81 | // Tests : >tests
82 | type Tests struct {
83 | XMLName xml.Name `xml:"tests"`
84 | Textfilecontent54Test Textfilecontent54Test `xml:"textfilecontent54_test"`
85 | UnameTest UnameTest `xml:"uname_test"`
86 | DpkginfoTest []DpkginfoTest `xml:"dpkginfo_test"`
87 | }
88 |
89 | // Textfilecontent54Test : >tests>textfilecontent54_test
90 | type Textfilecontent54Test struct {
91 | Text string `xml:",chardata"`
92 | Check string `xml:"check,attr"`
93 | CheckExistence string `xml:"check_existence,attr"`
94 | Comment string `xml:"comment,attr"`
95 | ID string `xml:"id,attr"`
96 | Object ObjectRef `xml:"object"`
97 | State StateRef `xml:"state"`
98 | }
99 |
100 | // UnameTest : >tests>uname_test
101 | type UnameTest struct {
102 | Text string `xml:",chardata"`
103 | Check string `xml:"check,attr"`
104 | CheckExistence string `xml:"check_existence,attr"`
105 | Comment string `xml:"comment,attr"`
106 | ID string `xml:"id,attr"`
107 | Object ObjectRef `xml:"object"`
108 | }
109 |
110 | // DpkginfoTest : >tests>dpkginfo_test
111 | type DpkginfoTest struct {
112 | Text string `xml:",chardata"`
113 | Check string `xml:"check,attr"`
114 | CheckExistence string `xml:"check_existence,attr"`
115 | Comment string `xml:"comment,attr"`
116 | ID string `xml:"id,attr"`
117 | Object ObjectRef `xml:"object"`
118 | State StateRef `xml:"state"`
119 | }
120 |
121 | // ObjectRef :
122 | // >tests>textfilecontent54_test>object-object_ref
123 | // >tests>uname_test>object-object_ref
124 | // >tests>dpkginfo_test>object-object_ref
125 | type ObjectRef struct {
126 | XMLName xml.Name `xml:"object"`
127 | Text string `xml:",chardata"`
128 | ObjectRef string `xml:"object_ref,attr"`
129 | }
130 |
131 | // StateRef :
132 | // >tests>textfilecontent54_test>state-state_ref
133 | // >tests>dpkginfo_test>state-state_ref
134 | type StateRef struct {
135 | XMLName xml.Name `xml:"state"`
136 | Text string `xml:",chardata"`
137 | StateRef string `xml:"state_ref,attr"`
138 | }
139 |
140 | // Objects : >objects
141 | type Objects struct {
142 | XMLName xml.Name `xml:"objects"`
143 | Textfilecontent54Object Textfilecontent54Object `xml:"textfilecontent54_object"`
144 | UnameObject UnameObject `xml:"uname_object"`
145 | DpkginfoObject []DpkginfoObject `xml:"dpkginfo_object"`
146 | }
147 |
148 | // Textfilecontent54Object : >objects>textfilecontent54_object
149 | type Textfilecontent54Object struct {
150 | ID string `xml:"id,attr"`
151 | Path string `xml:"path"`
152 | Filename string `xml:"filename"`
153 | Pattern struct {
154 | Text string `xml:",chardata"`
155 | Operation string `xml:"operation,attr"`
156 | } `xml:"pattern"`
157 | Instance struct {
158 | Text string `xml:",chardata"`
159 | Datatype string `xml:"datatype,attr"`
160 | } `xml:"instance"`
161 | }
162 |
163 | // UnameObject : >objects>uname_object
164 | type UnameObject struct {
165 | ID string `xml:"id,attr"`
166 | }
167 |
168 | // DpkginfoObject : >objects>dpkginfo_object
169 | type DpkginfoObject struct {
170 | ID string `xml:"id,attr"`
171 | Name string `xml:"name"`
172 | }
173 |
174 | // States : >states
175 | type States struct {
176 | XMLName xml.Name `xml:"states"`
177 | Textfilecontent54State Textfilecontent54State `xml:"textfilecontent54_state"`
178 | DpkginfoState []DpkginfoState `xml:"dpkginfo_state"`
179 | }
180 |
181 | // Textfilecontent54State : >states>textfilecontent54_state
182 | type Textfilecontent54State struct {
183 | ID string `xml:"id,attr"`
184 | Subexpression struct {
185 | Text string `xml:",chardata"`
186 | Operation string `xml:"operation,attr"`
187 | } `xml:"subexpression"`
188 | }
189 |
190 | // DpkginfoState : >states>dpkginfo_state
191 | type DpkginfoState struct {
192 | ID string `xml:"id,attr"`
193 | Evr struct {
194 | Text string `xml:",chardata"`
195 | Datatype string `xml:"datatype,attr"`
196 | Operation string `xml:"operation,attr"`
197 | } `xml:"evr"`
198 | }
199 |
--------------------------------------------------------------------------------
/models/ubuntu/ubuntu.go:
--------------------------------------------------------------------------------
1 | package ubuntu
2 |
3 | import (
4 | "regexp"
5 | "strings"
6 | "time"
7 |
8 | "github.com/inconshreveable/log15"
9 | "github.com/spf13/viper"
10 | "golang.org/x/xerrors"
11 |
12 | "github.com/vulsio/goval-dictionary/models"
13 | "github.com/vulsio/goval-dictionary/models/util"
14 | )
15 |
16 | // ConvertToModel Convert OVAL to models
17 | func ConvertToModel(root *Root) ([]models.Definition, error) {
18 | tests, err := parseTests(*root)
19 | if err != nil {
20 | return nil, xerrors.Errorf("Failed to parse oval.Tests. err: %w", err)
21 | }
22 | return parseDefinitions(root.Definitions.Definitions, tests), nil
23 | }
24 |
25 | var rePkgComment = regexp.MustCompile(`The '(.*)' package binar.+`)
26 |
27 | func parseObjects(ovalObjs Objects) map[string]string {
28 | objs := map[string]string{}
29 | for _, obj := range ovalObjs.Textfilecontent54Object {
30 | matched := rePkgComment.FindAllStringSubmatch(obj.Comment, 1)
31 | if len(matched[0]) != 2 {
32 | continue
33 | }
34 | objs[obj.ID] = matched[0][1]
35 | }
36 | return objs
37 | }
38 |
39 | func parseStates(objStates States) map[string]Textfilecontent54State {
40 | states := map[string]Textfilecontent54State{}
41 | for _, state := range objStates.Textfilecontent54State {
42 | states[state.ID] = state
43 | }
44 | return states
45 | }
46 |
47 | func parseTests(root Root) (map[string]dpkgInfoTest, error) {
48 | objs := parseObjects(root.Objects)
49 | states := parseStates(root.States)
50 | tests := map[string]dpkgInfoTest{}
51 | for _, test := range root.Tests.Textfilecontent54Test {
52 | t, err := followTestRefs(test, objs, states)
53 | if err != nil {
54 | return nil, xerrors.Errorf("Failed to follow test refs. err: %w", err)
55 | }
56 | tests[test.ID] = t
57 | }
58 | return tests, nil
59 | }
60 |
61 | func followTestRefs(test Textfilecontent54Test, objects map[string]string, states map[string]Textfilecontent54State) (dpkgInfoTest, error) {
62 | var t dpkgInfoTest
63 |
64 | // Follow object ref
65 | if test.Object.ObjectRef == "" {
66 | return t, nil
67 | }
68 |
69 | pkgName, ok := objects[test.Object.ObjectRef]
70 | if !ok {
71 | return t, xerrors.Errorf("Failed to find object ref. object ref: %s, test ref: %s, err: invalid tests data", test.Object.ObjectRef, test.ID)
72 | }
73 | t.Name = pkgName
74 |
75 | // Follow state ref
76 | if test.State.StateRef == "" {
77 | return t, nil
78 | }
79 |
80 | state, ok := states[test.State.StateRef]
81 | if !ok {
82 | return t, xerrors.Errorf("Failed to find state ref. state ref: %s, test ref: %s, err: invalid tests data", test.State.StateRef, test.ID)
83 | }
84 |
85 | if state.Subexpression.Datatype == "debian_evr_string" && state.Subexpression.Operation == "less than" {
86 | t.FixedVersion = state.Subexpression.Text
87 | }
88 |
89 | return t, nil
90 | }
91 |
92 | func parseDefinitions(ovalDefs []Definition, tests map[string]dpkgInfoTest) []models.Definition {
93 | defs := []models.Definition{}
94 |
95 | for _, d := range ovalDefs {
96 | if strings.Contains(d.Description, "** REJECT **") {
97 | continue
98 | }
99 |
100 | cves := []models.Cve{}
101 | rs := []models.Reference{}
102 | for _, r := range d.References {
103 | if r.Source == "CVE" {
104 | cves = append(cves, models.Cve{
105 | CveID: r.RefID,
106 | Href: r.RefURL,
107 | })
108 | }
109 |
110 | rs = append(rs, models.Reference{
111 | Source: r.Source,
112 | RefID: r.RefID,
113 | RefURL: r.RefURL,
114 | })
115 | }
116 |
117 | for _, r := range d.Advisory.Refs {
118 | rs = append(rs, models.Reference{
119 | Source: "Ref",
120 | RefURL: r.URL,
121 | })
122 | }
123 |
124 | for _, r := range d.Advisory.Bugs {
125 | rs = append(rs, models.Reference{
126 | Source: "Bug",
127 | RefURL: r.URL,
128 | })
129 | }
130 |
131 | date := util.ParsedOrDefaultTime([]string{"2006-01-02", "2006-01-02 15:04:05", "2006-01-02 15:04:05 +0000", "2006-01-02 15:04:05 MST"}, d.Advisory.PublicDate)
132 |
133 | def := models.Definition{
134 | DefinitionID: d.ID,
135 | Title: d.Title,
136 | Description: d.Description,
137 | Advisory: models.Advisory{
138 | Severity: d.Advisory.Severity,
139 | Cves: cves,
140 | Bugzillas: []models.Bugzilla{},
141 | AffectedCPEList: []models.Cpe{},
142 | Issued: date,
143 | Updated: date,
144 | },
145 | Debian: nil,
146 | AffectedPacks: collectUbuntuPacks(d.Criteria, tests),
147 | References: rs,
148 | }
149 |
150 | if viper.GetBool("no-details") {
151 | def.Title = ""
152 | def.Description = ""
153 | def.Advisory.Severity = ""
154 | def.Advisory.AffectedCPEList = []models.Cpe{}
155 | def.Advisory.Bugzillas = []models.Bugzilla{}
156 | def.Advisory.Issued = time.Time{}
157 | def.Advisory.Updated = time.Time{}
158 | def.References = []models.Reference{}
159 | }
160 |
161 | defs = append(defs, def)
162 | }
163 |
164 | return defs
165 | }
166 |
167 | func collectUbuntuPacks(cri Criteria, tests map[string]dpkgInfoTest) []models.Package {
168 | return walkCriterion(cri, tests)
169 | }
170 |
171 | func walkCriterion(cri Criteria, tests map[string]dpkgInfoTest) []models.Package {
172 | pkgs := []models.Package{}
173 | for _, c := range cri.Criterions {
174 | t, ok := tests[c.TestRef]
175 | if !ok {
176 | continue
177 | }
178 |
179 | if strings.Contains(c.Comment, "is related to the CVE in some way and has been fixed") || // status: not vulnerable(= not affected)
180 | strings.Contains(c.Comment, "is affected and may need fixing") { // status: needs-triage
181 | continue
182 | }
183 |
184 | if strings.Contains(c.Comment, "is affected and needs fixing") || // status: needed
185 | strings.Contains(c.Comment, "is affected, but a decision has been made to defer addressing it") || // status: deferred
186 | strings.Contains(c.Comment, "is affected. An update containing the fix has been completed and is pending publication") || // status: pending
187 | strings.Contains(c.Comment, "while related to the CVE in some way, a decision has been made to ignore this issue") { // status: ignored
188 | pkgs = append(pkgs, models.Package{
189 | Name: t.Name,
190 | NotFixedYet: true,
191 | })
192 | } else if strings.Contains(c.Comment, "was vulnerable but has been fixed") || // status: released
193 | strings.Contains(c.Comment, "was vulnerable and has been fixed") { // status: released, only this comment: "firefox package in $RELEASE_NAME was vulnerable and has been fixed, but no release version available for it."
194 | pkgs = append(pkgs, models.Package{
195 | Name: t.Name,
196 | Version: t.FixedVersion,
197 | NotFixedYet: false,
198 | })
199 | } else {
200 | log15.Warn("Failed to detect patch status.", "comment", c.Comment)
201 | }
202 | }
203 |
204 | for _, c := range cri.Criterias {
205 | if ps := walkCriterion(c, tests); len(ps) > 0 {
206 | pkgs = append(pkgs, ps...)
207 | }
208 | }
209 | return pkgs
210 | }
211 |
--------------------------------------------------------------------------------
/fetcher/fedora/types_test.go:
--------------------------------------------------------------------------------
1 | package fedora
2 |
3 | import (
4 | "strings"
5 | "testing"
6 |
7 | "github.com/google/go-cmp/cmp"
8 | "github.com/google/go-cmp/cmp/cmpopts"
9 |
10 | models "github.com/vulsio/goval-dictionary/models/fedora"
11 | )
12 |
13 | func TestRpmNewPackageFromRpm(t *testing.T) {
14 | tests := []struct {
15 | name string
16 | rpm Rpm
17 | want models.Package
18 | wantErr bool
19 | }{
20 | {
21 | name: "normal",
22 | rpm: "name-1:1.0-1.module_12345.aarch64",
23 | want: models.Package{
24 | Name: "name",
25 | Epoch: "1",
26 | Version: "1.0",
27 | Release: "1.module_12345",
28 | Arch: "aarch64",
29 | Filename: "name-1:1.0-1.module_12345.aarch64",
30 | },
31 | },
32 | {
33 | name: "with.rpm",
34 | rpm: "name-1:1.0-1.module_12345.aarch64.rpm",
35 | want: models.Package{
36 | Name: "name",
37 | Epoch: "1",
38 | Version: "1.0",
39 | Release: "1.module_12345",
40 | Arch: "aarch64",
41 | Filename: "name-1:1.0-1.module_12345.aarch64",
42 | },
43 | },
44 | {
45 | name: "name-with-hyphen",
46 | rpm: "name-with-hyphen-1:1.0-1.module_12345.aarch64",
47 | want: models.Package{
48 | Name: "name-with-hyphen",
49 | Epoch: "1",
50 | Version: "1.0",
51 | Release: "1.module_12345",
52 | Arch: "aarch64",
53 | Filename: "name-with-hyphen-1:1.0-1.module_12345.aarch64",
54 | },
55 | },
56 | {
57 | name: "invalid rpm",
58 | rpm: "invalid rpm",
59 | wantErr: true,
60 | },
61 | {
62 | name: "can not find release",
63 | rpm: "no_release:1.0.aarch64",
64 | wantErr: true,
65 | },
66 | {
67 | name: "can not find version",
68 | rpm: "no_version:1.0-1.module_12345.aarch64",
69 | wantErr: true,
70 | },
71 | {
72 | name: "can not find epoch",
73 | rpm: "noepoch-1.0-1.module_12345.aarch64",
74 | want: models.Package{
75 | Name: "noepoch",
76 | Epoch: "0",
77 | Version: "1.0",
78 | Release: "1.module_12345",
79 | Arch: "aarch64",
80 | Filename: "noepoch-1.0-1.module_12345.aarch64",
81 | },
82 | },
83 | }
84 | for _, tt := range tests {
85 | t.Run(tt.name, func(t *testing.T) {
86 | got, err := tt.rpm.NewPackageFromRpm()
87 | if (err == nil) == tt.wantErr {
88 | t.Fatalf("unexpected error: %v", err)
89 | }
90 |
91 | if diff := cmp.Diff(got, tt.want); diff != "" {
92 | t.Errorf("(-got +want):\n%s", diff)
93 | }
94 | })
95 | }
96 | }
97 |
98 | func TestUpdatesPerVersionMerge(t *testing.T) {
99 | tests := []struct {
100 | name string
101 | source map[string]*models.Updates
102 | target map[string]*models.Updates
103 | want map[string]*models.Updates
104 | }{
105 | {
106 | name: "merge success",
107 | source: map[string]*models.Updates{
108 | "35": {
109 | UpdateList: []models.UpdateInfo{
110 | {
111 | Title: "update35-1",
112 | },
113 | {
114 | Title: "update35-2",
115 | },
116 | },
117 | },
118 | "34": {
119 | UpdateList: []models.UpdateInfo{
120 | {
121 | Title: "update34-1",
122 | },
123 | },
124 | },
125 | },
126 | target: map[string]*models.Updates{
127 | "35": {
128 | UpdateList: []models.UpdateInfo{
129 | {
130 | Title: "update35-module-1",
131 | },
132 | },
133 | },
134 | "34": {
135 | UpdateList: []models.UpdateInfo{
136 | {
137 | Title: "update34-module-1",
138 | },
139 | },
140 | },
141 | },
142 | want: map[string]*models.Updates{
143 | "35": {
144 | UpdateList: []models.UpdateInfo{
145 | {
146 | Title: "update35-1",
147 | },
148 | {
149 | Title: "update35-2",
150 | },
151 | {
152 | Title: "update35-module-1",
153 | },
154 | },
155 | },
156 | "34": {
157 | UpdateList: []models.UpdateInfo{
158 | {
159 | Title: "update34-1",
160 | },
161 | {
162 | Title: "update34-module-1",
163 | },
164 | },
165 | },
166 | },
167 | },
168 | {
169 | name: "no panic when some version is missing",
170 | source: map[string]*models.Updates{
171 | "35": {
172 | UpdateList: []models.UpdateInfo{
173 | {
174 | Title: "update35-1",
175 | },
176 | {
177 | Title: "update35-2",
178 | },
179 | },
180 | },
181 | "34": {
182 | UpdateList: []models.UpdateInfo{
183 | {
184 | Title: "update34-1",
185 | },
186 | },
187 | },
188 | },
189 | target: map[string]*models.Updates{
190 | "35": {
191 | UpdateList: []models.UpdateInfo{
192 | {
193 | Title: "update35-module-1",
194 | },
195 | },
196 | },
197 | },
198 | want: map[string]*models.Updates{
199 | "35": {
200 | UpdateList: []models.UpdateInfo{
201 | {
202 | Title: "update35-1",
203 | },
204 | {
205 | Title: "update35-2",
206 | },
207 | {
208 | Title: "update35-module-1",
209 | },
210 | },
211 | },
212 | "34": {
213 | UpdateList: []models.UpdateInfo{
214 | {
215 | Title: "update34-1",
216 | },
217 | },
218 | },
219 | },
220 | },
221 | {
222 | name: "target is nil",
223 | source: map[string]*models.Updates{
224 | "39": {
225 | UpdateList: []models.UpdateInfo{
226 | {
227 | Title: "update39",
228 | },
229 | },
230 | },
231 | },
232 | target: nil,
233 | want: map[string]*models.Updates{
234 | "39": {
235 | UpdateList: []models.UpdateInfo{
236 | {
237 | Title: "update39",
238 | },
239 | },
240 | },
241 | },
242 | },
243 | }
244 | for _, tt := range tests {
245 | t.Run(tt.name, func(t *testing.T) {
246 | got := mergeUpdates(tt.source, tt.target)
247 | if diff := cmp.Diff(got, tt.want); diff != "" {
248 | t.Errorf("(-got +want):\n%s", diff)
249 | }
250 | })
251 | }
252 | }
253 |
254 | func TestUniquePackages(t *testing.T) {
255 | opt := cmpopts.SortSlices((func(x, y models.Package) bool { return strings.Compare(x.Filename, y.Filename) > 0 }))
256 | tests := []struct {
257 | name string
258 | in []models.Package
259 | want []models.Package
260 | }{
261 | {
262 | name: "normal",
263 | in: []models.Package{
264 | {Filename: "package1"},
265 | {Filename: "package2"},
266 | {Filename: "package2"},
267 | {Filename: "package3"},
268 | {Filename: "package3"},
269 | {Filename: "package3"},
270 | },
271 | want: []models.Package{
272 | {Filename: "package1"},
273 | {Filename: "package2"},
274 | {Filename: "package3"},
275 | },
276 | },
277 | {
278 | name: "no panic when it is blank",
279 | in: []models.Package{},
280 | want: []models.Package{},
281 | },
282 | }
283 | for _, tt := range tests {
284 | t.Run(tt.name, func(t *testing.T) {
285 | got := uniquePackages(tt.in)
286 | if diff := cmp.Diff(got, tt.want, opt); diff != "" {
287 | t.Errorf("(-got +want):\n%s", diff)
288 | }
289 | })
290 | }
291 | }
292 |
--------------------------------------------------------------------------------
/fetcher/amazon/amazon.go:
--------------------------------------------------------------------------------
1 | package amazon
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "compress/gzip"
7 | "encoding/json"
8 | "encoding/xml"
9 | "errors"
10 | "fmt"
11 | "net/url"
12 | "path"
13 |
14 | "github.com/inconshreveable/log15"
15 | "golang.org/x/xerrors"
16 |
17 | "github.com/vulsio/goval-dictionary/fetcher/util"
18 | models "github.com/vulsio/goval-dictionary/models/amazon"
19 | )
20 |
21 | // updateinfo for x86_64 also contains information for aarch64
22 |
23 | type mirror struct {
24 | core string
25 | extra string
26 | livepatch string
27 | }
28 |
29 | var mirrors = map[string]mirror{
30 | "1": {core: "http://repo.us-west-2.amazonaws.com/2018.03/updates/x86_64/mirror.list"},
31 | "2": {
32 | core: "https://cdn.amazonlinux.com/2/core/latest/x86_64/mirror.list",
33 | extra: "http://amazonlinux.default.amazonaws.com/2/extras-catalog.json",
34 | },
35 | "2022": {
36 | core: "https://cdn.amazonlinux.com/al2022/core/mirrors/latest/x86_64/mirror.list",
37 | },
38 | "2023": {
39 | core: "https://cdn.amazonlinux.com/al2023/core/mirrors/latest/x86_64/mirror.list",
40 | livepatch: "https://cdn.amazonlinux.com/al2023/kernel-livepatch/mirrors/latest/x86_64/mirror.list",
41 | },
42 | }
43 |
44 | var errNoUpdateInfo = xerrors.New("No updateinfo field in the repomd")
45 |
46 | // FetchFiles fetch from Amazon ALAS
47 | func FetchFiles(versions []string) (map[string]*models.Updates, error) {
48 | m := map[string]*models.Updates{}
49 | for _, v := range versions {
50 | switch v {
51 | case "1", "2022":
52 | us, err := fetchUpdateInfoAmazonLinux(mirrors[v].core)
53 | if err != nil {
54 | return nil, xerrors.Errorf("Failed to fetch Amazon Linux %s UpdateInfo. err: %w", v, err)
55 | }
56 | m[v] = us
57 | case "2":
58 | updates, err := fetchUpdateInfoAmazonLinux(mirrors[v].core)
59 | if err != nil {
60 | return nil, xerrors.Errorf("Failed to fetch Amazon Linux %s UpdateInfo. err: %w", v, err)
61 | }
62 |
63 | rs, err := util.FetchFeedFiles([]util.FetchRequest{{URL: mirrors[v].extra, MIMEType: util.MIMETypeJSON}})
64 | if err != nil || len(rs) != 1 {
65 | return nil, xerrors.Errorf("Failed to fetch extras-catalog.json for Amazon Linux 2. url: %s, err: %w", mirrors[v].extra, err)
66 | }
67 |
68 | var catalog extrasCatalog
69 | if err := json.Unmarshal(rs[0].Body, &catalog); err != nil {
70 | return nil, xerrors.Errorf("Failed to unmarshal extras-catalog.json for Amazon Linux 2. err: %w", err)
71 | }
72 |
73 | for _, t := range catalog.Topics {
74 | us, err := fetchUpdateInfoAmazonLinux(fmt.Sprintf("https://cdn.amazonlinux.com/2/extras/%s/latest/x86_64/mirror.list", t.N))
75 | if err != nil {
76 | if errors.Is(err, errNoUpdateInfo) {
77 | continue
78 | }
79 | return nil, xerrors.Errorf("Failed to fetch Amazon Linux 2 %s updateinfo. err: %w", t.N, err)
80 | }
81 | for _, u := range us.UpdateList {
82 | u.Repository = fmt.Sprintf("amzn2extra-%s", t.N)
83 | updates.UpdateList = append(updates.UpdateList, u)
84 | }
85 | }
86 |
87 | m[v] = updates
88 | case "2023":
89 | updates, err := fetchUpdateInfoAmazonLinux(mirrors[v].core)
90 | if err != nil {
91 | return nil, xerrors.Errorf("Failed to fetch Amazon Linux %s UpdateInfo. err: %w", v, err)
92 | }
93 |
94 | us, err := fetchUpdateInfoAmazonLinux(mirrors[v].livepatch)
95 | if err != nil {
96 | return nil, xerrors.Errorf("Failed to fetch Amazon Linux %s Kernel Livepatch UpdateInfo. err: %w", v, err)
97 | }
98 |
99 | for _, u := range us.UpdateList {
100 | u.Repository = "kernel-livepatch"
101 | updates.UpdateList = append(updates.UpdateList, u)
102 | }
103 |
104 | m[v] = updates
105 | default:
106 | log15.Warn("Skip unknown amazon.", "version", v)
107 | }
108 | }
109 | return m, nil
110 | }
111 |
112 | func fetchUpdateInfoAmazonLinux(mirrorListURL string) (uinfo *models.Updates, err error) {
113 | results, err := util.FetchFeedFiles([]util.FetchRequest{{URL: mirrorListURL, MIMEType: util.MIMETypeXML}})
114 | if err != nil || len(results) != 1 {
115 | return nil, xerrors.Errorf("Failed to fetch mirror list files. err: %w", err)
116 | }
117 |
118 | mirrors := []string{}
119 | for _, r := range results {
120 | scanner := bufio.NewScanner(bytes.NewReader(r.Body))
121 | for scanner.Scan() {
122 | mirrors = append(mirrors, scanner.Text())
123 | }
124 | }
125 |
126 | uinfoURLs, err := fetchUpdateInfoURL(mirrors)
127 | if err != nil {
128 | return nil, xerrors.Errorf("Failed to fetch updateInfo URL. err: %w", err)
129 | }
130 | for _, url := range uinfoURLs {
131 | uinfo, err = fetchUpdateInfo(url)
132 | if err != nil {
133 | log15.Warn("Failed to fetch updateinfo. continue with other mirror", "err", err)
134 | continue
135 | }
136 | return uinfo, nil
137 | }
138 | return nil, xerrors.New("Failed to fetch updateinfo")
139 | }
140 |
141 | // FetchUpdateInfoURL fetches update info urls for AmazonLinux1 ,Amazon Linux2 and Amazon Linux2022.
142 | func fetchUpdateInfoURL(mirrors []string) (updateInfoURLs []string, err error) {
143 | reqs := []util.FetchRequest{}
144 | for _, mirror := range mirrors {
145 | u, err := url.Parse(mirror)
146 | if err != nil {
147 | return nil, err
148 | }
149 | u.Path = path.Join(u.Path, "/repodata/repomd.xml")
150 | reqs = append(reqs, util.FetchRequest{
151 | Target: mirror, // base URL of the mirror site
152 | URL: u.String(),
153 | MIMEType: util.MIMETypeXML,
154 | })
155 | }
156 |
157 | results, err := util.FetchFeedFiles(reqs)
158 | if err != nil {
159 | log15.Warn("Some errors occurred while fetching repomd", "err", err)
160 | }
161 | if len(results) == 0 {
162 | return nil, xerrors.Errorf("Failed to fetch repomd.xml. URLs: %s", mirrors)
163 | }
164 |
165 | for _, r := range results {
166 | var repoMd repoMd
167 | if err := xml.NewDecoder(bytes.NewBuffer(r.Body)).Decode(&repoMd); err != nil {
168 | log15.Warn("Failed to decode repomd. Trying another mirror", "err", err)
169 | continue
170 | }
171 |
172 | for _, repo := range repoMd.RepoList {
173 | if repo.Type == "updateinfo" {
174 | u, err := url.Parse(r.Target)
175 | if err != nil {
176 | return nil, err
177 | }
178 | u.Path = path.Join(u.Path, repo.Location.Href)
179 | updateInfoURLs = append(updateInfoURLs, u.String())
180 | break
181 | }
182 | }
183 | }
184 | if len(updateInfoURLs) == 0 {
185 | return nil, errNoUpdateInfo
186 | }
187 | return updateInfoURLs, nil
188 | }
189 |
190 | func fetchUpdateInfo(url string) (*models.Updates, error) {
191 | results, err := util.FetchFeedFiles([]util.FetchRequest{{URL: url, MIMEType: util.MIMETypeXML}})
192 | if err != nil || len(results) != 1 {
193 | return nil, xerrors.Errorf("Failed to fetch updateInfo. err: %w", err)
194 | }
195 | r, err := gzip.NewReader(bytes.NewBuffer(results[0].Body))
196 | if err != nil {
197 | return nil, xerrors.Errorf("Failed to decompress updateInfo. err: %w", err)
198 | }
199 | defer r.Close()
200 |
201 | var updateInfo models.Updates
202 | if err := xml.NewDecoder(r).Decode(&updateInfo); err != nil {
203 | return nil, err
204 | }
205 | for i, alas := range updateInfo.UpdateList {
206 | cveIDs := []string{}
207 | for _, ref := range alas.References {
208 | if ref.Type == "cve" {
209 | cveIDs = append(cveIDs, ref.ID)
210 | }
211 | }
212 | updateInfo.UpdateList[i].CVEIDs = cveIDs
213 | }
214 | return &updateInfo, nil
215 | }
216 |
--------------------------------------------------------------------------------
/models/redhat/redhat_test.go:
--------------------------------------------------------------------------------
1 | package redhat
2 |
3 | import (
4 | "reflect"
5 | "sort"
6 | "testing"
7 |
8 | "github.com/k0kubun/pp"
9 |
10 | "github.com/vulsio/goval-dictionary/models"
11 | )
12 |
13 | func TestWalkRedHat(t *testing.T) {
14 | var tests = []struct {
15 | version string
16 | cri Criteria
17 | expected []models.Package
18 | }{
19 | {
20 | version: "6",
21 | cri: Criteria{
22 | Criterions: []Criterion{
23 | {Comment: "kernel-headers is earlier than 0:2.6.32-71.7.1.el6"},
24 | },
25 | },
26 | expected: []models.Package{
27 | {
28 | Name: "kernel-headers",
29 | Version: "0:2.6.32-71.7.1.el6",
30 | },
31 | },
32 | },
33 | {
34 | version: "6",
35 | cri: Criteria{
36 | Criterias: []Criteria{
37 | {
38 | Criterions: []Criterion{
39 | {Comment: "kernel-headers is earlier than 0:2.6.32-71.7.1.el6"},
40 | {Comment: "kernel-headers is signed with Red Hat redhatrelease2 key"},
41 | },
42 | },
43 | },
44 | Criterions: []Criterion{
45 | {Comment: "kernel-kdump is signed with Red Hat redhatrelease2 key"},
46 | {Comment: "kernel-kdump is earlier than 0:2.6.32-71.7.1.el6"},
47 | },
48 | },
49 | expected: []models.Package{
50 | {
51 | Name: "kernel-headers",
52 | Version: "0:2.6.32-71.7.1.el6",
53 | },
54 | {
55 | Name: "kernel-kdump",
56 | Version: "0:2.6.32-71.7.1.el6",
57 | },
58 | },
59 | },
60 | {
61 | version: "6",
62 | cri: Criteria{
63 | Criterias: []Criteria{
64 | {
65 | Criterions: []Criterion{
66 | {Comment: "bzip2 is earlier than 0:1.0.5-7.el6_0"},
67 | {Comment: "bzip2 is signed with Red Hat redhatrelease2 key"},
68 | },
69 |
70 | Criterias: []Criteria{
71 | {
72 | Criterions: []Criterion{
73 | {Comment: "samba-domainjoin-gui is earlier than 0:3.5.4-68.el6_0.1"},
74 | {Comment: "samba-domainjoin-gui is signed with Red Hat redhatrelease2 key"},
75 | },
76 | },
77 | },
78 | },
79 | {
80 | Criterions: []Criterion{
81 | {Comment: "poppler-qt4 is signed with Red Hat redhatrelease2 key"},
82 | {Comment: "poppler-qt4 is earlier than 0:0.12.4-3.el6_0.1"},
83 | },
84 | },
85 | },
86 | Criterions: []Criterion{
87 | {Comment: "kernel-kdump is earlier than 0:2.6.32-71.7.1.el6"},
88 | {Comment: "kernel-kdump is signed with Red Hat redhatrelease2 key"},
89 | },
90 | },
91 | expected: []models.Package{
92 | {
93 | Name: "bzip2",
94 | Version: "0:1.0.5-7.el6_0",
95 | },
96 | {
97 | Name: "samba-domainjoin-gui",
98 | Version: "0:3.5.4-68.el6_0.1",
99 | },
100 | {
101 | Name: "poppler-qt4",
102 | Version: "0:0.12.4-3.el6_0.1",
103 | },
104 | {
105 | Name: "kernel-kdump",
106 | Version: "0:2.6.32-71.7.1.el6",
107 | },
108 | },
109 | },
110 | {
111 | version: "6",
112 | cri: Criteria{
113 | Criterias: []Criteria{
114 | {
115 | Criterias: []Criteria{
116 | {
117 | Criterions: []Criterion{
118 | {Comment: "rpm is earlier than 0:4.8.0-12.el6_0.2"},
119 | },
120 | },
121 | },
122 | Criterions: []Criterion{
123 | {Comment: "Red Hat Enterprise Linux 6 is installed"},
124 | },
125 | },
126 | {
127 | Criterias: []Criteria{
128 | {
129 | Criterions: []Criterion{
130 | {Comment: "rpm is earlier than 0:4.8.0-19.el6_2.1"},
131 | },
132 | },
133 | },
134 | Criterions: []Criterion{
135 | {Comment: "Red Hat Enterprise Linux 6 is installed"},
136 | },
137 | },
138 | },
139 | },
140 | expected: []models.Package{
141 | {
142 | Name: "rpm",
143 | Version: "0:4.8.0-19.el6_2.1",
144 | },
145 | },
146 | },
147 | {
148 | version: "6",
149 | cri: Criteria{
150 | Criterias: []Criteria{
151 | {
152 | Criterias: []Criteria{
153 | {
154 | Criterions: []Criterion{
155 | {Comment: "rpm is earlier than 0:4.8.0-12.el6_0.2"},
156 | },
157 | },
158 | },
159 | Criterions: []Criterion{
160 | {Comment: "Red Hat Enterprise Linux 6 is installed"},
161 | },
162 | },
163 | {
164 | Criterias: []Criteria{
165 | {
166 | Criterions: []Criterion{
167 | {Comment: "rpm is earlier than 0:4.8.0-19.el7_0.1"},
168 | },
169 | },
170 | },
171 | Criterions: []Criterion{
172 | {Comment: "Red Hat Enterprise Linux 7 is installed"},
173 | },
174 | },
175 | },
176 | },
177 | expected: []models.Package{
178 | {
179 | Name: "rpm",
180 | Version: "0:4.8.0-12.el6_0.2",
181 | },
182 | },
183 | },
184 | {
185 | version: "8",
186 | cri: Criteria{
187 | Criterias: []Criteria{
188 | {
189 | Criterions: []Criterion{
190 | {Comment: "ruby is earlier than 0:2.5.5-105.module+el8.1.0+3656+f80bfa1d"},
191 | {Comment: "ruby is signed with Red Hat redhatrelease2 key"},
192 | },
193 | },
194 | },
195 | Criterions: []Criterion{
196 | {Comment: "Red Hat Enterprise Linux 8 is installed"},
197 | {Comment: "Module ruby:2.5 is enabled"},
198 | },
199 | },
200 | expected: []models.Package{
201 | {
202 | Name: "ruby",
203 | Version: "0:2.5.5-105.module+el8.1.0+3656+f80bfa1d",
204 | ModularityLabel: "ruby:2.5",
205 | },
206 | },
207 | },
208 | {
209 | version: "8",
210 | cri: Criteria{
211 | Criterias: []Criteria{
212 | {
213 | Criterias: []Criteria{
214 | {
215 | Criterions: []Criterion{
216 | {Comment: "libvirt is earlier than 0:4.5.0-42.module+el8.2.0+6024+15a2423f"},
217 | {Comment: "libvirt is signed with Red Hat redhatrelease2 key"},
218 | },
219 | },
220 | },
221 | Criterions: []Criterion{
222 | {Comment: "Module virt:rhel is enabled"},
223 | },
224 | },
225 | {
226 | Criterias: []Criteria{
227 | {
228 | Criterions: []Criterion{
229 | {Comment: "libvirt is earlier than 0:4.5.0-42.module+el8.2.0+6024+15a2423f"},
230 | {Comment: "libvirt is signed with Red Hat redhatrelease2 key"},
231 | },
232 | },
233 | },
234 | Criterions: []Criterion{
235 | {Comment: "Module virt-devel:rhel is enabled"},
236 | },
237 | },
238 | },
239 | Criterions: []Criterion{
240 | {Comment: "Red Hat Enterprise Linux 8 is installed"},
241 | },
242 | },
243 | expected: []models.Package{
244 | {
245 | Name: "libvirt",
246 | Version: "0:4.5.0-42.module+el8.2.0+6024+15a2423f",
247 | ModularityLabel: "virt:rhel",
248 | },
249 | {
250 | Name: "libvirt",
251 | Version: "0:4.5.0-42.module+el8.2.0+6024+15a2423f",
252 | ModularityLabel: "virt-devel:rhel",
253 | },
254 | },
255 | },
256 | {
257 | version: "8",
258 | cri: Criteria{
259 | Criterias: []Criteria{
260 | {
261 | Criterias: []Criteria{
262 | {
263 | Criterias: []Criteria{
264 | {
265 | Criterias: []Criteria{
266 | {
267 | Criterions: []Criterion{
268 | {Comment: "python2 is installed"},
269 | {Comment: "python2 is signed with Red Hat redhatrelease2 key"},
270 | },
271 | },
272 | },
273 | },
274 | },
275 | Criterions: []Criterion{
276 | {Comment: "Module inkscape:flatpak is enabled"},
277 | },
278 | },
279 | },
280 | Criterions: []Criterion{
281 | {Comment: "Red Hat Enterprise Linux 8 is installed"},
282 | {Comment: "Red Hat CoreOS 4 is installed"},
283 | },
284 | },
285 | },
286 | Criterions: []Criterion{
287 | {Comment: "Red Hat Enterprise Linux must be installed"},
288 | },
289 | },
290 | expected: []models.Package{
291 | {
292 | Name: "python2",
293 | ModularityLabel: "inkscape:flatpak",
294 | NotFixedYet: true,
295 | },
296 | },
297 | },
298 | }
299 |
300 | for i, tt := range tests {
301 | actual := collectRedHatPacks(tt.version, tt.cri)
302 | sort.Slice(actual, func(i, j int) bool {
303 | if actual[i].Name == actual[j].Name {
304 | return actual[i].ModularityLabel < actual[j].ModularityLabel
305 | }
306 | return actual[i].Name < actual[j].Name
307 | })
308 | sort.Slice(tt.expected, func(i, j int) bool {
309 | if tt.expected[i].Name == tt.expected[j].Name {
310 | return tt.expected[i].ModularityLabel < tt.expected[j].ModularityLabel
311 | }
312 | return tt.expected[i].Name < tt.expected[j].Name
313 | })
314 |
315 | if !reflect.DeepEqual(tt.expected, actual) {
316 | e := pp.Sprintf("%v", tt.expected)
317 | a := pp.Sprintf("%v", actual)
318 | t.Errorf("[%d]: expected: %s\n, actual: %s\n", i, e, a)
319 | }
320 | }
321 | }
322 |
--------------------------------------------------------------------------------
/db/db_test.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 |
7 | "github.com/vulsio/goval-dictionary/config"
8 | "github.com/vulsio/goval-dictionary/models"
9 | )
10 |
11 | func Test_formatFamilyAndOSVer(t *testing.T) {
12 | type args struct {
13 | family string
14 | osVer string
15 | }
16 | tests := []struct {
17 | in args
18 | expected args
19 | wantErr string
20 | }{
21 | {
22 | in: args{
23 | family: config.Debian,
24 | osVer: "11",
25 | },
26 | expected: args{
27 | family: config.Debian,
28 | osVer: "11",
29 | },
30 | },
31 | {
32 | in: args{
33 | family: config.Debian,
34 | osVer: "11.1",
35 | },
36 | expected: args{
37 | family: config.Debian,
38 | osVer: "11",
39 | },
40 | },
41 | {
42 | in: args{
43 | family: config.Ubuntu,
44 | osVer: "20.04",
45 | },
46 | expected: args{
47 | family: config.Ubuntu,
48 | osVer: "20.04",
49 | },
50 | },
51 | {
52 | in: args{
53 | family: config.Ubuntu,
54 | osVer: "20.04.3",
55 | },
56 | expected: args{
57 | family: config.Ubuntu,
58 | osVer: "20.04",
59 | },
60 | },
61 | {
62 | in: args{
63 | family: config.Raspbian,
64 | osVer: "10",
65 | },
66 | expected: args{
67 | family: config.Debian,
68 | osVer: "10",
69 | },
70 | },
71 | {
72 | in: args{
73 | family: config.Raspbian,
74 | osVer: "10.1",
75 | },
76 | expected: args{
77 | family: config.Debian,
78 | osVer: "10",
79 | },
80 | },
81 | {
82 | in: args{
83 | family: config.RedHat,
84 | osVer: "8",
85 | },
86 | expected: args{
87 | family: config.RedHat,
88 | osVer: "8",
89 | },
90 | },
91 | {
92 | in: args{
93 | family: config.RedHat,
94 | osVer: "8.4",
95 | },
96 | expected: args{
97 | family: config.RedHat,
98 | osVer: "8",
99 | },
100 | },
101 | {
102 | in: args{
103 | family: config.CentOS,
104 | osVer: "8",
105 | },
106 | expected: args{
107 | family: config.RedHat,
108 | osVer: "8",
109 | },
110 | },
111 | {
112 | in: args{
113 | family: config.CentOS,
114 | osVer: "8.4",
115 | },
116 | expected: args{
117 | family: config.RedHat,
118 | osVer: "8",
119 | },
120 | },
121 | {
122 | in: args{
123 | family: config.Oracle,
124 | osVer: "8",
125 | },
126 | expected: args{
127 | family: config.Oracle,
128 | osVer: "8",
129 | },
130 | },
131 | {
132 | in: args{
133 | family: config.Oracle,
134 | osVer: "8.4",
135 | },
136 | expected: args{
137 | family: config.Oracle,
138 | osVer: "8",
139 | },
140 | },
141 | {
142 | in: args{
143 | family: config.Amazon,
144 | osVer: "1",
145 | },
146 | expected: args{
147 | family: config.Amazon,
148 | osVer: "1",
149 | },
150 | },
151 | {
152 | in: args{
153 | family: config.Amazon,
154 | osVer: "2",
155 | },
156 | expected: args{
157 | family: config.Amazon,
158 | osVer: "2",
159 | },
160 | },
161 | {
162 | in: args{
163 | family: config.Amazon,
164 | osVer: "2022",
165 | },
166 | expected: args{
167 | family: config.Amazon,
168 | osVer: "2022",
169 | },
170 | },
171 | {
172 | in: args{
173 | family: config.Amazon,
174 | osVer: "2023",
175 | },
176 | expected: args{
177 | family: config.Amazon,
178 | osVer: "2023",
179 | },
180 | },
181 | {
182 | in: args{
183 | family: config.Alpine,
184 | osVer: "3.15",
185 | },
186 | expected: args{
187 | family: config.Alpine,
188 | osVer: "3.15",
189 | },
190 | },
191 | {
192 | in: args{
193 | family: config.Alpine,
194 | osVer: "3.14",
195 | },
196 | expected: args{
197 | family: config.Alpine,
198 | osVer: "3.14",
199 | },
200 | },
201 | {
202 | in: args{
203 | family: config.Alpine,
204 | osVer: "3.14.1",
205 | },
206 | expected: args{
207 | family: config.Alpine,
208 | osVer: "3.14",
209 | },
210 | },
211 | {
212 | in: args{
213 | family: config.OpenSUSE,
214 | osVer: "10.2",
215 | },
216 | expected: args{
217 | family: config.OpenSUSE,
218 | osVer: "10.2",
219 | },
220 | },
221 | {
222 | in: args{
223 | family: config.OpenSUSE,
224 | osVer: "tumbleweed",
225 | },
226 | expected: args{
227 | family: config.OpenSUSE,
228 | osVer: "tumbleweed",
229 | },
230 | },
231 | {
232 | in: args{
233 | family: config.OpenSUSELeap,
234 | osVer: "15.3",
235 | },
236 | expected: args{
237 | family: config.OpenSUSELeap,
238 | osVer: "15.3",
239 | },
240 | },
241 | {
242 | in: args{
243 | family: config.SUSEEnterpriseServer,
244 | osVer: "15",
245 | },
246 | expected: args{
247 | family: config.SUSEEnterpriseServer,
248 | osVer: "15",
249 | },
250 | },
251 | {
252 | in: args{
253 | family: config.SUSEEnterpriseDesktop,
254 | osVer: "15",
255 | },
256 | expected: args{
257 | family: config.SUSEEnterpriseDesktop,
258 | osVer: "15",
259 | },
260 | },
261 | {
262 | in: args{
263 | family: config.Fedora,
264 | osVer: "35",
265 | },
266 | expected: args{
267 | family: config.Fedora,
268 | osVer: "35",
269 | },
270 | },
271 | {
272 | in: args{
273 | family: "unknown",
274 | osVer: "unknown",
275 | },
276 | wantErr: "Failed to detect family. err: unknown os family(unknown)",
277 | },
278 | }
279 | for i, tt := range tests {
280 | family, osVer, err := formatFamilyAndOSVer(tt.in.family, tt.in.osVer)
281 | if tt.wantErr != "" {
282 | if err.Error() != tt.wantErr {
283 | t.Errorf("[%d] formatFamilyAndOSVer expected: %#v\n actual: %#v\n", i, tt.wantErr, err)
284 | }
285 | }
286 |
287 | if family != tt.expected.family || osVer != tt.expected.osVer {
288 | t.Errorf("[%d] formatFamilyAndOSVer expected: %#v\n actual: %#v\n", i, tt.expected, args{family: family, osVer: osVer})
289 | }
290 | }
291 | }
292 |
293 | func Test_filterByRedHatMajor(t *testing.T) {
294 | type args struct {
295 | packs []models.Package
296 | majorVer string
297 | }
298 | tests := []struct {
299 | in args
300 | expected []models.Package
301 | }{
302 | {
303 | in: args{
304 | packs: []models.Package{
305 | {
306 | Name: "name-el7",
307 | Version: "0:0.0.1-0.0.1.el7",
308 | },
309 | {
310 | Name: "name-el8",
311 | Version: "0:0.0.1-0.0.1.el8",
312 | },
313 | {
314 | Name: "name-module+el7",
315 | Version: "0:0.1.1-1.module+el7.1.0+7785+0ea9f177",
316 | },
317 | {
318 | Name: "name-module+el8",
319 | Version: "0:0.1.1-1.module+el8.1.0+7785+0ea9f177",
320 | },
321 | },
322 | majorVer: "8",
323 | },
324 | expected: []models.Package{
325 | {
326 | Name: "name-el8",
327 | Version: "0:0.0.1-0.0.1.el8",
328 | },
329 | {
330 | Name: "name-module+el8",
331 | Version: "0:0.1.1-1.module+el8.1.0+7785+0ea9f177",
332 | },
333 | },
334 | },
335 | {
336 | in: args{
337 | packs: []models.Package{
338 | {
339 | Name: "name-el7",
340 | Version: "0:0.0.1-0.0.1.el7",
341 | },
342 | {
343 | Name: "name-el8",
344 | Version: "0:0.0.1-0.0.1.el8",
345 | },
346 | {
347 | Name: "name-module+el7",
348 | Version: "0:0.1.1-1.module+el7.1.0+7785+0ea9f177",
349 | },
350 | {
351 | Name: "name-module+el8",
352 | Version: "0:0.1.1-1.module+el8.1.0+7785+0ea9f177",
353 | },
354 | },
355 | majorVer: "",
356 | },
357 | expected: []models.Package{
358 | {
359 | Name: "name-el7",
360 | Version: "0:0.0.1-0.0.1.el7",
361 | },
362 | {
363 | Name: "name-el8",
364 | Version: "0:0.0.1-0.0.1.el8",
365 | },
366 | {
367 | Name: "name-module+el7",
368 | Version: "0:0.1.1-1.module+el7.1.0+7785+0ea9f177",
369 | },
370 | {
371 | Name: "name-module+el8",
372 | Version: "0:0.1.1-1.module+el8.1.0+7785+0ea9f177",
373 | },
374 | },
375 | },
376 | {
377 | in: args{
378 | packs: []models.Package{
379 | {
380 | Name: "name-el7",
381 | Version: "0:0.0.1-0.0.1.el7",
382 | },
383 | {
384 | Name: "name-el8",
385 | Version: "0:0.0.1-0.0.1.el8",
386 | },
387 | {
388 | Name: "notfixedyet",
389 | NotFixedYet: true,
390 | },
391 | },
392 | majorVer: "8",
393 | },
394 | expected: []models.Package{
395 | {
396 | Name: "name-el8",
397 | Version: "0:0.0.1-0.0.1.el8",
398 | },
399 | {
400 | Name: "notfixedyet",
401 | NotFixedYet: true,
402 | },
403 | },
404 | },
405 | }
406 |
407 | for i, tt := range tests {
408 | if aout := filterByRedHatMajor(tt.in.packs, tt.in.majorVer); !reflect.DeepEqual(aout, tt.expected) {
409 | t.Errorf("[%d] filterByRedHatMajor expected: %#v\n actual: %#v\n", i, tt.expected, aout)
410 | }
411 | }
412 | }
413 |
--------------------------------------------------------------------------------
/models/redhat/types.go:
--------------------------------------------------------------------------------
1 | package redhat
2 |
3 | import "encoding/xml"
4 |
5 | // Root : root object
6 | type Root struct {
7 | XMLName xml.Name `xml:"oval_definitions"`
8 | Generator Generator `xml:"generator"`
9 | Definitions Definitions `xml:"definitions"`
10 | Tests Tests `xml:"tests"`
11 | Objects Objects `xml:"objects"`
12 | States States `xml:"states"`
13 | }
14 |
15 | // Generator : >generator
16 | type Generator struct {
17 | XMLName xml.Name `xml:"generator"`
18 | ProductName string `xml:"product_name"`
19 | ProductVersion string `xml:"product_version"`
20 | SchemaVersion string `xml:"schema_version"`
21 | Timestamp string `xml:"timestamp"`
22 | }
23 |
24 | // Definitions : >definitions
25 | type Definitions struct {
26 | XMLName xml.Name `xml:"definitions"`
27 | Definitions []Definition `xml:"definition"`
28 | }
29 |
30 | // Definition : >definitions>definition
31 | type Definition struct {
32 | XMLName xml.Name `xml:"definition"`
33 | ID string `xml:"id,attr"`
34 | Class string `xml:"class,attr"`
35 | Title string `xml:"metadata>title"`
36 | Affecteds []Affected `xml:"metadata>affected"`
37 | References []Reference `xml:"metadata>reference"`
38 | Description string `xml:"metadata>description"`
39 | Advisory Advisory `xml:"metadata>advisory"`
40 | Criteria Criteria `xml:"criteria"`
41 | }
42 |
43 | // Criteria : >definitions>definition>criteria
44 | type Criteria struct {
45 | XMLName xml.Name `xml:"criteria"`
46 | Operator string `xml:"operator,attr"`
47 | Criterias []Criteria `xml:"criteria"`
48 | Criterions []Criterion `xml:"criterion"`
49 | }
50 |
51 | // Criterion : >definitions>definition>criteria>*>criterion
52 | type Criterion struct {
53 | XMLName xml.Name `xml:"criterion"`
54 | TestRef string `xml:"test_ref,attr"`
55 | Comment string `xml:"comment,attr"`
56 | }
57 |
58 | // Affected : >definitions>definition>metadata>affected
59 | type Affected struct {
60 | XMLName xml.Name `xml:"affected"`
61 | Family string `xml:"family,attr"`
62 | Platforms []string `xml:"platform"`
63 | }
64 |
65 | // Reference : >definitions>definition>metadata>reference
66 | type Reference struct {
67 | XMLName xml.Name `xml:"reference"`
68 | Source string `xml:"source,attr"`
69 | RefID string `xml:"ref_id,attr"`
70 | RefURL string `xml:"ref_url,attr"`
71 | }
72 |
73 | // Advisory : >definitions>definition>metadata>advisory
74 | // RedHat and Ubuntu OVAL
75 | type Advisory struct {
76 | XMLName xml.Name `xml:"advisory"`
77 | Severity string `xml:"severity"`
78 | Rights string `xml:"rights"`
79 | Cves []Cve `xml:"cve"`
80 | Bugzillas []Bugzilla `xml:"bugzilla"`
81 | AffectedCPEList []string `xml:"affected_cpe_list>cpe"`
82 | Affected AffectedPkgs `xml:"affected"`
83 | Issued struct {
84 | Date string `xml:"date,attr"`
85 | } `xml:"issued"`
86 | Updated struct {
87 | Date string `xml:"date,attr"`
88 | } `xml:"updated"`
89 | }
90 |
91 | // Cve : >definitions>definition>metadata>advisory>cve
92 | type Cve struct {
93 | XMLName xml.Name `xml:"cve"`
94 | CveID string `xml:",chardata"`
95 | Cvss2 string `xml:"cvss2,attr"`
96 | Cvss3 string `xml:"cvss3,attr"`
97 | Cwe string `xml:"cwe,attr"`
98 | Impact string `xml:"impact,attr"`
99 | Href string `xml:"href,attr"`
100 | Public string `xml:"public,attr"`
101 | }
102 |
103 | // Bugzilla : >definitions>definition>metadata>advisory>bugzilla
104 | type Bugzilla struct {
105 | XMLName xml.Name `xml:"bugzilla"`
106 | ID string `xml:"id,attr"`
107 | URL string `xml:"href,attr"`
108 | Title string `xml:",chardata"`
109 | }
110 |
111 | // AffectedPkgs : >definitions>definition>metadata>advisory>affected
112 | type AffectedPkgs struct {
113 | Resolution []struct {
114 | State string `xml:"state,attr"`
115 | Component []string `xml:"component"`
116 | } `xml:"resolution"`
117 | }
118 |
119 | // Tests : >tests
120 | type Tests struct {
121 | XMLName xml.Name `xml:"tests"`
122 | RpminfoTests []RpminfoTest `xml:"rpminfo_test"`
123 | RpmverifyfileTests []RpmverifyfileTest `xml:"rpmverifyfile_test"`
124 | Textfilecontent54Tests []Textfilecontent54Test `xml:"textfilecontent54_test"`
125 | UnameTests []UnameTest `xml:"uname_test"`
126 | }
127 |
128 | // RpminfoTest : >tests>rpminfo_test
129 | type RpminfoTest struct {
130 | Check string `xml:"check,attr"`
131 | Comment string `xml:"comment,attr"`
132 | ID string `xml:"id,attr"`
133 | Version string `xml:"version,attr"`
134 | CheckExistence string `xml:"check_existence,attr"`
135 | Object ObjectRef `xml:"object"`
136 | State StateRef `xml:"state"`
137 | }
138 |
139 | // RpmverifyfileTest : tests>rpmverifyfile_test
140 | type RpmverifyfileTest struct {
141 | Check string `xml:"check,attr"`
142 | Comment string `xml:"comment,attr"`
143 | ID string `xml:"id,attr"`
144 | Version string `xml:"version,attr"`
145 | Object ObjectRef `xml:"object"`
146 | State StateRef `xml:"state"`
147 | }
148 |
149 | // Textfilecontent54Test : tests>textfilecontent54_test
150 | type Textfilecontent54Test struct {
151 | Check string `xml:"check,attr"`
152 | Comment string `xml:"comment,attr"`
153 | ID string `xml:"id,attr"`
154 | Version string `xml:"version,attr"`
155 | Object ObjectRef `xml:"object"`
156 | State StateRef `xml:"state"`
157 | }
158 |
159 | // UnameTest : tests>uname_test
160 | type UnameTest struct {
161 | Check string `xml:"check,attr"`
162 | Comment string `xml:"comment,attr"`
163 | ID string `xml:"id,attr"`
164 | Version string `xml:"version,attr"`
165 | Object ObjectRef `xml:"object"`
166 | State StateRef `xml:"state"`
167 | }
168 |
169 | // ObjectRef :
170 | // >tests>rpminfo_test>object-object_ref
171 | // >tests>rpmverifyfile_test>object-object_ref
172 | // >tests>textfilecontent54_test>object-object_ref
173 | // >tests>uname_test>object-object_ref
174 | type ObjectRef struct {
175 | XMLName xml.Name `xml:"object"`
176 | Text string `xml:",chardata"`
177 | ObjectRef string `xml:"object_ref,attr"`
178 | }
179 |
180 | // StateRef :
181 | // >tests>rpminfo_test>state-state_ref
182 | // >tests>rpmverifyfile_test>state-state_ref
183 | // >tests>textfilecontent54_test>state-state_ref
184 | // >tests>uname_test>state-state_ref
185 | type StateRef struct {
186 | XMLName xml.Name `xml:"state"`
187 | Text string `xml:",chardata"`
188 | StateRef string `xml:"state_ref,attr"`
189 | }
190 |
191 | // Objects : >objects
192 | type Objects struct {
193 | XMLName xml.Name `xml:"objects"`
194 | RpminfoObjects []RpminfoObject `xml:"rpminfo_object"`
195 | RpmverifyfileObjects []RpmverifyfileObject `xml:"rpmverifyfile_object"`
196 | Textfilecontent54Objects []Textfilecontent54Object `xml:"textfilecontent54_object"`
197 | UnameObjects UnameObject `xml:"uname_object"`
198 | }
199 |
200 | // RpminfoObject : >objects>rpminfo_object
201 | type RpminfoObject struct {
202 | ID string `xml:"id,attr"`
203 | Version string `xml:"version,attr"`
204 | Name string `xml:"name"`
205 | }
206 |
207 | // RpmverifyfileObject : >objects>rpmverifyfile_object
208 | type RpmverifyfileObject struct {
209 | ID string `xml:"id,attr"`
210 | AttrVersion string `xml:"version,attr"`
211 | Behaviors struct {
212 | Text string `xml:",chardata"`
213 | Noconfigfiles string `xml:"noconfigfiles,attr"`
214 | Noghostfiles string `xml:"noghostfiles,attr"`
215 | Nogroup string `xml:"nogroup,attr"`
216 | Nolinkto string `xml:"nolinkto,attr"`
217 | Nomd5 string `xml:"nomd5,attr"`
218 | Nomode string `xml:"nomode,attr"`
219 | Nomtime string `xml:"nomtime,attr"`
220 | Nordev string `xml:"nordev,attr"`
221 | Nosize string `xml:"nosize,attr"`
222 | Nouser string `xml:"nouser,attr"`
223 | } `xml:"behaviors"`
224 | Name struct {
225 | Text string `xml:",chardata"`
226 | Operation string `xml:"operation,attr"`
227 | } `xml:"name"`
228 | Epoch struct {
229 | Text string `xml:",chardata"`
230 | Operation string `xml:"operation,attr"`
231 | } `xml:"epoch"`
232 | Version struct {
233 | Text string `xml:",chardata"`
234 | Operation string `xml:"operation,attr"`
235 | } `xml:"version"`
236 | Release struct {
237 | Text string `xml:",chardata"`
238 | Operation string `xml:"operation,attr"`
239 | } `xml:"release"`
240 | Arch struct {
241 | Text string `xml:",chardata"`
242 | Operation string `xml:"operation,attr"`
243 | } `xml:"arch"`
244 | Filepath string `xml:"filepath"`
245 | }
246 |
247 | // Textfilecontent54Object : >objects>textfilecontent54_object
248 | type Textfilecontent54Object struct {
249 | ID string `xml:"id,attr"`
250 | Version string `xml:"version,attr"`
251 | Filepath struct {
252 | Text string `xml:",chardata"`
253 | Datatype string `xml:"datatype,attr"`
254 | } `xml:"filepath"`
255 | Pattern struct {
256 | Text string `xml:",chardata"`
257 | Operation string `xml:"operation,attr"`
258 | } `xml:"pattern"`
259 | Instance struct {
260 | Text string `xml:",chardata"`
261 | Datatype string `xml:"datatype,attr"`
262 | VarRef string `xml:"var_ref,attr"`
263 | } `xml:"instance"`
264 | }
265 |
266 | // UnameObject : >objects>uname_object
267 | type UnameObject struct {
268 | ID string `xml:"id,attr"`
269 | Version string `xml:"version,attr"`
270 | }
271 |
272 | // States : >states
273 | type States struct {
274 | XMLName xml.Name `xml:"states"`
275 | RpminfoStates []RpminfoState `xml:"rpminfo_state"`
276 | RpmverifyfileStates []RpmverifyfileState `xml:"rpmverifyfile_state"`
277 | Textfilecontent54States []Textfilecontent54State `xml:"textfilecontent54_state"`
278 | UnameStates []UnameState `xml:"uname_state"`
279 | }
280 |
281 | // RpminfoState : >states>rpminfo_state
282 | type RpminfoState struct {
283 | ID string `xml:"id,attr"`
284 | Version string `xml:"version,attr"`
285 | Evr struct {
286 | Text string `xml:",chardata"`
287 | Datatype string `xml:"datatype,attr"`
288 | Operation string `xml:"operation,attr"`
289 | } `xml:"evr"`
290 | SignatureKeyid SignatureKeyid `xml:"signature_keyid"`
291 | Arch struct {
292 | Text string `xml:",chardata"`
293 | Datatype string `xml:"datatype,attr"`
294 | Operation string `xml:"operation,attr"`
295 | } `xml:"arch"`
296 | }
297 |
298 | // SignatureKeyid : >states>rpminfo_state>signature_keyid
299 | type SignatureKeyid struct {
300 | Text string `xml:",chardata"`
301 | Operation string `xml:"operation,attr"`
302 | }
303 |
304 | // RpmverifyfileState : >states>rpmverifyfile_state
305 | type RpmverifyfileState struct {
306 | ID string `xml:"id,attr"`
307 | AttrVersion string `xml:"version,attr"`
308 | Name struct {
309 | Text string `xml:",chardata"`
310 | Operation string `xml:"operation,attr"`
311 | } `xml:"name"`
312 | Version struct {
313 | Text string `xml:",chardata"`
314 | Operation string `xml:"operation,attr"`
315 | } `xml:"version"`
316 | }
317 |
318 | // Textfilecontent54State : >states>textfilecontent54_state
319 | type Textfilecontent54State struct {
320 | ID string `xml:"id,attr"`
321 | Version string `xml:"version,attr"`
322 | Text struct {
323 | Text string `xml:",chardata"`
324 | Operation string `xml:"operation,attr"`
325 | } `xml:"text"`
326 | }
327 |
328 | // UnameState : >states>uname_state
329 | type UnameState struct {
330 | ID string `xml:"id,attr"`
331 | Version string `xml:"version,attr"`
332 | OsRelease struct {
333 | Text string `xml:",chardata"`
334 | Operation string `xml:"operation,attr"`
335 | } `xml:"os_release"`
336 | }
337 |
--------------------------------------------------------------------------------