diff --git a/cmd/csaf_aggregator/config.go b/cmd/csaf_aggregator/config.go index b4205a4c..8e79021c 100644 --- a/cmd/csaf_aggregator/config.go +++ b/cmd/csaf_aggregator/config.go @@ -20,6 +20,7 @@ import ( "github.com/ProtonMail/gopenpgp/v2/crypto" "github.com/csaf-poc/csaf_distribution/v2/csaf" + "github.com/csaf-poc/csaf_distribution/v2/internal/certs" "github.com/csaf-poc/csaf_distribution/v2/internal/filter" "github.com/csaf-poc/csaf_distribution/v2/internal/options" "github.com/csaf-poc/csaf_distribution/v2/util" @@ -52,6 +53,15 @@ type provider struct { // IgnorePattern is a list of patterns of advisory URLs to be ignored. IgnorePattern []string `toml:"ignorepattern"` + + // ExtraHeader adds extra HTTP header fields to client + ExtraHeader http.Header `toml:"header"` + + ClientCert *string `toml:"client_cert"` + ClientKey *string `toml:"client_key"` + ClientPassphrase *string `toml:"client_passphrase"` + + clientCerts []tls.Certificate ignorePattern filter.PatternMatcher } @@ -74,6 +84,10 @@ type config struct { Passphrase *string `toml:"passphrase"` AllowSingleProvider bool `toml:"allow_single_provider"` + ClientCert *string `toml:"client_cert"` + ClientKey *string `toml:"client_key"` + ClientPassphrase *string `toml:"client_passphrase"` + // LockFile tries to lock to a given file. LockFile *string `toml:"lock_file"` @@ -97,13 +111,18 @@ type config struct { // IgnorePattern is a list of patterns of advisory URLs to be ignored. IgnorePattern []string `toml:"ignorepattern"` - ignorePattern filter.PatternMatcher + + // ExtraHeader adds extra HTTP header fields to client + ExtraHeader http.Header `toml:"header"` Config string `short:"c" long:"config" description:"Path to config TOML file" value-name:"TOML-FILE" toml:"-"` keyMu sync.Mutex key *crypto.Key keyErr error + + clientCerts []tls.Certificate + ignorePattern filter.PatternMatcher } // configPaths are the potential file locations of the config file. @@ -217,20 +236,44 @@ func (c *config) privateOpenPGPKey() (*crypto.Key, error) { func (c *config) httpClient(p *provider) util.Client { hClient := http.Client{} + + var tlsConfig tls.Config if p.Insecure != nil && *p.Insecure || c.Insecure != nil && *c.Insecure { - hClient.Transport = &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - } + tlsConfig.InsecureSkipVerify = true } - var client util.Client + // Use client certs if needed. + switch { + // Provider has precedence over global. + case len(p.clientCerts) != 0: + tlsConfig.Certificates = p.clientCerts + case len(c.clientCerts) != 0: + tlsConfig.Certificates = c.clientCerts + } + + hClient.Transport = &http.Transport{ + TLSClientConfig: &tlsConfig, + } + + client := util.Client(&hClient) + + // Add extra headers. + switch { + // Provider has precedence over global. + case len(p.ExtraHeader) > 0: + client = &util.HeaderClient{ + Client: client, + Header: p.ExtraHeader, + } + case len(c.ExtraHeader) > 0: + client = &util.HeaderClient{ + Client: client, + Header: c.ExtraHeader, + } + } if c.Verbose { - client = &util.LoggingClient{Client: &hClient} - } else { - client = &hClient + client = &util.LoggingClient{Client: client} } if p.Rate == nil && c.Rate == nil { @@ -325,7 +368,7 @@ func (c *config) setDefaults() { func (p *provider) compileIgnorePatterns() error { pm, err := filter.NewPatternMatcher(p.IgnorePattern) if err != nil { - return err + return fmt.Errorf("invalid ignore patterns for %q: %w", p.Name, err) } p.ignorePattern = pm return nil @@ -342,30 +385,59 @@ func (c *config) compileIgnorePatterns() error { // Compile the patterns of the providers. for _, p := range c.Providers { if err := p.compileIgnorePatterns(); err != nil { - return fmt.Errorf("invalid ignore patterns for %q: %w", p.Name, err) + return err } } return nil } -// prepare prepares internal state of a loaded configuration. -func (c *config) prepare() error { - - if len(c.Providers) == 0 { - return errors.New("no providers given in configuration") +// prepareCertificates loads the provider specific client side certificates +// used by the HTTP client. +func (p *provider) prepareCertificates() error { + cert, err := certs.LoadCertificate( + p.ClientCert, p.ClientKey, p.ClientPassphrase) + if err != nil { + return fmt.Errorf("invalid certificates for %q: %w", p.Name, err) } + p.clientCerts = cert + return nil +} - if err := c.compileIgnorePatterns(); err != nil { +// prepareCertificates loads the client side certificates used by the HTTP client. +func (c *config) prepareCertificates() error { + // Global certificates + cert, err := certs.LoadCertificate( + c.ClientCert, c.ClientKey, c.ClientPassphrase) + if err != nil { return err } - - if err := c.Aggregator.Validate(); err != nil { - return err + c.clientCerts = cert + // Provider certificates + for _, p := range c.Providers { + if err := p.prepareCertificates(); err != nil { + return err + } } + return nil +} - if err := c.checkProviders(); err != nil { - return err +// prepare prepares internal state of a loaded configuration. +func (c *config) prepare() error { + + if len(c.Providers) == 0 { + return errors.New("no providers given in configuration") } - return c.checkMirror() + for _, prepare := range []func() error{ + c.prepareCertificates, + c.compileIgnorePatterns, + c.Aggregator.Validate, + c.checkProviders, + c.checkMirror, + } { + if err := prepare(); err != nil { + return err + } + } + return nil } diff --git a/docs/csaf_aggregator.md b/docs/csaf_aggregator.md index 2a9e4829..c94dd90f 100644 --- a/docs/csaf_aggregator.md +++ b/docs/csaf_aggregator.md @@ -95,6 +95,10 @@ interim_years // limiting the years for which interim documents are se verbose // print more diagnostic output, e.g. https requests (default false) allow_single_provider // debugging option (default false) ignorepattern // patterns of advisory URLs to be ignored +client_cert // path to client certificate to access access-protected advisories +client_key // path to client key to access access-protected advisories +client_passphrase // client passphrase to access access-protected advisories +header // adds extra HTTP header fields to the client ``` Next we have two TOML _tables_: @@ -125,6 +129,10 @@ update_interval create_service_document categories ignorepattern +client_cert +client_key +client_passphrase +header ``` Where valid `name` and `domain` settings are required. @@ -196,6 +204,10 @@ insecure = true # rate = 1.2 # insecure = true write_indices = true + client_cert = "./../devca1/testclient1.crt" + client_key = "./../devca1/testclient1-key.pem" +# client_passphrase = +# header = [[providers]] name = "local-dev-provider3" diff --git a/docs/examples/aggregator.toml b/docs/examples/aggregator.toml index 80f9b8a9..4abbba3a 100644 --- a/docs/examples/aggregator.toml +++ b/docs/examples/aggregator.toml @@ -38,6 +38,10 @@ insecure = true # rate = 1.2 # insecure = true write_indices = true + client_cert = "./../devca1/testclient1.crt" + client_key = "./../devca1/testclient1-key.pem" +# client_passphrase = +# header = [[providers]] name = "local-dev-provider3"