feat: add k8s cve collector (#239)
This commit is contained in:
parent
26dae1a5f7
commit
b98364d3e4
41
.github/workflows/k8s.yml
vendored
Normal file
41
.github/workflows/k8s.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
name: Update vuln-list-k8s repo
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 */6 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update:
|
||||
name: Update vuln-list-k8s
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
VULN_LIST_DIR: "vuln-list-k8s"
|
||||
REPOSITORY_OWNER: ${{ github.repository_owner }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Check out vuln-list-k8s repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ env.REPOSITORY_OWNER }}/${{ env.VULN_LIST_DIR }}
|
||||
token: ${{ secrets.ACCESS_TOKEN }}
|
||||
path: ${{ env.VULN_LIST_DIR }}
|
||||
|
||||
- name: Setup github user email and name
|
||||
run: |
|
||||
git config --global user.email "action@github.com"
|
||||
git config --global user.name "GitHub Action"
|
||||
|
||||
- name: Compile vuln-list-update
|
||||
run: go build -o vuln-list-update .
|
||||
|
||||
- if: always()
|
||||
name: K8s official vulnerability advisory
|
||||
run: ./create_pr.sh k8s
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,3 +16,4 @@ vuln-list-update
|
||||
|
||||
# MacOs
|
||||
.DS_Store
|
||||
.vscode
|
||||
|
46
create_pr.sh
Executable file
46
create_pr.sh
Executable file
@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
|
||||
TARGET=$1
|
||||
|
||||
if [ -z "$TARGET" ]; then
|
||||
echo "target required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
./vuln-list-update -vuln-list-dir "$VULN_LIST_DIR" -target "$TARGET"
|
||||
|
||||
cd "$VULN_LIST_DIR" || exit 1
|
||||
|
||||
if [[ -n $(git status --porcelain) ]]; then
|
||||
# List changed files
|
||||
CHANGED_FILES=$(git ls-files . --exclude-standard --others | grep "CVE")
|
||||
REPO="$REPOSITORY_OWNER/$VULN_LIST_DIR"
|
||||
BASE_BRANCH="main"
|
||||
# Loop through changed files and create PRs
|
||||
for FILE in $CHANGED_FILES; do
|
||||
|
||||
BRANCH_NAME=$(echo "$FILE" | tr / -)
|
||||
PR_TITLE="Update $FILE"
|
||||
PR_BODY="This PR updates $FILE"
|
||||
|
||||
# Check if a PR with the same branch name already exists
|
||||
OPEN_PR_COUNT=$(gh pr list --state open --base $BASE_BRANCH --repo "$REPO" | grep "$FILE" | wc -l)
|
||||
|
||||
if [ "$OPEN_PR_COUNT" != 0 ]; then
|
||||
echo "PR for $FILE already exists, skipping."
|
||||
continue
|
||||
fi
|
||||
|
||||
# Create a new branch and push it
|
||||
git checkout -b "$BRANCH_NAME"
|
||||
echo "$FILE"
|
||||
git add "$FILE"
|
||||
git commit -m "Update $FILE"
|
||||
|
||||
git push origin "$BRANCH_NAME" --force
|
||||
# Create a new pull request using gh
|
||||
gh pr create --base "$BASE_BRANCH" --head "$BRANCH_NAME" --title "$PR_TITLE" --body "$PR_BODY" --repo "$REPO"
|
||||
|
||||
git checkout $BASE_BRANCH
|
||||
done
|
||||
fi
|
4
go.mod
4
go.mod
@ -4,9 +4,12 @@ go 1.20
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.8.0
|
||||
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46
|
||||
github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492
|
||||
github.com/araddon/dateparse v0.0.0-20190426192744-0d74ffceef83
|
||||
github.com/cheggaaa/pb v2.0.7+incompatible
|
||||
github.com/cheggaaa/pb/v3 v3.1.4
|
||||
github.com/goark/go-cvss v1.6.6
|
||||
github.com/hashicorp/go-getter v1.7.2
|
||||
github.com/kylelemons/godebug v1.1.0
|
||||
github.com/mattn/go-jsonpointer v0.0.1
|
||||
@ -35,6 +38,7 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/goark/errs v1.1.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
|
12
go.sum
12
go.sum
@ -198,6 +198,10 @@ github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAU
|
||||
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
|
||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 h1:vmXNl+HDfqqXgr0uY1UgK1GAhps8nbAAtqHNBcgyf+4=
|
||||
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46/go.mod h1:olhPNdiiAAMiSujemd1O/sc6GcyePr23f/6uGKtthNg=
|
||||
github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 h1:rcEG5HI490FF0a7zuvxOxen52ddygCfNVjP0XOCMl+M=
|
||||
github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492/go.mod h1:9Beu8XsUNNfzml7WBf3QmyPToP1wm1Gj/Vc5UJKqTzU=
|
||||
github.com/araddon/dateparse v0.0.0-20190426192744-0d74ffceef83 h1:ukTLOeMC0aVxbJWVg6hOsVJ0VPIo8w++PbNsze/pqF8=
|
||||
github.com/araddon/dateparse v0.0.0-20190426192744-0d74ffceef83/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI=
|
||||
github.com/aws/aws-sdk-go v1.44.122 h1:p6mw01WBaNpbdP2xrisz5tIkcNwzj/HysobNoaAHjgo=
|
||||
@ -225,6 +229,7 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -247,6 +252,10 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/goark/errs v1.1.0 h1:FKnyw4LVyRADIjM8Nj0Up6r0/y5cfADvZAd1E+tthXE=
|
||||
github.com/goark/errs v1.1.0/go.mod h1:TtaPEoadm2mzqzfXdkkfpN2xuniCFm2q4JH+c1qzaqw=
|
||||
github.com/goark/go-cvss v1.6.6 h1:WJFuIWqmAw1Ilb9USv0vuX+nYzOWJp8lIujseJ/y3sU=
|
||||
github.com/goark/go-cvss v1.6.6/go.mod h1:H3qbfUSUlV7XtA3EwWNunvXz6OySwWHOuO+R6ZPMQPI=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@ -407,10 +416,12 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/githubv4 v0.0.0-20191127044304-8f68eb5628d0 h1:T9uus1QvcPgeLShS30YOnnzk3r9Vvygp45muhlrufgY=
|
||||
github.com/shurcooL/githubv4 v0.0.0-20191127044304-8f68eb5628d0/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
|
||||
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f h1:tygelZueB1EtXkPI6mQ4o9DQ0+FKW41hTbunoXZCTqk=
|
||||
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/simplereach/timeutils v1.2.0 h1:btgOAlu9RW6de2r2qQiONhjgxdAG7BL6je0G6J/yPnA=
|
||||
github.com/simplereach/timeutils v1.2.0/go.mod h1:VVbQDfN/FHRZa1LSqcwo4kNZ62OOyqLLGQKYB3pB0Q8=
|
||||
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
|
||||
@ -434,6 +445,7 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
|
||||
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
280
k8s/k8s.go
Normal file
280
k8s/k8s.go
Normal file
@ -0,0 +1,280 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/vuln-list-update/osv"
|
||||
"github.com/aquasecurity/vuln-list-update/utils"
|
||||
uu "github.com/aquasecurity/vuln-list-update/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
k8svulnDBURL = "https://kubernetes.io/docs/reference/issues-security/official-cve-feed/index.json"
|
||||
vulnListRepoTarBall = "https://api.github.com/repos/aquasecurity/vuln-list-k8s/tarball"
|
||||
mitreURL = "https://cveawg.mitre.org/api/cve"
|
||||
cveList = "https://www.cve.org/"
|
||||
)
|
||||
|
||||
type VulnDB struct {
|
||||
Cves []*osv.OSV
|
||||
}
|
||||
|
||||
type CVE struct {
|
||||
Items []Item `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
ContentText string `json:"content_text,omitempty"`
|
||||
DatePublished string `json:"date_published,omitempty"`
|
||||
ExternalURL string `json:"external_url,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
func Collect() (*VulnDB, error) {
|
||||
response, err := http.Get(k8svulnDBURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
var db CVE
|
||||
if err = json.NewDecoder(response.Body).Decode(&db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cvesMap, err := getExitingCvesToModifiedMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseVulnDBData(db, cvesMap)
|
||||
}
|
||||
|
||||
const (
|
||||
// excludeNonCoreComponentsCves exclude cves with missing data or non k8s core components
|
||||
excludeNonCoreComponentsCves = "CVE-2019-11255,CVE-2020-10749,CVE-2020-8554"
|
||||
)
|
||||
|
||||
func Update() error {
|
||||
if err := update(); err != nil {
|
||||
return xerrors.Errorf("error in k8s update: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func update() error {
|
||||
log.Printf("Fetching k8s cves")
|
||||
|
||||
k8sdb, err := Collect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, cve := range k8sdb.Cves {
|
||||
if err = uu.Write(filepath.Join(uu.VulnListDir(), "upstream", fmt.Sprintf("%s.json", cve.ID)), cve); err != nil {
|
||||
return xerrors.Errorf("failed to save k8s CVE detail: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseVulnDBData(db CVE, cvesMap map[string]string) (*VulnDB, error) {
|
||||
var fullVulnerabilities []*osv.OSV
|
||||
for _, item := range db.Items {
|
||||
for _, cveID := range getMultiIDs(item.ID) {
|
||||
// check if the current cve is older than the existing one on the vuln-list-k8s repo
|
||||
if strings.Contains(excludeNonCoreComponentsCves, item.ID) || olderCve(cveID, item.DatePublished, cvesMap) {
|
||||
continue
|
||||
}
|
||||
vulnerability, err := parseMitreCve(item.ExternalURL, cveID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cveMissingImportantData(vulnerability) {
|
||||
continue
|
||||
}
|
||||
descComponent := getComponentFromDescription(item.ContentText, vulnerability.Package)
|
||||
fullVulnerabilities = append(fullVulnerabilities, &osv.OSV{
|
||||
ID: cveID,
|
||||
Modified: item.DatePublished,
|
||||
Published: item.DatePublished,
|
||||
Summary: item.Summary,
|
||||
Details: vulnerability.Description,
|
||||
Affected: getAffectedEvents(vulnerability.versions, getComponentName(descComponent, vulnerability.Package), vulnerability.CvssV3),
|
||||
References: []osv.Reference{{Url: item.URL}, {Url: item.ExternalURL}},
|
||||
})
|
||||
}
|
||||
}
|
||||
err := validateCvesData(fullVulnerabilities)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &VulnDB{fullVulnerabilities}, nil
|
||||
}
|
||||
|
||||
func getAffectedEvents(v []*Version, p string, cvss Cvssv3) []osv.Affected {
|
||||
affected := make([]osv.Affected, 0)
|
||||
for _, av := range v {
|
||||
if len(av.Introduced) == 0 {
|
||||
continue
|
||||
}
|
||||
if av.Introduced == "0.0.0" {
|
||||
av.Introduced = "0"
|
||||
}
|
||||
events := make([]osv.Event, 0)
|
||||
ranges := make([]osv.Range, 0)
|
||||
if len(av.Introduced) > 0 {
|
||||
events = append(events, osv.Event{Introduced: av.Introduced})
|
||||
}
|
||||
if len(av.Fixed) > 0 {
|
||||
events = append(events, osv.Event{Fixed: av.Fixed})
|
||||
} else if len(av.LastAffected) > 0 {
|
||||
events = append(events, osv.Event{LastAffected: av.LastAffected})
|
||||
} else if len(av.Introduced) > 0 && len(av.LastAffected) == 0 && len(av.Fixed) == 0 {
|
||||
events = append(events, osv.Event{LastAffected: av.Introduced})
|
||||
}
|
||||
ranges = append(ranges, osv.Range{
|
||||
Events: events,
|
||||
})
|
||||
affected = append(affected, osv.Affected{Ranges: ranges, Package: osv.Package{Name: p, Ecosystem: "kubernetes"}, Severities: []osv.Severity{{Type: cvss.Type, Score: cvss.Vector}}})
|
||||
}
|
||||
return affected
|
||||
}
|
||||
|
||||
func getComponentName(k8sComponent string, mitreComponent string) string {
|
||||
if len(k8sComponent) == 0 {
|
||||
k8sComponent = mitreComponent
|
||||
}
|
||||
if strings.ToLower(mitreComponent) != "kubernetes" {
|
||||
k8sComponent = mitreComponent
|
||||
}
|
||||
return strings.ToLower(fmt.Sprintf("%s/%s", upstreamOrgByName(k8sComponent), upstreamRepoByName(k8sComponent)))
|
||||
}
|
||||
|
||||
func validateCvesData(cves []*osv.OSV) error {
|
||||
var result error
|
||||
for _, cve := range cves {
|
||||
if len(cve.ID) == 0 {
|
||||
result = errors.Join(result, fmt.Errorf("\nid is mssing on cve #%s", cve.ID))
|
||||
}
|
||||
if len(cve.Published) == 0 {
|
||||
result = errors.Join(result, fmt.Errorf("\nCreatedAt is mssing on cve #%s", cve.ID))
|
||||
}
|
||||
if len(cve.Summary) == 0 {
|
||||
result = errors.Join(result, fmt.Errorf("\nSummary is mssing on cve #%s", cve.ID))
|
||||
}
|
||||
for _, af := range cve.Affected {
|
||||
if len(strings.TrimPrefix(af.Package.Name, upstreamOrgByName(af.Package.Name))) == 0 {
|
||||
result = errors.Join(result, fmt.Errorf("\nComponent is mssing on cve #%s", cve.ID))
|
||||
}
|
||||
}
|
||||
if len(cve.Details) == 0 {
|
||||
result = errors.Join(result, fmt.Errorf("\nDescription is mssing on cve #%s", cve.ID))
|
||||
}
|
||||
if len(cve.Affected) == 0 {
|
||||
result = errors.Join(result, fmt.Errorf("\nAffected Version is missing on cve #%s", cve.ID))
|
||||
}
|
||||
if len(cve.Affected) > 0 {
|
||||
for _, v := range cve.Affected {
|
||||
for _, s := range v.Severities {
|
||||
if len(s.Type) == 0 {
|
||||
result = errors.Join(result, fmt.Errorf("\nVector is mssing on cve #%s", cve.ID))
|
||||
}
|
||||
}
|
||||
for _, r := range v.Ranges {
|
||||
for i := 1; i < len(r.Events); i++ {
|
||||
if len(r.Events[i-1].Introduced) == 0 {
|
||||
result = errors.Join(result, fmt.Errorf("\nAffectedVersion Introduced is missing from cve #%s", cve.ID))
|
||||
}
|
||||
if len(r.Events[i].Fixed) == 0 && len(r.Events[i].LastAffected) == 0 {
|
||||
result = errors.Join(result, fmt.Errorf("\nAffectedVersion Fixed and LastAffected are missing from cve #%s", cve.ID))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(cve.References) == 0 {
|
||||
result = errors.Join(result, fmt.Errorf("\nUrls is mssing on cve #%s", cve.ID))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func cveMissingImportantData(vulnerability *Cve) bool {
|
||||
return len(vulnerability.versions) == 0 ||
|
||||
len(vulnerability.Package) == 0 ||
|
||||
len(vulnerability.CvssV3.Vector) == 0
|
||||
}
|
||||
|
||||
// getExitingCvesToModifiedMap get the existing cves from vuln-list-k8s repo and map it to cve id and last updated
|
||||
func getExitingCvesToModifiedMap() (map[string]string, error) {
|
||||
response, err := http.Get(vulnListRepoTarBall)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
return cveIDToModifiedMap(utils.VulnListDir())
|
||||
}
|
||||
|
||||
// cveIDToModifiedMap read existing cves from vulnList folder and map it to cve id and last updated
|
||||
func cveIDToModifiedMap(cveFolderPath string) (map[string]string, error) {
|
||||
mapCveTime := make(map[string]string)
|
||||
if _, err := os.Stat(cveFolderPath); os.IsNotExist(err) {
|
||||
return mapCveTime, nil
|
||||
}
|
||||
fileInfo, err := os.ReadDir(cveFolderPath)
|
||||
if err != nil {
|
||||
return mapCveTime, err
|
||||
}
|
||||
for _, file := range fileInfo {
|
||||
if file.IsDir() {
|
||||
continue
|
||||
}
|
||||
b, err := os.ReadFile(filepath.Join(cveFolderPath, file.Name()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cve osv.OSV
|
||||
err = json.Unmarshal([]byte(strings.ReplaceAll(string(b), "\n", "")), &cve)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mapCveTime[cve.ID] = cve.Modified
|
||||
}
|
||||
return mapCveTime, nil
|
||||
}
|
||||
|
||||
// olderCve check if the current cve is older than the existing one
|
||||
func olderCve(cveID string, currentCVEUpdated string, existCveLastUpdated map[string]string) bool {
|
||||
if len(existCveLastUpdated) == 0 {
|
||||
return false
|
||||
}
|
||||
var lastUpdated string
|
||||
var ok bool
|
||||
if lastUpdated, ok = existCveLastUpdated[cveID]; !ok {
|
||||
return false
|
||||
}
|
||||
existLastUpdated, err := time.Parse(time.RFC3339, lastUpdated)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
currentLastUpdated, err := time.Parse(time.RFC3339, currentCVEUpdated)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// check if the current collcted cve is older or same as the existing one
|
||||
if currentLastUpdated.Before(existLastUpdated) || currentLastUpdated == existLastUpdated {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
62
k8s/k8s_test.go
Normal file
62
k8s/k8s_test.go
Normal file
@ -0,0 +1,62 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_ParseVulneDB(t *testing.T) {
|
||||
b, err := os.ReadFile("./testdata/k8s-db.json")
|
||||
assert.NoError(t, err)
|
||||
var bi CVE
|
||||
err = json.Unmarshal(b, &bi)
|
||||
assert.NoError(t, err)
|
||||
kvd, err := ParseVulnDBData(bi, map[string]string{})
|
||||
assert.NoError(t, err)
|
||||
err = validateCvesData(kvd.Cves)
|
||||
assert.NoError(t, err)
|
||||
gotVulnDB, err := json.Marshal(kvd.Cves)
|
||||
assert.NoError(t, err)
|
||||
wantVulnDB, err := os.ReadFile("./testdata/expected-vulndb.json")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, string(wantVulnDB), string(gotVulnDB))
|
||||
}
|
||||
|
||||
func Test_cveIDToModifiedMap(t *testing.T) {
|
||||
t.Run("valid folder with cve", func(t *testing.T) {
|
||||
tm, err := cveIDToModifiedMap("./testdata/happy/upstream")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tm["CVE-2018-1002102"], "2018-11-26T11:07:36Z")
|
||||
})
|
||||
|
||||
t.Run("non existing folder", func(t *testing.T) {
|
||||
tm, err := cveIDToModifiedMap("./test")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, len(tm) == 0)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_OlderCve(t *testing.T) {
|
||||
tests := []struct {
|
||||
Name string
|
||||
currentCveID string
|
||||
currentModified string
|
||||
cveModified map[string]string
|
||||
want bool
|
||||
}{
|
||||
{Name: "match CVE but older Modified", currentCveID: "CVE-2018-1002102", currentModified: "2018-11-25T11:07:36Z", cveModified: map[string]string{"CVE-2018-1002102": "2018-11-26T11:07:36Z"}, want: true},
|
||||
{Name: "match CVE but older not Modified", currentCveID: "CVE-2018-1002102", currentModified: "2018-11-27T11:07:36Z", cveModified: map[string]string{"CVE-2018-1002102": "2018-11-26T11:07:36Z"}, want: false},
|
||||
{Name: "match CVE same time", currentCveID: "CVE-2018-1002102", currentModified: "2018-11-27T11:07:36Z", cveModified: map[string]string{"CVE-2018-1002102": "2018-11-27T11:07:36Z"}, want: true},
|
||||
{Name: "no existing cve", currentCveID: "CVE-2018-1002102", currentModified: "2018-11-27T11:07:36Z", cveModified: map[string]string{}, want: false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
got := olderCve(tt.currentCveID, tt.currentModified, tt.cveModified)
|
||||
assert.Equal(t, got, tt.want)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
286
k8s/mitre.go
Normal file
286
k8s/mitre.go
Normal file
@ -0,0 +1,286 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
version "github.com/aquasecurity/go-version/pkg/version"
|
||||
)
|
||||
|
||||
type MitreCVE struct {
|
||||
CveMetadata CveMetadata
|
||||
Containers Containers
|
||||
}
|
||||
|
||||
type Containers struct {
|
||||
Cna struct {
|
||||
Affected []struct {
|
||||
Product string
|
||||
Vendor string
|
||||
Versions []*MitreVersion
|
||||
}
|
||||
Descriptions []Descriptions
|
||||
Metrics []struct {
|
||||
CvssV3_1 struct {
|
||||
VectorString string
|
||||
}
|
||||
CvssV3_0 struct {
|
||||
VectorString string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type MitreVersion struct {
|
||||
Status string
|
||||
Version string
|
||||
LessThanOrEqual string
|
||||
LessThan string
|
||||
VersionType string
|
||||
}
|
||||
|
||||
type CveMetadata struct {
|
||||
CveId string
|
||||
}
|
||||
|
||||
type Descriptions struct {
|
||||
Lang string
|
||||
Value string
|
||||
}
|
||||
|
||||
type Cve struct {
|
||||
Description string
|
||||
versions []*Version
|
||||
CvssV3 Cvssv3
|
||||
Package string
|
||||
}
|
||||
|
||||
type Cvssv3 struct {
|
||||
Vector string
|
||||
Type string
|
||||
}
|
||||
|
||||
type Version struct {
|
||||
Introduced string `json:"introduced,omitempty"`
|
||||
Fixed string `json:"fixed,omitempty"`
|
||||
LastAffected string `json:"last_affected,omitempty"`
|
||||
FixedIndex int `json:"-"`
|
||||
}
|
||||
|
||||
func parseMitreCve(externalURL string, cveID string) (*Cve, error) {
|
||||
if !strings.HasPrefix(externalURL, cveList) {
|
||||
// if no external url provided, return empty vulnerability to be skipped
|
||||
return &Cve{}, nil
|
||||
}
|
||||
response, err := http.Get(fmt.Sprintf("%s/%s", mitreURL, cveID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
var cve MitreCVE
|
||||
if err = json.NewDecoder(response.Body).Decode(&cve); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vulnerableVersions := make([]*Version, 0)
|
||||
var component string
|
||||
var requireMerge bool
|
||||
for _, a := range cve.Containers.Cna.Affected {
|
||||
if len(component) == 0 {
|
||||
component = strings.ToLower(a.Product)
|
||||
}
|
||||
for _, sv := range a.Versions {
|
||||
if sv.Status != "affected" {
|
||||
continue
|
||||
}
|
||||
var introduce, lastAffected, fixed string
|
||||
v, ok := sanitizedVersions(sv)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case len(v.LessThan) > 0:
|
||||
if strings.HasSuffix(v.LessThan, ".0") {
|
||||
v.Version = "0"
|
||||
}
|
||||
introduce, fixed = updateVersions(v.LessThan, v.Version)
|
||||
case len(v.LessThanOrEqual) > 0:
|
||||
introduce, lastAffected = updateVersions(v.LessThanOrEqual, v.Version)
|
||||
case minorVersion(v.Version):
|
||||
requireMerge = true
|
||||
introduce = v.Version
|
||||
default:
|
||||
introduce, lastAffected = extractRangeVersions(v.Version)
|
||||
}
|
||||
vulnerableVersions = append(vulnerableVersions, &Version{
|
||||
Introduced: introduce,
|
||||
Fixed: fixed,
|
||||
LastAffected: lastAffected,
|
||||
})
|
||||
}
|
||||
}
|
||||
if requireMerge {
|
||||
vulnerableVersions, err = mergeVersionRange(vulnerableVersions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
vector, vectorType := getMetrics(cve)
|
||||
description := getDescription(cve.Containers.Cna.Descriptions)
|
||||
return &Cve{
|
||||
Description: description,
|
||||
CvssV3: Cvssv3{
|
||||
Vector: vector,
|
||||
Type: vectorType,
|
||||
},
|
||||
Package: getComponentFromDescription(description, component),
|
||||
versions: vulnerableVersions,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func sanitizedVersions(v *MitreVersion) (*MitreVersion, bool) {
|
||||
if strings.Contains(v.Version, "n/a") && len(v.LessThan) == 0 && len(v.LessThanOrEqual) == 0 {
|
||||
return v, false
|
||||
}
|
||||
if (v.LessThanOrEqual == "unspecified" || v.LessThan == "unspecified") && len(v.Version) > 0 {
|
||||
return v, false
|
||||
}
|
||||
// example https://cveawg.mitre.org/api/cve/CVE-2023-2727
|
||||
if len(v.LessThanOrEqual) > 0 && v.LessThanOrEqual == "<=" {
|
||||
v.LessThanOrEqual = v.Version
|
||||
} else if len(v.LessThan) > 0 {
|
||||
switch {
|
||||
// example https://cveawg.mitre.org/api/cve/CVE-2019-11244
|
||||
case strings.HasSuffix(strings.TrimSpace(v.LessThan), "*"):
|
||||
v.Version = strings.TrimSpace(strings.ReplaceAll(v.LessThan, "*", ""))
|
||||
v.LessThan = ""
|
||||
}
|
||||
} else if len(v.Version) > 0 {
|
||||
switch {
|
||||
// example https://cveawg.mitre.org/api/cve/CVE-2020-8566
|
||||
case strings.HasPrefix(v.Version, "< "):
|
||||
v.LessThan = strings.TrimPrefix(v.Version, "< ")
|
||||
// example https://cveawg.mitre.org/api/cve/CVE-2020-8565
|
||||
case strings.HasPrefix(v.Version, "<= "):
|
||||
v.LessThanOrEqual = strings.TrimPrefix(v.Version, "<= ")
|
||||
//example https://cveawg.mitre.org/api/cve/CVE-2019-11247
|
||||
case strings.HasPrefix(strings.TrimSpace(v.Version), "prior to"):
|
||||
priorToVersion := strings.TrimSpace(strings.TrimPrefix(v.Version, "prior to"))
|
||||
if minorVersion(priorToVersion) {
|
||||
priorToVersion = priorToVersion + ".0"
|
||||
v.Version = priorToVersion
|
||||
}
|
||||
v.LessThan = priorToVersion
|
||||
// all version is vulnerable : https://cveawg.mitre.org/api/cve/CVE-2017-1002101
|
||||
case strings.HasSuffix(strings.TrimSpace(v.Version), ".x"):
|
||||
v.Version = strings.TrimSpace(strings.ReplaceAll(v.Version, ".x", ""))
|
||||
}
|
||||
}
|
||||
return &MitreVersion{
|
||||
Version: trimString(v.Version, []string{"v", "V"}),
|
||||
LessThanOrEqual: trimString(v.LessThanOrEqual, []string{"v", "V"}),
|
||||
LessThan: trimString(v.LessThan, []string{"v", "V"}),
|
||||
}, true
|
||||
}
|
||||
|
||||
func getDescription(descriptions []Descriptions) string {
|
||||
for _, d := range descriptions {
|
||||
if d.Lang == "en" {
|
||||
return d.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type byVersion []*Version
|
||||
|
||||
func (s byVersion) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s byVersion) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s byVersion) Less(i, j int) bool {
|
||||
v1, err := version.Parse(s[i].Introduced)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
v2, err := version.Parse(s[j].Introduced)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return v1.LessThan(v2)
|
||||
}
|
||||
|
||||
func mergeVersionRange(affectedVersions []*Version) ([]*Version, error) {
|
||||
// this special handling is made to handle to case of conceutive vulnable minor versions.
|
||||
// example: vulnerable 1.3, 1.4, 1.5, 1.6 and prior to versions 1.7.14, 1.8.9 will be form as follow:
|
||||
// Introduced: 1.3.0 Fixed: 1.7.14
|
||||
// Introduced: 1.8.0 Fixed: 1.8.9
|
||||
// example: https://cveawg.mitre.org/api/cve/CVE-2019-11249
|
||||
sort.Sort(byVersion(affectedVersions))
|
||||
newAffectedVesion := make([]*Version, 0)
|
||||
minorVersions := make([]*Version, 0)
|
||||
for _, av := range affectedVersions {
|
||||
if minorVersion(av.Introduced) {
|
||||
minorVersions = append(minorVersions, av)
|
||||
} else if strings.Count(av.Introduced, ".") > 1 && len(minorVersions) > 0 {
|
||||
newAffectedVesion = append(newAffectedVesion, &Version{
|
||||
Introduced: fmt.Sprintf("%s.0", minorVersions[0].Introduced),
|
||||
LastAffected: av.LastAffected,
|
||||
Fixed: av.Fixed,
|
||||
})
|
||||
minorVersions = minorVersions[:0]
|
||||
continue
|
||||
}
|
||||
if len(minorVersions) == 0 {
|
||||
newAffectedVesion = append(newAffectedVesion, av)
|
||||
}
|
||||
}
|
||||
|
||||
// this special handling is made to handle to case of consecutive vulnable minor versions, wheen there is no fixed version is provided.
|
||||
// example: vulnerable 1.3, 1.4, 1.5, 1.6 will be form as follow:
|
||||
// Introduced: 1.3.0 Fixed: 1.7.0
|
||||
if len(minorVersions) > 0 {
|
||||
currentVersion := fmt.Sprintf("%s.0", minorVersions[len(minorVersions)-1].Introduced)
|
||||
versionParts, err := versionParts(currentVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fixed := fmt.Sprintf("%d.%d.%d", versionParts[0], versionParts[1]+1, versionParts[2])
|
||||
newAffectedVesion = append(newAffectedVesion, &Version{Introduced: fmt.Sprintf("%s.0", minorVersions[0].Introduced), Fixed: fixed})
|
||||
}
|
||||
return newAffectedVesion, nil
|
||||
}
|
||||
|
||||
func getMetrics(cve MitreCVE) (string, string) {
|
||||
var vectorString string
|
||||
var vectorType string
|
||||
for _, metric := range cve.Containers.Cna.Metrics {
|
||||
vectorString = metric.CvssV3_0.VectorString
|
||||
vectorType = "CVSS_V3_0"
|
||||
if len(vectorString) == 0 {
|
||||
vectorString = metric.CvssV3_1.VectorString
|
||||
vectorType = "CVSS_V3_1"
|
||||
}
|
||||
}
|
||||
return vectorString, vectorType
|
||||
}
|
||||
|
||||
func versionParts(version string) ([]int, error) {
|
||||
parts := strings.Split(version, ".")
|
||||
intParts := make([]int, 0)
|
||||
for _, p := range parts {
|
||||
i, err := strconv.Atoi(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
intParts = append(intParts, i)
|
||||
}
|
||||
return intParts, nil
|
||||
}
|
75
k8s/mitre_test.go
Normal file
75
k8s/mitre_test.go
Normal file
@ -0,0 +1,75 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSanitizedVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
Name string
|
||||
Version *MitreVersion
|
||||
Want *MitreVersion
|
||||
}{
|
||||
{Name: "validate n/a version ", Version: &MitreVersion{Version: "n/a"}, Want: &MitreVersion{Version: "n/a"}},
|
||||
{Name: "validate unspecified version ", Version: &MitreVersion{Version: "unspecified"}, Want: &MitreVersion{Version: "unspecified"}},
|
||||
{Name: "validate less equal sign and version", Version: &MitreVersion{LessThanOrEqual: "<=", Version: "1.3.4"}, Want: &MitreVersion{Version: "1.3.4", LessThanOrEqual: "1.3.4"}},
|
||||
{Name: "validate less sign in version", Version: &MitreVersion{Version: "< 1.3.4"}, Want: &MitreVersion{Version: "< 1.3.4", LessThan: "1.3.4"}},
|
||||
{Name: "validate prior to then sign in version", Version: &MitreVersion{Version: "prior to 1.3.4"}, Want: &MitreVersion{Version: "prior to 1.3.4", LessThan: "1.3.4"}},
|
||||
{Name: "validate prior to with minor in version", Version: &MitreVersion{Version: "prior to 1.3"}, Want: &MitreVersion{Version: "1.3.0", LessThan: "1.3.0"}},
|
||||
{Name: "validate less with astrix", Version: &MitreVersion{LessThan: "1.3*"}, Want: &MitreVersion{Version: "1.3"}},
|
||||
{Name: "validate less with x", Version: &MitreVersion{Version: "1.3.x"}, Want: &MitreVersion{Version: "1.3"}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
got, _ := sanitizedVersions(tt.Version)
|
||||
assert.Equal(t, got, tt.Want)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergedVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
Name string
|
||||
affectedVersions []*Version
|
||||
WantAffectedVersions []*Version
|
||||
}{
|
||||
{Name: "merge regular version", affectedVersions: []*Version{
|
||||
{Introduced: "1.2"},
|
||||
{Introduced: "1.3"},
|
||||
{Introduced: "1.4.1", LastAffected: "1.4.6"},
|
||||
}, WantAffectedVersions: []*Version{
|
||||
{Introduced: "1.2.0", LastAffected: "1.4.6"}},
|
||||
},
|
||||
{Name: "merge mixed version", affectedVersions: []*Version{
|
||||
{Introduced: "1.3"},
|
||||
{Introduced: "1.4"},
|
||||
{Introduced: "1.5"},
|
||||
{Introduced: "1.6"},
|
||||
{Introduced: "1.7.0", Fixed: "1.7.14"},
|
||||
{Introduced: "1.8.0", Fixed: "1.8.9"},
|
||||
}, WantAffectedVersions: []*Version{
|
||||
{Introduced: "1.3.0", Fixed: "1.7.14"},
|
||||
{Introduced: "1.8.0", Fixed: "1.8.9"}},
|
||||
},
|
||||
{Name: "merge all minor version", affectedVersions: []*Version{
|
||||
{Introduced: "1.3"},
|
||||
{Introduced: "1.4"},
|
||||
{Introduced: "1.5"},
|
||||
{Introduced: "1.6"},
|
||||
{Introduced: "1.7"},
|
||||
}, WantAffectedVersions: []*Version{
|
||||
{Introduced: "1.3.0", Fixed: "1.8.0"},
|
||||
}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
gotLastAffected, err := mergeVersionRange(tt.affectedVersions)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, gotLastAffected, tt.WantAffectedVersions)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
140
k8s/parser.go
Normal file
140
k8s/parser.go
Normal file
@ -0,0 +1,140 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
version "github.com/aquasecurity/go-version/pkg/version"
|
||||
)
|
||||
|
||||
var (
|
||||
UpstreamOrgName = map[string]string{
|
||||
"k8s.io": "controller-manager,kubelet,apiserver,kubectl,kubernetes,kube-scheduler,kube-proxy",
|
||||
"sigs.k8s.io": "secrets-store-csi-driver",
|
||||
}
|
||||
|
||||
UpstreamRepoName = map[string]string{
|
||||
"kube-controller-manager": "controller-manager",
|
||||
"kubelet": "kubelet",
|
||||
"kube-apiserver": "apiserver",
|
||||
"kubectl": "kubectl",
|
||||
"kubernetes": "kubernetes",
|
||||
"kube-scheduler": "kube-scheduler",
|
||||
"kube-proxy": "kube-proxy",
|
||||
"api server": "apiserver",
|
||||
"secrets-store-csi-driver": "secrets-store-csi-driver",
|
||||
}
|
||||
)
|
||||
|
||||
func trimString(s string, trimValues []string) string {
|
||||
for _, v := range trimValues {
|
||||
s = strings.Trim(s, v)
|
||||
}
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
|
||||
func updateVersions(to, introduce string) (string, string) {
|
||||
// Example: https://cveawg.mitre.org/api/cve/CVE-2023-2878
|
||||
if introduce == "0" {
|
||||
return introduce, to
|
||||
}
|
||||
// Example: https://cveawg.mitre.org/api/cve/CVE-2019-11243
|
||||
if minorVersion(introduce) {
|
||||
return introduce + ".0", to
|
||||
}
|
||||
// Example: https://cveawg.mitre.org/api/cve/CVE-2019-1002100
|
||||
if lIndex := strings.LastIndex(to, "."); lIndex != -1 {
|
||||
return strings.TrimSpace(fmt.Sprintf("%s.%s", to[:lIndex], "0")), to
|
||||
}
|
||||
return introduce, to
|
||||
}
|
||||
|
||||
func extractRangeVersions(introduce string) (string, string) {
|
||||
// Example https://cveawg.mitre.org/api/cve/CVE-2021-25749
|
||||
var lastAffected string
|
||||
validVersion := make([]string, 0)
|
||||
// clean unwanted strings from versions
|
||||
versionRangeParts := strings.Split(introduce, " ")
|
||||
for _, p := range versionRangeParts {
|
||||
candidate, err := version.Parse(p)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
validVersion = append(validVersion, candidate.String())
|
||||
}
|
||||
if len(validVersion) >= 1 {
|
||||
introduce = strings.TrimSpace(validVersion[0])
|
||||
}
|
||||
if len(validVersion) == 2 {
|
||||
lastAffected = strings.TrimSpace(validVersion[1])
|
||||
}
|
||||
return introduce, lastAffected
|
||||
}
|
||||
|
||||
func getMultiIDs(id string) []string {
|
||||
var idsList []string
|
||||
if strings.Contains(id, ",") {
|
||||
idParts := strings.Split(id, ",")
|
||||
for _, p := range idParts {
|
||||
if strings.HasPrefix(strings.TrimSpace(p), "CVE-") {
|
||||
idsList = append(idsList, strings.TrimSpace(p))
|
||||
}
|
||||
}
|
||||
return idsList
|
||||
}
|
||||
return []string{id}
|
||||
}
|
||||
|
||||
func upstreamOrgByName(component string) string {
|
||||
for key, components := range UpstreamOrgName {
|
||||
for _, c := range strings.Split(components, ",") {
|
||||
if strings.TrimSpace(c) == strings.ToLower(component) {
|
||||
return key
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func upstreamRepoByName(component string) string {
|
||||
if val, ok := UpstreamRepoName[component]; ok {
|
||||
return val
|
||||
}
|
||||
return component
|
||||
}
|
||||
|
||||
func getComponentFromDescription(descriptions string, currentComponent string) string {
|
||||
if strings.ToLower(currentComponent) != "kubernetes" {
|
||||
return currentComponent
|
||||
}
|
||||
var compName string
|
||||
var compCounter int
|
||||
var kubeCtlVersionFound bool
|
||||
CoreComponentsNaming := []string{"kube-controller-manager", "kubelet", "kube-apiserver", "kubectl", "kube-scheduler", "kube-proxy", "secrets-store-csi-driver", "api server"}
|
||||
|
||||
for _, key := range CoreComponentsNaming {
|
||||
if strings.Contains(strings.ToLower(descriptions), key) {
|
||||
c := strings.Count(strings.ToLower(descriptions), key)
|
||||
if UpstreamRepoName[key] == compName {
|
||||
compCounter = compCounter + c
|
||||
}
|
||||
if strings.Contains(strings.ToLower(descriptions), "kubectl version") {
|
||||
kubeCtlVersionFound = true
|
||||
}
|
||||
if c > compCounter {
|
||||
compCounter = c
|
||||
compName = UpstreamRepoName[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
// in case found kubectl in env description and only one component found or no component found then fallback to k8s.io/kubernetes component
|
||||
if len(compName) == 0 || (kubeCtlVersionFound && compName == "kubectl" && compCounter == 1) {
|
||||
return currentComponent
|
||||
}
|
||||
return compName
|
||||
}
|
||||
|
||||
// MinorVersion returns true if version is minor version 1.1 or 2.2 and etc
|
||||
func minorVersion(version string) bool {
|
||||
return strings.Count(version, ".") == 1
|
||||
}
|
47
k8s/parser_test.go
Normal file
47
k8s/parser_test.go
Normal file
@ -0,0 +1,47 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_ExtractVersions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
version string
|
||||
less string
|
||||
wantIntroduce string
|
||||
wantLastAffected string
|
||||
}{
|
||||
{name: "range less with minor", version: "1.2", less: "1.2.5", wantIntroduce: "1.2.0", wantLastAffected: "1.2.5"},
|
||||
{name: "range less", version: "", less: "1.2.5", wantIntroduce: "1.2.0", wantLastAffected: "1.2.5"},
|
||||
{name: "range lessThen", version: "", less: "1.2.5", wantIntroduce: "1.2.0", wantLastAffected: "1.2.5"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotIntoduce, gotLastAffected := updateVersions(tt.less, tt.version)
|
||||
assert.Equal(t, gotIntoduce, tt.wantIntroduce)
|
||||
assert.Equal(t, gotLastAffected, tt.wantLastAffected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ExtractRangeVersions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
version string
|
||||
wantIntroduce string
|
||||
wantLastAffected string
|
||||
}{
|
||||
{name: "range versions", version: "1.2.3 - 1.2.5", wantIntroduce: "1.2.3", wantLastAffected: "1.2.5"},
|
||||
{name: "single versions", version: "1.2.5", wantIntroduce: "1.2.5", wantLastAffected: ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotIntoduce, gotLastAffected := extractRangeVersions(tt.version)
|
||||
assert.Equal(t, gotIntoduce, tt.wantIntroduce)
|
||||
assert.Equal(t, gotLastAffected, tt.wantLastAffected)
|
||||
})
|
||||
}
|
||||
}
|
1
k8s/testdata/expected-vulndb.json
vendored
Normal file
1
k8s/testdata/expected-vulndb.json
vendored
Normal file
File diff suppressed because one or more lines are too long
91
k8s/testdata/happy/upstream/CVE-2018-1002102
vendored
Normal file
91
k8s/testdata/happy/upstream/CVE-2018-1002102
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
{
|
||||
"id": "CVE-2018-1002102",
|
||||
"modified": "2018-11-26T11:07:36Z",
|
||||
"published": "2018-11-26T11:07:36Z",
|
||||
"summary": "proxy request handling in kube-apiserver can leave vulnerable TCP connections",
|
||||
"details": "In all Kubernetes versions prior to v1.10.11, v1.11.5, and v1.12.3, incorrect handling of error responses to proxied upgrade requests in the kube-apiserver
|
||||
allowed specially crafted requests to establish a connection through the Kubernetes API server to backend servers, then send arbitrary requests over the same connection
|
||||
directly to the backend, authenticated with the Kubernetes API server's TLS credentials used to establish the backend connection.",
|
||||
"affected": [
|
||||
{
|
||||
"package": {
|
||||
"ecosystem": "kubernetes",
|
||||
"name": "k8s.io/apiserver"
|
||||
},
|
||||
"severity": [
|
||||
{
|
||||
"type": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"score": "9.8"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"introduced": "1.0.0"
|
||||
},
|
||||
{
|
||||
"fixed": "1.10.11"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"package": {
|
||||
"ecosystem": "kubernetes",
|
||||
"name": "k8s.io/apiserver"
|
||||
},
|
||||
"severity": [
|
||||
{
|
||||
"type": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"score": "9.8"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"introduced": "1.11.0"
|
||||
},
|
||||
{
|
||||
"fixed": "1.11.5"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"package": {
|
||||
"ecosystem": "kubernetes",
|
||||
"name": "k8s.io/apiserver"
|
||||
},
|
||||
"severity": [
|
||||
{
|
||||
"type": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"score": "9.8"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"introduced": "1.12.0"
|
||||
},
|
||||
{
|
||||
"fixed": "1.12.3"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"url": "https://github.com/kubernetes/kubernetes/issues/71411"
|
||||
},
|
||||
{
|
||||
"url": "https://www.cve.org/cverecord?id=CVE-2018-1002105"
|
||||
}
|
||||
]
|
||||
}
|
535
k8s/testdata/k8s-db.json
vendored
Normal file
535
k8s/testdata/k8s-db.json
vendored
Normal file
File diff suppressed because one or more lines are too long
7
main.go
7
main.go
@ -21,6 +21,7 @@ import (
|
||||
"github.com/aquasecurity/vuln-list-update/debian/tracker"
|
||||
"github.com/aquasecurity/vuln-list-update/ghsa"
|
||||
"github.com/aquasecurity/vuln-list-update/glad"
|
||||
"github.com/aquasecurity/vuln-list-update/k8s"
|
||||
"github.com/aquasecurity/vuln-list-update/kevc"
|
||||
"github.com/aquasecurity/vuln-list-update/mariner"
|
||||
"github.com/aquasecurity/vuln-list-update/nvd"
|
||||
@ -38,7 +39,7 @@ import (
|
||||
|
||||
var (
|
||||
target = flag.String("target", "", "update target (nvd, alpine, alpine-unfixed, redhat, redhat-oval, "+
|
||||
"debian, ubuntu, amazon, oracle-oval, suse-cvrf, photon, arch-linux, ghsa, glad, cwe, osv, mariner, kevc, wolfi, chainguard)")
|
||||
"debian, ubuntu, amazon, oracle-oval, suse-cvrf, photon, arch-linux, ghsa, glad, cwe, osv, mariner, kevc, wolfi, chainguard, k8s)")
|
||||
vulnListDir = flag.String("vuln-list-dir", "", "vuln-list dir")
|
||||
targetUri = flag.String("target-uri", "", "alternative repository URI (only glad)")
|
||||
targetBranch = flag.String("target-branch", "", "alternative repository branch (only glad)")
|
||||
@ -171,6 +172,10 @@ func run() error {
|
||||
if err := cu.Update(); err != nil {
|
||||
return xerrors.Errorf("Chainguard update error: %w", err)
|
||||
}
|
||||
case "k8s":
|
||||
if err := k8s.Update(); err != nil {
|
||||
return xerrors.Errorf("Chainguard update error: %w", err)
|
||||
}
|
||||
default:
|
||||
return xerrors.New("unknown target")
|
||||
}
|
||||
|
17
osv/types.go
17
osv/types.go
@ -1,13 +1,20 @@
|
||||
package osv
|
||||
|
||||
type Affected struct {
|
||||
Package Package `json:"package,omitempty"`
|
||||
Ranges []Range `json:"ranges,omitempty"`
|
||||
Versions []string `json:"versions,omitempty"`
|
||||
Ecosystem interface{} `json:"ecosystem_specific,omitempty"` //The meaning of the values within the object is entirely defined by the ecosystem
|
||||
Database interface{} `json:"database_specific,omitempty"` //The meaning of the values within the object is entirely defined by the database
|
||||
Package Package `json:"package,omitempty"`
|
||||
Severities []Severity `json:"severity,omitempty"`
|
||||
Ranges []Range `json:"ranges,omitempty"`
|
||||
Versions []string `json:"versions,omitempty"`
|
||||
Ecosystem interface{} `json:"ecosystem_specific,omitempty"` //The meaning of the values within the object is entirely defined by the ecosystem
|
||||
Database interface{} `json:"database_specific,omitempty"` //The meaning of the values within the object is entirely defined by the database
|
||||
|
||||
}
|
||||
|
||||
type Severity struct {
|
||||
Type string `json:"type"`
|
||||
Score string `json:"score"`
|
||||
}
|
||||
|
||||
type Package struct {
|
||||
Ecosystem string `json:"ecosystem,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
|
Loading…
Reference in New Issue
Block a user