Completed
Push — master ( 76037c...7585f9 )
by kota
09:11 queued 01:58
created

report.cvedictClient.isFetchViaHTTP   A

Complexity

Conditions 3

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
dl 0
loc 6
rs 10
c 0
b 0
f 0
nop 0
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
29
	"github.com/future-architect/vuls/config"
30
	"github.com/future-architect/vuls/util"
31
	cvedb "github.com/kotakanbe/go-cve-dictionary/db"
32
	cve "github.com/kotakanbe/go-cve-dictionary/models"
33
)
34
35
// CveClient is api client of CVE disctionary service.
36
var CveClient cvedictClient
37
38
type cvedictClient struct {
39
	//  httpProxy string
40
	baseURL string
41
}
42
43
func (api *cvedictClient) initialize() {
44
	api.baseURL = config.Conf.CveDict.URL
45
}
46
47
func (api cvedictClient) CheckHealth() error {
48
	if !config.Conf.CveDict.IsFetchViaHTTP() {
49
		util.Log.Debugf("get cve-dictionary from %s", config.Conf.CveDict.Type)
50
		return nil
51
	}
52
53
	api.initialize()
54
	url := fmt.Sprintf("%s/health", api.baseURL)
55
	var errs []error
56
	var resp *http.Response
57
	resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
58
	//  resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
59
	if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
60
		return fmt.Errorf("Failed to request to CVE server. url: %s, errs: %v",
61
			url, errs)
62
	}
63
	return nil
64
}
65
66
type response struct {
67
	Key       string
68
	CveDetail cve.CveDetail
69
}
70
71
func (api cvedictClient) FetchCveDetails(driver cvedb.DB, cveIDs []string) (cveDetails []cve.CveDetail, err error) {
72
	if !config.Conf.CveDict.IsFetchViaHTTP() {
73
		for _, cveID := range cveIDs {
74
			cveDetail, err := driver.Get(cveID)
75
			if err != nil {
76
				return nil, fmt.Errorf("Failed to fetch CVE. err: %s", err)
77
			}
78
			if len(cveDetail.CveID) == 0 {
79
				cveDetails = append(cveDetails, cve.CveDetail{
80
					CveID: cveID,
81
				})
82
			} else {
83
				cveDetails = append(cveDetails, *cveDetail)
84
			}
85
		}
86
		return
87
	}
88
89
	api.baseURL = config.Conf.CveDict.URL
90
	reqChan := make(chan string, len(cveIDs))
91
	resChan := make(chan response, len(cveIDs))
92
	errChan := make(chan error, len(cveIDs))
93
	defer close(reqChan)
94
	defer close(resChan)
95
	defer close(errChan)
96
97
	go func() {
98
		for _, cveID := range cveIDs {
99
			reqChan <- cveID
100
		}
101
	}()
102
103
	concurrency := 10
104
	tasks := util.GenWorkers(concurrency)
105
	for range cveIDs {
106
		tasks <- func() {
107
			select {
108
			case cveID := <-reqChan:
109
				url, err := util.URLPathJoin(api.baseURL, "cves", cveID)
110
				if err != nil {
111
					errChan <- err
112
				} else {
113
					util.Log.Debugf("HTTP Request to %s", url)
114
					api.httpGet(cveID, url, resChan, errChan)
115
				}
116
			}
117
		}
118
	}
119
120
	timeout := time.After(2 * 60 * time.Second)
121
	var errs []error
122
	for range cveIDs {
123
		select {
124
		case res := <-resChan:
125
			if len(res.CveDetail.CveID) == 0 {
126
				cveDetails = append(cveDetails, cve.CveDetail{
127
					CveID: res.Key,
128
				})
129
			} else {
130
				cveDetails = append(cveDetails, res.CveDetail)
131
			}
132
		case err := <-errChan:
133
			errs = append(errs, err)
134
		case <-timeout:
135
			return nil, fmt.Errorf("Timeout Fetching CVE")
136
		}
137
	}
138
	if len(errs) != 0 {
139
		return nil,
140
			fmt.Errorf("Failed to fetch CVE. err: %v", errs)
141
	}
142
	return
143
}
144
145
func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errChan chan<- error) {
146
	var body string
147
	var errs []error
148
	var resp *http.Response
149
	f := func() (err error) {
150
		//  resp, body, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
151
		resp, body, errs = gorequest.New().Get(url).End()
152
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
153
			return fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v",
154
				errs, url, resp)
155
		}
156
		return nil
157
	}
158
	notify := func(err error, t time.Duration) {
159
		util.Log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %s",
160
			t, err)
161
	}
162
	err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
163
	if err != nil {
164
		errChan <- fmt.Errorf("HTTP Error %s", err)
165
		return
166
	}
167
	cveDetail := cve.CveDetail{}
168
	if err := json.Unmarshal([]byte(body), &cveDetail); err != nil {
169
		errChan <- fmt.Errorf("Failed to Unmarshall. body: %s, err: %s",
170
			body, err)
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 fmt.Errorf("HTTP POST error: %v, url: %s, resp: %v", errs, url, resp)
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, fmt.Errorf("HTTP Error %s", err)
216
	}
217
218
	cveDetails := []cve.CveDetail{}
219
	if err := json.Unmarshal([]byte(body), &cveDetails); err != nil {
220
		return nil,
221
			fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", body, err)
222
	}
223
	return cveDetails, nil
224
}
225