From 51fba46893f04646474e7d63309e6696f589afcc Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Wed, 25 Jan 2023 16:45:05 +0100 Subject: [PATCH] Add extra http header support to downloader and checker. --- cmd/csaf_checker/main.go | 20 ++++++----- cmd/csaf_checker/processor.go | 30 +++++++++------- cmd/csaf_downloader/downloader.go | 30 +++++++++------- cmd/csaf_downloader/main.go | 12 ++++--- docs/csaf_checker.md | 3 +- docs/csaf_downloader.md | 4 +-- util/client.go | 59 +++++++++++++++++++++++++++++++ 7 files changed, 117 insertions(+), 41 deletions(-) diff --git a/cmd/csaf_checker/main.go b/cmd/csaf_checker/main.go index 78f58b6..476851a 100644 --- a/cmd/csaf_checker/main.go +++ b/cmd/csaf_checker/main.go @@ -19,6 +19,7 @@ import ( "html/template" "io" "log" + "net/http" "os" "github.com/csaf-poc/csaf_distribution/util" @@ -29,15 +30,16 @@ import ( var reportHTML string type options struct { - Output string `short:"o" long:"output" description:"File name of the generated report" value-name:"REPORT-FILE"` - Format string `short:"f" long:"format" choice:"json" choice:"html" description:"Format of report" default:"json"` - Insecure bool `long:"insecure" description:"Do not check TLS certificates from provider"` - ClientCert *string `long:"client-cert" description:"TLS client certificate file (PEM encoded data)" value-name:"CERT-FILE"` - ClientKey *string `long:"client-key" description:"TLS client private key file (PEM encoded data)" value-name:"KEY-FILE"` - Version bool `long:"version" description:"Display version of the binary"` - Verbose bool `long:"verbose" short:"v" description:"Verbose output"` - Rate *float64 `long:"rate" short:"r" description:"The average upper limit of https operations per second"` - Years *uint `long:"years" short:"y" description:"Number of years to look back from now" value-name:"YEARS"` + Output string `short:"o" long:"output" description:"File name of the generated report" value-name:"REPORT-FILE"` + Format string `short:"f" long:"format" choice:"json" choice:"html" description:"Format of report" default:"json"` + Insecure bool `long:"insecure" description:"Do not check TLS certificates from provider"` + ClientCert *string `long:"client-cert" description:"TLS client certificate file (PEM encoded data)" value-name:"CERT-FILE"` + ClientKey *string `long:"client-key" description:"TLS client private key file (PEM encoded data)" value-name:"KEY-FILE"` + Version bool `long:"version" description:"Display version of the binary"` + Verbose bool `long:"verbose" short:"v" description:"Verbose output"` + Rate *float64 `long:"rate" short:"r" description:"The average upper limit of https operations per second"` + Years *uint `long:"years" short:"y" description:"Number of years to look back from now" value-name:"YEARS"` + ExtraHeader http.Header `long:"header" short:"H" description:"One or more extra HTTP header fields"` clientCerts []tls.Certificate } diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index 32eb991..46591ad 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -351,24 +351,30 @@ func (p *processor) httpClient() util.Client { TLSClientConfig: &tlsConfig, } - var client util.Client + client := util.Client(&hClient) + // Add extra headers. + if len(p.opts.ExtraHeader) > 0 { + client = &util.HeaderClient{ + Client: client, + Header: p.opts.ExtraHeader, + } + } + + // Add optional URL logging. if p.opts.Verbose { - client = &util.LoggingClient{Client: &hClient} - } else { - client = &hClient + client = &util.LoggingClient{Client: client} } - if p.opts.Rate == nil { - p.client = client - return client - } - - p.client = &util.LimitingClient{ - Client: client, - Limiter: rate.NewLimiter(rate.Limit(*p.opts.Rate), 1), + // Add optional rate limiting. + if p.opts.Rate != nil { + client = &util.LimitingClient{ + Client: client, + Limiter: rate.NewLimiter(rate.Limit(*p.opts.Rate), 1), + } } + p.client = client return p.client } diff --git a/cmd/csaf_downloader/downloader.go b/cmd/csaf_downloader/downloader.go index 6674ef7..3bb2f93 100644 --- a/cmd/csaf_downloader/downloader.go +++ b/cmd/csaf_downloader/downloader.go @@ -64,24 +64,30 @@ func (d *downloader) httpClient() util.Client { } } - var client util.Client + client := util.Client(&hClient) + // Add extra headers. + if len(d.opts.ExtraHeader) > 0 { + client = &util.HeaderClient{ + Client: client, + Header: d.opts.ExtraHeader, + } + } + + // Add optional URL logging. if d.opts.Verbose { - client = &util.LoggingClient{Client: &hClient} - } else { - client = &hClient + client = &util.LoggingClient{Client: client} } - if d.opts.Rate == nil { - d.client = client - return client - } - - d.client = &util.LimitingClient{ - Client: client, - Limiter: rate.NewLimiter(rate.Limit(*d.opts.Rate), 1), + // Add optional rate limiting. + if d.opts.Rate != nil { + client = &util.LimitingClient{ + Client: client, + Limiter: rate.NewLimiter(rate.Limit(*d.opts.Rate), 1), + } } + d.client = client return d.client } diff --git a/cmd/csaf_downloader/main.go b/cmd/csaf_downloader/main.go index 58ebe5e..38afe7d 100644 --- a/cmd/csaf_downloader/main.go +++ b/cmd/csaf_downloader/main.go @@ -12,6 +12,7 @@ package main import ( "fmt" "log" + "net/http" "os" "github.com/csaf-poc/csaf_distribution/util" @@ -19,11 +20,12 @@ import ( ) type options struct { - Directory *string `short:"d" long:"directory" description:"Directory to store the downloaded files in"` - Insecure bool `long:"insecure" description:"Do not check TLS certificates from provider"` - Version bool `long:"version" description:"Display version of the binary"` - Verbose bool `long:"verbose" short:"v" description:"Verbose output"` - Rate *float64 `long:"rate" short:"r" description:"The average upper limit of https operations per second"` + Directory *string `short:"d" long:"directory" description:"Directory to store the downloaded files in"` + Insecure bool `long:"insecure" description:"Do not check TLS certificates from provider"` + Version bool `long:"version" description:"Display version of the binary"` + Verbose bool `long:"verbose" short:"v" description:"Verbose output"` + Rate *float64 `long:"rate" short:"r" description:"The average upper limit of https operations per second"` + ExtraHeader http.Header `long:"header" short:"H" description:"One or more extra HTTP header fields"` } func errCheck(err error) { diff --git a/docs/csaf_checker.md b/docs/csaf_checker.md index 39c265d..5e134c0 100644 --- a/docs/csaf_checker.md +++ b/docs/csaf_checker.md @@ -15,13 +15,14 @@ Application Options: -v, --verbose Verbose output -r, --rate= The average upper limit of https operations per second -y, --years=YEARS Number of years to look back from now + -H, --header= One or more extra HTTP header fields Help Options: -h, --help Show this help message ``` Usage example: -` ./csaf_checker example.com -f html --rate=5.3 -o check-results.html` +` ./csaf_checker example.com -f html --rate=5.3 -H apikey:SECRET -o check-results.html` Each performed check has a return type of either 0,1 or 2: ``` diff --git a/docs/csaf_downloader.md b/docs/csaf_downloader.md index c88a35b..56d54ce 100644 --- a/docs/csaf_downloader.md +++ b/docs/csaf_downloader.md @@ -4,8 +4,7 @@ A tool to download CSAF content from a specific domain/provider. ### Usage ``` -Usage: - csaf_downloader [OPTIONS] domain... +csaf_downloader [OPTIONS] domain... Application Options: -d, --directory= Directory to store the downloaded files in @@ -13,6 +12,7 @@ Application Options: --version Display version of the binary -v, --verbose Verbose output -r, --rate= The average upper limit of https operations per second + -H, --header= One or more extra HTTP header fields Help Options: -h, --help Show this help message diff --git a/util/client.go b/util/client.go index d6cd150..2a2b32e 100644 --- a/util/client.go +++ b/util/client.go @@ -14,6 +14,7 @@ import ( "log" "net/http" "net/url" + "strings" "golang.org/x/time/rate" ) @@ -38,6 +39,64 @@ type LimitingClient struct { Limiter *rate.Limiter } +// HeaderClient adds extra HTTP header fields to the ou going requests. +type HeaderClient struct { + Client + Header http.Header +} + +// Do implements the respective method of the [Client] interface. +func (hc *HeaderClient) Do(req *http.Request) (*http.Response, error) { + // Maybe this overly careful but this minimizes + // potential side effects in the caller. + orig := req.Header + defer func() { req.Header = orig }() + + // Work on a copy. + req.Header = req.Header.Clone() + + for key, values := range hc.Header { + for _, v := range values { + req.Header.Add(key, v) + } + } + return hc.Client.Do(req) +} + +// Get implements the respective method of the [Client] interface. +func (hc *HeaderClient) Get(url string) (*http.Response, error) { + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, err + } + return hc.Do(req) +} + +// Head implements the respective method of the [Client] interface. +func (hc *HeaderClient) Head(url string) (*http.Response, error) { + req, err := http.NewRequest(http.MethodHead, url, nil) + if err != nil { + return nil, err + } + return hc.Do(req) +} + +// Post implements the respective method of the [Client] interface. +func (hc *HeaderClient) Post(url, contentType string, body io.Reader) (*http.Response, error) { + req, err := http.NewRequest(http.MethodPost, url, nil) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", contentType) + return hc.Do(req) +} + +// PostForm implements the respective method of the [Client] interface. +func (hc *HeaderClient) PostForm(url string, data url.Values) (*http.Response, error) { + return hc.Post( + url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) +} + // Do implements the respective method of the Client interface. func (lc *LoggingClient) Do(req *http.Request) (*http.Response, error) { log.Printf("[DO]: %s\n", req.URL.String())