Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions config/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package config

import (
"slices"
"time"
)

// API holds configuration for the Clair API services.
type API struct {
// V1 is the configuration for the HTTP v1 API.
V1 APIv1 `yaml:"v1,omitempty" json:"v1,omitempty"`
}

func (a *API) validate(_ Mode) ([]Warning, error) {
// TODO(hank) When there's an "UpdaterMode," don't bother with validating
// the API configurations.

enabled := slices.ContainsFunc([]*bool{}, func(e *bool) bool {
return e != nil && *e
})
// With multiple versions, the highest one should be the default, probably.
if !enabled {
a.V1.Enabled = &[]bool{true}[0] // TODO(go1.26) Use the "new(true)" syntax.
}

return nil, nil
}

// APIv1 holds configuration values for the HTTP v1 API.
type APIv1 struct {
// Enabled configures enabling the API server at all.
// The set of API endpoints served by any one process depends on the mode
// the process is started in.
//
// If unset, defaults to "true".
Enabled *bool `yaml:"enabled" json:"enabled"`

// Network configures the network type to be used for serving API requests.
//
// If unset, [DefaultAPIv1Network] will be used.
// See also: [net.Dial].
Network string `yaml:"network" json:"network"`

// Address configures the address to listen on for serving API requests.
// The format depends on the "network" member.
//
// If unset, [DefaultAPIv1Address] will be used.
// See also: [net.Dial].
Address string `yaml:"address" json:"address"`

// IdleTimeout configures whether the Clair process should exit after not
// handling any requests for a specified non-zero duration.
IdleTimeout Duration `yaml:"idle_timeout" json:"idle_timeout"`

// TLS configures HTTPS support.
//
// Note that any non-trivial deployment means the certificate provided here
// will need to be for the name the load balancer used to connect to a given
// Clair instance.
TLS *TLS `yaml:"tls,omitempty" json:"tls,omitempty"`
}

func (a *APIv1) validate(_ Mode) ([]Warning, error) {
if a.Enabled == nil || !*a.Enabled {
return nil, nil
}
if a.Network == "" {
a.Network = DefaultAPIv1Network
}
if a.Address == "" {
a.Address = DefaultAPIv1Address
}

return a.lint()
}

func (a *APIv1) lint() (ws []Warning, err error) {
if a.Network == "" {
ws = append(ws, Warning{
path: ".network",
msg: `listen network not provided, default will be used`,
})
}
if a.Address == "" {
ws = append(ws, Warning{
path: ".address",
msg: `listen address not provided, default will be used`,
})
}

switch dur := time.Duration(a.IdleTimeout); {
case dur == 0: // OK, disabled.
case dur < (2 * time.Minute):
ws = append(ws, Warning{
path: ".idle_timeout",
msg: `idle timeout seems short, may cause frequent startups`,
})
default: // OK, reasonably long.
}

return ws, nil
}
59 changes: 31 additions & 28 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,41 @@ type Config struct {
// TLS configures HTTPS support.
//
// Note that any non-trivial deployment means the certificate provided here
// will need to be for the name the load balancer uses to connect to a given
// Clair instance.
// will need to be for the name used by the load balancer for a given Clair
// instance.
//
// This is not used for outgoing requests; setting the SSL_CERT_DIR
// environment variable is the recommended way to do that. The release
// container has `/var/run/certs` added to the list already.
// Deprecated: Use the [API] configuration hierarchy.
TLS *TLS `yaml:"tls,omitempty" json:"tls,omitempty"`
// Sets which mode the clair instance will run.
Mode Mode `yaml:"-" json:"-"`
// A string in <host>:<port> format where <host> can be an empty string.
//
// exposes Clair node's functionality to the network.
// see /openapi/v1 for api spec.
// Exposes Clair node's functionality to the network.
// See /openapi/v1 for the API spec.
//
// Deprecated: Use the [API] configuration hierarchy.
HTTPListenAddr string `yaml:"http_listen_addr" json:"http_listen_addr"`
// A string in <host>:<port> format where <host> can be an empty string.
//
// exposes Clair's metrics and health endpoints.
// Exposes Clair's metrics and health endpoints.
//
// Deprecated: Use the [Introspection] configuration hierarchy.
IntrospectionAddr string `yaml:"introspection_addr" json:"introspection_addr"`
// Set the logging level.
LogLevel LogLevel `yaml:"log_level" json:"log_level"`
Indexer Indexer `yaml:"indexer,omitempty" json:"indexer,omitempty"`
Matcher Matcher `yaml:"matcher,omitempty" json:"matcher,omitempty"`
Matchers Matchers `yaml:"matchers,omitempty" json:"matchers,omitempty"`
Updaters Updaters `yaml:"updaters,omitempty" json:"updaters,omitempty"`
Notifier Notifier `yaml:"notifier,omitempty" json:"notifier,omitempty"`
Auth Auth `yaml:"auth,omitempty" json:"auth,omitempty"`
Trace Trace `yaml:"trace,omitempty" json:"trace,omitempty"`
Metrics Metrics `yaml:"metrics,omitempty" json:"metrics,omitempty"`
LogLevel LogLevel `yaml:"log_level" json:"log_level"`
Indexer Indexer `yaml:"indexer,omitempty" json:"indexer,omitempty"`
Matcher Matcher `yaml:"matcher,omitempty" json:"matcher,omitempty"`
Matchers Matchers `yaml:"matchers,omitempty" json:"matchers,omitempty"`
Updaters Updaters `yaml:"updaters,omitempty" json:"updaters,omitempty"`
Notifier Notifier `yaml:"notifier,omitempty" json:"notifier,omitempty"`
Auth Auth `yaml:"auth,omitempty" json:"auth,omitempty"`
Trace Trace `yaml:"trace,omitempty" json:"trace,omitempty"`
Metrics Metrics `yaml:"metrics,omitempty" json:"metrics,omitempty"`
API API `yaml:"api,omitempty" json:"api,omitempty"`
Introspection Introspection `yaml:"introspection,omitempty" json:"introspection,omitempty"`
}

