Completed
Pull Request — master (#775)
by kota
11:19
created

github/github.go   A

Size/Duplication

Total Lines 120
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 77
dl 0
loc 120
rs 10
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
C github.FillGitHubSecurityAlerts 0 78 10
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
28
	"github.com/future-architect/vuls/models"
29
	"github.com/future-architect/vuls/util"
30
	"github.com/k0kubun/pp"
31
	"golang.org/x/oauth2"
32
)
33
34
// 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.
35
// https://help.github.com/articles/about-security-alerts-for-vulnerable-dependencies/
36
func FillGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string) (nCVEs int, err error) {
37
	src := oauth2.StaticTokenSource(
38
		&oauth2.Token{AccessToken: token},
39
	)
40
	httpClient := oauth2.NewClient(context.Background(), src)
41
42
	// TODO Use `https://github.com/shurcooL/githubv4` if the tool supports vulnerabilityAlerts Endpoint
43
	const jsonfmt = `{"query":
44
	"query { repository(owner:\"%s\", name:\"%s\") { name, vulnerabilityAlerts(first: %d, %s) { pageInfo{ endCursor, hasNextPage, startCursor}, edges { node { id, externalIdentifier, externalReference, fixedIn, packageName } } } } }"}`
45
	after := ""
46
47
	for {
48
		jsonStr := fmt.Sprintf(jsonfmt, owner, repo, 100, after)
49
		req, err := http.NewRequest("POST",
50
			"https://api.github.com/graphql",
51
			bytes.NewBuffer([]byte(jsonStr)),
52
		)
53
		if err != nil {
54
			return 0, err
55
		}
56
57
		// https://developer.github.com/v4/previews/#repository-vulnerability-alerts
58
		// To toggle this preview and access data, need to provide a custom media type in the Accept header:
59
		// TODO remove this header if it is no longer preview status in the future.
60
		req.Header.Set("Accept", "application/vnd.github.vixen-preview+json")
61
		req.Header.Set("Content-Type", "application/json")
62
63
		resp, err := httpClient.Do(req)
64
		if err != nil {
65
			return 0, err
66
		}
67
		defer resp.Body.Close()
68
		bodyBytes, err := ioutil.ReadAll(resp.Body)
69
		if err != nil {
70
			return 0, err
71
		}
72
73
		alerts := SecurityAlerts{}
74
		if err = json.Unmarshal(bodyBytes, &alerts); err != nil {
75
			return 0, err
76
		}
77
		// TODO remove before merging to the master
78
		util.Log.Debugf("%s", pp.Sprint(alerts))
79
80
		// TODO add type field to models.Pakcage.
81
		// OS Packages ... osPkg
82
		// CPE ... CPE
83
		// GitHub ... GitHub
84
		// WordPress theme ... wpTheme
85
		// WordPress plugin ... wpPlugin
86
		// WordPress core ... wpCore
87
		for _, v := range alerts.Data.Repository.VulnerabilityAlerts.Edges {
88
			cveID := v.Node.ExternalIdentifier
89
			affectedPkg := models.PackageStatus{Name: v.Node.PackageName}
90
			if val, ok := r.ScannedCves[cveID]; ok {
91
				val.AffectedPackages = append(val.AffectedPackages, affectedPkg)
92
				r.ScannedCves[cveID] = val
93
				// TODO add package information to r.Packages
94
				// TODO get current version via github API if possible
95
				nCVEs++
96
			} else {
97
				v := models.VulnInfo{
98
					CveID:            cveID,
99
					Confidences:      models.Confidences{models.GitHubMatch},
100
					AffectedPackages: models.PackageStatuses{affectedPkg},
101
				}
102
				r.ScannedCves[cveID] = v
103
				// TODO add package information to r.Packages
104
				// TODO get current version via github API if possible
105
				nCVEs++
106
			}
107
		}
108
		if !alerts.Data.Repository.VulnerabilityAlerts.PageInfo.HasNextPage {
109
			break
110
		}
111
		after = fmt.Sprintf(`after: \"%s\"`, alerts.Data.Repository.VulnerabilityAlerts.PageInfo.EndCursor)
112
	}
113
	return nCVEs, err
114
}
115
116
//SecurityAlerts has detected CVE-IDs, PackageNames, Refs
117
type SecurityAlerts struct {
118
	Data struct {
119
		Repository struct {
120
			VulnerabilityAlerts struct {
121
				PageInfo struct {
122
					EndCursor   string `json:"endCursor"`
123
					HasNextPage bool   `json:"hasNextPage"`
124
					StartCursor string `json:"startCursor"`
125
				} `json:"pageInfo"`
126
				Edges []struct {
127
					Node struct {
128
						ID                 string `json:"id"`
129
						ExternalIdentifier string `json:"externalIdentifier"`
130
						ExternalReference  string `json:"externalReference"`
131
						FixedIn            string `json:"fixedIn"`
132
						PackageName        string `json:"packageName"`
133
					} `json:"node"`
134
				} `json:"edges"`
135
			} `json:"vulnerabilityAlerts"`
136
		} `json:"repository"`
137
	} `json:"data"`
138
}
139