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 report |
||
19 | |||
20 | import ( |
||
21 | "encoding/json" |
||
22 | "fmt" |
||
23 | "net/http" |
||
24 | "time" |
||
25 | |||
26 | "github.com/cenkalti/backoff" |
||
27 | "github.com/parnurzeal/gorequest" |
||
28 | "golang.org/x/xerrors" |
||
29 | |||
30 | "github.com/future-architect/vuls/config" |
||
31 | "github.com/future-architect/vuls/util" |
||
32 | cvedb "github.com/kotakanbe/go-cve-dictionary/db" |
||
33 | cve "github.com/kotakanbe/go-cve-dictionary/models" |
||
34 | ) |
||
35 | |||
36 | // CveClient is api client of CVE disctionary service. |
||
37 | var CveClient cvedictClient |
||
38 | |||
39 | type cvedictClient struct { |
||
40 | // httpProxy string |
||
41 | baseURL string |
||
42 | } |
||
43 | |||
44 | func (api *cvedictClient) initialize() { |
||
45 | api.baseURL = config.Conf.CveDict.URL |
||
46 | } |
||
47 | |||
48 | func (api cvedictClient) CheckHealth() error { |
||
49 | if !config.Conf.CveDict.IsFetchViaHTTP() { |
||
50 | util.Log.Debugf("get cve-dictionary from %s", config.Conf.CveDict.Type) |
||
51 | return nil |
||
52 | } |
||
53 | |||
54 | api.initialize() |
||
55 | url := fmt.Sprintf("%s/health", api.baseURL) |
||
56 | var errs []error |
||
57 | var resp *http.Response |
||
58 | resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End() |
||
59 | // resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End() |
||
60 | if 0 < len(errs) || resp == nil || resp.StatusCode != 200 { |
||
61 | return xerrors.Errorf("Failed to request to CVE server. url: %s, errs: %w", |
||
0 ignored issues
–
show
introduced
by
![]() |
|||
62 | url, errs) |
||
63 | } |
||
64 | return nil |
||
65 | } |
||
66 | |||
67 | type response struct { |
||
68 | Key string |
||
69 | CveDetail cve.CveDetail |
||
70 | } |
||
71 | |||
72 | func (api cvedictClient) FetchCveDetails(driver cvedb.DB, cveIDs []string) (cveDetails []cve.CveDetail, err error) { |
||
73 | if !config.Conf.CveDict.IsFetchViaHTTP() { |
||
74 | for _, cveID := range cveIDs { |
||
75 | cveDetail, err := driver.Get(cveID) |
||
76 | if err != nil { |
||
77 | return nil, xerrors.Errorf("Failed to fetch CVE. err: %w", err) |
||
0 ignored issues
–
show
|
|||
78 | } |
||
79 | if len(cveDetail.CveID) == 0 { |
||
80 | cveDetails = append(cveDetails, cve.CveDetail{ |
||
81 | CveID: cveID, |
||
82 | }) |
||
83 | } else { |
||
84 | cveDetails = append(cveDetails, *cveDetail) |
||
85 | } |
||
86 | } |
||
87 | return |
||
88 | } |
||
89 | |||
90 | api.baseURL = config.Conf.CveDict.URL |
||
91 | reqChan := make(chan string, len(cveIDs)) |
||
92 | resChan := make(chan response, len(cveIDs)) |
||
93 | errChan := make(chan error, len(cveIDs)) |
||
94 | defer close(reqChan) |
||
95 | defer close(resChan) |
||
96 | defer close(errChan) |
||
97 | |||
98 | go func() { |
||
99 | for _, cveID := range cveIDs { |
||
100 | reqChan <- cveID |
||
101 | } |
||
102 | }() |
||
103 | |||
104 | concurrency := 10 |
||
105 | tasks := util.GenWorkers(concurrency) |
||
106 | for range cveIDs { |
||
107 | tasks <- func() { |
||
108 | select { |
||
109 | case cveID := <-reqChan: |
||
110 | url, err := util.URLPathJoin(api.baseURL, "cves", cveID) |
||
111 | if err != nil { |
||
112 | errChan <- err |
||
113 | } else { |
||
114 | util.Log.Debugf("HTTP Request to %s", url) |
||
115 | api.httpGet(cveID, url, resChan, errChan) |
||
116 | } |
||
117 | } |
||
118 | } |
||
119 | } |
||
120 | |||
121 | timeout := time.After(2 * 60 * time.Second) |
||
122 | var errs []error |
||
123 | for range cveIDs { |
||
124 | select { |
||
125 | case res := <-resChan: |
||
126 | if len(res.CveDetail.CveID) == 0 { |
||
127 | cveDetails = append(cveDetails, cve.CveDetail{ |
||
128 | CveID: res.Key, |
||
129 | }) |
||
130 | } else { |
||
131 | cveDetails = append(cveDetails, res.CveDetail) |
||
132 | } |
||
133 | case err := <-errChan: |
||
134 | errs = append(errs, err) |
||
135 | case <-timeout: |
||
136 | return nil, xerrors.New("Timeout Fetching CVE") |
||
137 | } |
||
138 | } |
||
139 | if len(errs) != 0 { |
||
140 | return nil, |
||
141 | xerrors.Errorf("Failed to fetch CVE. err: %w", errs) |
||
0 ignored issues
–
show
|
|||
142 | } |
||
143 | return |
||
144 | } |
||
145 | |||
146 | func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errChan chan<- error) { |
||
147 | var body string |
||
148 | var errs []error |
||
149 | var resp *http.Response |
||
150 | f := func() (err error) { |
||
151 | // resp, body, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End() |
||
152 | resp, body, errs = gorequest.New().Get(url).End() |
||
153 | if 0 < len(errs) || resp == nil || resp.StatusCode != 200 { |
||
154 | return xerrors.Errorf("HTTP GET Error, url: %s, resp: %v, err: %w", |
||
0 ignored issues
–
show
|
|||
155 | url, resp, errs) |
||
156 | } |
||
157 | return nil |
||
158 | } |
||
159 | notify := func(err error, t time.Duration) { |
||
160 | util.Log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %s", |
||
161 | t, err) |
||
162 | } |
||
163 | err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify) |
||
164 | if err != nil { |
||
165 | errChan <- xerrors.Errorf("HTTP Error: %w", err) |
||
0 ignored issues
–
show
|
|||
166 | return |
||
167 | } |
||
168 | cveDetail := cve.CveDetail{} |
||
169 | if err := json.Unmarshal([]byte(body), &cveDetail); err != nil { |
||
170 | errChan <- xerrors.Errorf("Failed to Unmarshall. body: %s, err: %w", body, err) |
||
0 ignored issues
–
show
|
|||
171 | return |
||
172 | } |
||
173 | resChan <- response{ |
||
174 | key, |
||
175 | cveDetail, |
||
176 | } |
||
177 | } |
||
178 | |||
179 | func (api cvedictClient) FetchCveDetailsByCpeName(driver cvedb.DB, cpeName string) ([]cve.CveDetail, error) { |
||
180 | if config.Conf.CveDict.IsFetchViaHTTP() { |
||
181 | api.baseURL = config.Conf.CveDict.URL |
||
182 | url, err := util.URLPathJoin(api.baseURL, "cpes") |
||
183 | if err != nil { |
||
184 | return nil, err |
||
185 | } |
||
186 | |||
187 | query := map[string]string{"name": cpeName} |
||
188 | util.Log.Debugf("HTTP Request to %s, query: %#v", url, query) |
||
189 | return api.httpPost(cpeName, url, query) |
||
190 | } |
||
191 | return driver.GetByCpeURI(cpeName) |
||
192 | } |
||
193 | |||
194 | func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]cve.CveDetail, error) { |
||
195 | var body string |
||
196 | var errs []error |
||
197 | var resp *http.Response |
||
198 | f := func() (err error) { |
||
199 | // req := gorequest.New().SetDebug(config.Conf.Debug).Post(url) |
||
200 | req := gorequest.New().Post(url) |
||
201 | for key := range query { |
||
202 | req = req.Send(fmt.Sprintf("%s=%s", key, query[key])).Type("json") |
||
203 | } |
||
204 | resp, body, errs = req.End() |
||
205 | if 0 < len(errs) || resp == nil || resp.StatusCode != 200 { |
||
206 | return xerrors.Errorf("HTTP POST error. url: %s, resp: %v, err: %w", url, resp, errs) |
||
0 ignored issues
–
show
|
|||
207 | } |
||
208 | return nil |
||
209 | } |
||
210 | notify := func(err error, t time.Duration) { |
||
211 | util.Log.Warnf("Failed to HTTP POST. retrying in %s seconds. err: %s", t, err) |
||
212 | } |
||
213 | err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify) |
||
214 | if err != nil { |
||
215 | return nil, xerrors.Errorf("HTTP Error: %w", err) |
||
0 ignored issues
–
show
|
|||
216 | } |
||
217 | |||
218 | cveDetails := []cve.CveDetail{} |
||
219 | if err := json.Unmarshal([]byte(body), &cveDetails); err != nil { |
||
220 | return nil, |
||
221 | xerrors.Errorf("Failed to Unmarshall. body: %s, err: %w", body, err) |
||
0 ignored issues
–
show
|
|||
222 | } |
||
223 | return cveDetails, nil |
||
224 | } |
||
225 |