func (c *Config) validate(mode Mode) ([]Warning, error) {
if c.HTTPListenAddr == "" {
c.HTTPListenAddr = DefaultAddress
}
if c.Matcher.DisableUpdaters {
c.Updaters.Sets = []string{}
}
Expand All @@ -55,23 +56,25 @@ func (c *Config) validate(mode Mode) ([]Warning, error) {
default:
return nil, fmt.Errorf("unknown mode: %q", mode)
}
if _, _, err := net.SplitHostPort(c.HTTPListenAddr); err != nil {
return nil, err
if c.HTTPListenAddr != "" {
if _, _, err := net.SplitHostPort(c.HTTPListenAddr); err != nil {
return nil, err
}
}
return c.lint()
}

func (c *Config) lint() (ws []Warning, err error) {
if c.HTTPListenAddr == "" {
if c.HTTPListenAddr != "" {
ws = append(ws, Warning{
path: ".http_listen_addr",
msg: `http listen address not provided, default will be used`,
path: ".http_listen_addr",
inner: fmt.Errorf(`configuration via $.api.v1 is preferred: %w`, ErrDeprecated),
})
}
if c.IntrospectionAddr == "" {
if c.IntrospectionAddr != "" {
ws = append(ws, Warning{
path: ".introspection_addr",
msg: `introspection address not provided, default will be used`,
path: ".introspection_addr",
inner: fmt.Errorf(`configuration via $.introspection is preferred: %w`, ErrDeprecated),
})
}
return ws, nil
Expand Down
21 changes: 19 additions & 2 deletions config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,20 @@ import "time"

// These are defaults, used in the documented spots.
const (
// DefaultAddress is used if an "http_listen_addr" is not provided in the config.
DefaultAddress = ":6060"
// DefaultAPIv1Network is used if a network for the v1 API is not provided
// in the config.
DefaultAPIv1Network = "tcp"
// DefaultAPIv1Address is used if an address for the v1 API is not provided
// in the config.
DefaultAPIv1Address = ":6060"

// DefaultIntrospectionNetwork is used if a network for the Introspection
// server is not provided in the config.
DefaultIntrospectionNetwork = "tcp"
// DefaultIntrospectionAddress is used if an address for the Introspection
// server is not provided in the config.
DefaultIntrospectionAddress = ":8089"

// DefaultScanLockRetry is the default retry period for attempting locks
// during the indexing process. Its name is a historical accident.
DefaultScanLockRetry = 1
Expand All @@ -23,3 +35,8 @@ const (
// outstanding notifications at this rate.
DefaultNotifierDeliveryInterval = 1 * time.Hour
)

// DefaultAddress is the previous name of [DefaultAPIv1Address].
//
// Deprecated: Refer to [DefaultAPIv1Address] directly.
const DefaultAddress = DefaultAPIv1Address
63 changes: 63 additions & 0 deletions config/introspection.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,69 @@ package config

import "fmt"

// Introspection is the configuration for Clair's introspection and debugging
// endpoints.
type Introspection struct {
// Enabled configures enabling the Introspection server at all.
//
// If unset, defaults to "true".
Enabled *bool `yaml:"enabled" json:"enabled"`

// Required configures Clair to exit with an error if the Introspection
// server fails to start.
//
// Defaults to "false".
Required bool `yaml:"required" json:"required"`

// Network configures the network type to be used for serving Introspection
// requests.
//
// If unset, [DefaultIntrospectionNetwork] will be used.
// See also: [net.Dial].
Network string `yaml:"network" json:"network"`

// Address configures the address to listen on for serving Introspection
// requests. The format depends on the "network" member.
//
// If unset, [DefaultIntrospectionAddress] will be used.
// See also: [net.Dial].
Address string `yaml:"address" json:"address"`
}

func (i *Introspection) validate(_ Mode) ([]Warning, error) {
switch {
case i.Enabled == nil:
i.Enabled = &[]bool{true}[0] // TODO(go1.26) Use the "new(true)" syntax.
case !*i.Enabled:
return nil, nil
}
if i.Network == "" {
i.Network = DefaultIntrospectionNetwork
}
if i.Address == "" {
i.Address = DefaultIntrospectionAddress
}

return i.lint()
}

func (i *Introspection) lint() (ws []Warning, err error) {
if i.Network == "" {
ws = append(ws, Warning{
path: ".network",
msg: `listen network not provided, default will be used`,
})
}
if i.Address == "" {
ws = append(ws, Warning{
path: ".address",
msg: `listen address not provided, default will be used`,
})
}

return ws, nil
}

// Trace specifies how to configure Clair's tracing support.
//
// The "Name" key must match the provider to use.
Expand Down
6 changes: 4 additions & 2 deletions config/lint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ func ExampleLint() {
}
// Output:
// error: <nil>
// warning: http listen address not provided, default will be used (at $.http_listen_addr)
// warning: introspection address not provided, default will be used (at $.introspection_addr)
// warning: connection string is empty and no relevant environment variables found (at $.indexer.connstring)
// warning: connection string is empty and no relevant environment variables found (at $.matcher.connstring)
// warning: updater period is very aggressive: most sources are updated daily (at $.matcher.period)
// warning: update garbage collection is off (at $.matcher.update_retention)
// warning: connection string is empty and no relevant environment variables found (at $.notifier.connstring)
// warning: interval is very fast: may result in increased workload (at $.notifier.poll_interval)
// warning: interval is very fast: may result in increased workload (at $.notifier.delivery_interval)
// warning: listen network not provided, default will be used (at $.api.v1.network)
// warning: listen address not provided, default will be used (at $.api.v1.address)
// warning: listen network not provided, default will be used (at $.introspection.network)
// warning: listen address not provided, default will be used (at $.introspection.address)
}
12 changes: 6 additions & 6 deletions config/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,23 @@ import (
//
// Using the environment variables "SSL_CERT_DIR" or "SSL_CERT_FILE" or
// modifying the system's trust store are the ways to modify root CAs for all
// outgoing TLS connections.
// outgoing TLS connections. The Clair release containers have `/var/run/certs`
// added to the list already.
type TLS struct {
// The filesystem path where a root CA can be read.
//
// This can also be controlled by the SSL_CERT_FILE and SSL_CERT_DIR
// environment variables, or adding the relevant certs to the system trust
// store.
// Deprecated: Use the "SSL_CERT_FILE" or "SSL_CERT_DIR" environment
// variables, or add the relevant certs to the system trust store.
RootCA string `yaml:"root_ca" json:"root_ca"`
// The filesystem path where a TLS certificate can be read.
Cert string `yaml:"cert" json:"cert"`
// The filesystem path where a TLS private key can be read.
Key string `yaml:"key" json:"key"`
}

// Config returns a tls.Config modified according to the TLS struct.
// Config returns a [tls.Config] modified according to the TLS struct.
//
// If the *TLS is nil, a default tls.Config is returned.
// If the receiver is nil, a default [tls.Config] is returned.
func (t *TLS) Config() (*tls.Config, error) {
var cfg tls.Config
if t == nil {
Expand Down
Loading
Loading