Completed
Pull Request — master (#775)
by kota
06:23
created

github/github.go   A

Size/Duplication

Total Lines 134
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 93
dl 0
loc 134
rs 10
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
D github.FillGitHubSecurityAlerts 0 84 12
1
/* Vuls - Vulnerability Scanner
2
Copyright (C) 2016  Future Corporation , Japan.
3
4
This program is free software: you can redistribute it and/or modify
5
it under the terms of the GNU General Public License as published by
6
the Free Software Foundation, either version 3 of the License, or
7
(at your option) any later version.
8
9
This program is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
GNU General Public License for more details.
13
14
You should have received a copy of the GNU General Public License
15
along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
*/
17
18
package github
19
20
import (
21
	"bytes"
22
	"context"
23
	"encoding/json"
24
	"fmt"
25
	"io/ioutil"
26
	"net/http"
27
	"time"
28
29
	"github.com/future-architect/vuls/config"
30
	"github.com/future-architect/vuls/models"
31
	"github.com/future-architect/vuls/util"
32
	"github.com/k0kubun/pp"
33
	"golang.org/x/oauth2"
34
)
35
36
// FillGitHubSecurityAlerts access to owner/repo on GitHub and fetch scurity alerts of the repository via GitHub API v4 GraphQL and then set to the given ScanResult.
37
// https://help.github.com/articles/about-security-alerts-for-vulnerable-dependencies/
38
func FillGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string) (nCVEs int, err error) {
39
	src := oauth2.StaticTokenSource(
40
		&oauth2.Token{AccessToken: token},
41
	)
42
	httpClient := oauth2.NewClient(context.Background(), src)
43
44
	// TODO Use `https://github.com/shurcooL/githubv4` if the tool supports vulnerabilityAlerts Endpoint
45
	const jsonfmt = `{"query":
46
	"query { repository(owner:\"%s\", name:\"%s\") { url, vulnerabilityAlerts(first: %d, %s) { pageInfo{ endCursor, hasNextPage, startCursor}, edges { node { id, externalIdentifier, externalReference, fixedIn, packageName,  dismissReason, dismissedAt } } } } }"}`
47
	after := ""
48
49
	for {
50
		jsonStr := fmt.Sprintf(jsonfmt, owner, repo, 100, after)
51
		req, err := http.NewRequest("POST",
52
			"https://api.github.com/graphql",
53
			bytes.NewBuffer([]byte(jsonStr)),
54
		)
55
		if err != nil {
56
			return 0, err
57
		}
58
59
		// https://developer.github.com/v4/previews/#repository-vulnerability-alerts
60
		// To toggle this preview and access data, need to provide a custom media type in the Accept header:
61
		// MEMO: I tried to get the affected version via GitHub API. Bit it seems difficult to determin the affected version if there are multiple dependency files such as package.json.
62
		// TODO remove this header if it is no longer preview status in the future.
63
		req.Header.Set("Accept", "application/vnd.github.vixen-preview+json")
64
		req.Header.Set("Content-Type", "application/json")
65
66
		resp, err := httpClient.Do(req)
67
		if err != nil {
68
			return 0, err
69
		}
70
		defer resp.Body.Close()
71
		bodyBytes, err := ioutil.ReadAll(resp.Body)
72
		if err != nil {
73
			return 0, err
74
		}
75
76
		alerts := SecurityAlerts{}
77
		if err = json.Unmarshal(bodyBytes, &alerts); err != nil {
78
			return 0, err
79
		}
80
81
		util.Log.Debugf("%s", pp.Sprint(alerts))
82
83
		for _, v := range alerts.Data.Repository.VulnerabilityAlerts.Edges {
84
			if config.Conf.IgnoreGitHubDismissed && v.Node.DismissReason != "" {
85
				continue
86
			}
87
88
			pkgName := fmt.Sprintf("%s %s",
89
				alerts.Data.Repository.URL, v.Node.PackageName)
90
91
			m := models.GitHubSecurityAlert{
92
				PackageName:     pkgName,
93
				FixedIn:         v.Node.FixedIn,
94
				AffectedRange:   v.Node.AffectedRange,
95
				Dismissed:       len(v.Node.DismissReason) != 0,
96
				DismissedAt:     v.Node.DismissedAt,
97
				DismissedReason: v.Node.DismissReason,
98
			}
99
100
			cveID := v.Node.ExternalIdentifier
101
102
			if val, ok := r.ScannedCves[cveID]; ok {
103
				val.GitHubSecurityAlerts = val.GitHubSecurityAlerts.Add(m)
104
				r.ScannedCves[cveID] = val
105
				nCVEs++
106
			} else {
107
				v := models.VulnInfo{
108
					CveID:                cveID,
109
					Confidences:          models.Confidences{models.GitHubMatch},
110
					GitHubSecurityAlerts: models.GitHubSecurityAlerts{m},
111
				}
112
				r.ScannedCves[cveID] = v
113
				nCVEs++
114
			}
115
		}
116
		if !alerts.Data.Repository.VulnerabilityAlerts.PageInfo.HasNextPage {
117
			break
118
		}
119
		after = fmt.Sprintf(`after: \"%s\"`, alerts.Data.Repository.VulnerabilityAlerts.PageInfo.EndCursor)
120
	}
121
	return nCVEs, err
122
}
123
124
//SecurityAlerts has detected CVE-IDs, PackageNames, Refs
125
type SecurityAlerts struct {
126
	Data struct {
127
		Repository struct {
128
			URL                 string `json:"url,omitempty"`
129
			VulnerabilityAlerts struct {
130
				PageInfo struct {
131
					EndCursor   string `json:"endCursor,omitempty"`
132
					HasNextPage bool   `json:"hasNextPage,omitempty"`
133
					StartCursor string `json:"startCursor,omitempty"`
134
				} `json:"pageInfo,omitempty"`
135
				Edges []struct {
136
					Node struct {
137
						ID                 string `json:"id,omitempty"`
138
						ExternalIdentifier string `json:"externalIdentifier,omitempty"`
139
						ExternalReference  string `json:"externalReference,omitempty"`
140
						FixedIn            string `json:"fixedIn,omitempty"`
141
						AffectedRange      string `json:"affectedRange,omitempty"`
142
						PackageName        string `json:"packageName,omitempty"`
143
						DismissReason      string `json:"dismissReason,omitempty"`
144
						// DismissedAt        string `json:"dismissedAt,omitempty"`
145
						DismissedAt time.Time `json:"dismissedAt,omitempty"`
146
						// Dismisser          string `json:"dismisser,omitempty"`
147
					} `json:"node,omitempty"`
148
				} `json:"edges,omitempty"`
149
			} `json:"vulnerabilityAlerts,omitempty"`
150
		} `json:"repository,omitempty"`
151
	} `json:"data,omitempty"`
152
}
153