configuration/configuration.go (731 lines of code) (raw):

package configuration import ( "errors" "fmt" "io" "net/http" "os" "reflect" "regexp" "strings" "time" ) // Configuration is a versioned registry configuration, intended to be provided by a yaml file, and // optionally modified by environment variables. // // Note that yaml field names should never include _ characters, since this is the separator used // in environment variable names. type Configuration struct { // Version is the version which defines the format of the rest of the configuration Version Version `yaml:"version"` // Log supports setting various parameters related to the logging // subsystem. Log struct { // AccessLog configures access logging. AccessLog struct { // Disabled disables access logging. Disabled bool `yaml:"disabled,omitempty"` // Formatter overrides the default formatter with another. Options include "text" and "json". The default // is "json". Formatter accessLogFormat `yaml:"formatter,omitempty"` } `yaml:"accesslog,omitempty"` // Level is the granularity at which registry operations are logged. // Options include "error", "warn", "info", "debug" and "trace". The // default is "info". Level Loglevel `yaml:"level,omitempty"` // Formatter sets the format of logging output. Options include "text" and "json". The default is "json". Formatter logFormat `yaml:"formatter,omitempty"` // Output sets the output destination. Options include "stderr" and // "stdout". The default is "stdout". Output logOutput `yaml:"output,omitempty"` // Fields allows users to specify static string fields to include in // the logger context. Fields map[string]any `yaml:"fields,omitempty"` } // Loglevel is the level at which registry operations are logged. // // Deprecated: Use Log.Level instead. Loglevel Loglevel `yaml:"loglevel,omitempty"` // Storage is the configuration for the registry's storage driver Storage Storage `yaml:"storage"` // Database is the configuration for the registry's metadata database Database Database `yaml:"database"` // Auth allows configuration of various authorization methods that may be // used to gate requests. Auth Auth `yaml:"auth,omitempty"` // Middleware lists all middlewares to be used by the registry. Middleware map[string][]Middleware `yaml:"middleware,omitempty"` // Reporting is the configuration for error reporting Reporting Reporting `yaml:"reporting,omitempty"` // Profiling configures external profiling services. Profiling Profiling `yaml:"profiling,omitempty"` // HTTP contains configuration parameters for the registry's http // interface. HTTP struct { // Addr specifies the bind address for the registry instance. Addr string `yaml:"addr,omitempty"` // Net specifies the net portion of the bind address. A default empty value means tcp. Net string `yaml:"net,omitempty"` // Host specifies an externally-reachable address for the registry, as a fully // qualified URL. Host string `yaml:"host,omitempty"` Prefix string `yaml:"prefix,omitempty"` // Secret specifies the secret key which HMAC tokens are created with. Secret string `yaml:"secret,omitempty"` // RelativeURLs specifies that relative URLs should be returned in // Location headers RelativeURLs bool `yaml:"relativeurls,omitempty"` // Amount of time to wait for connection to drain before shutting down when registry // receives a stop signal DrainTimeout time.Duration `yaml:"draintimeout,omitempty"` // TLS instructs the http server to listen with a TLS configuration. // This only support simple tls configuration with a cert and key. // Mostly, this is useful for testing situations or simple deployments // that require tls. If more complex configurations are required, use // a proxy or make a proposal to add support here. TLS TLS `yaml:"tls,omitempty"` // Headers is a set of headers to include in HTTP responses. A common // use case for this would be security headers such as // Strict-Transport-Security. The map keys are the header names, and // the values are the associated header payloads. Headers http.Header `yaml:"headers,omitempty"` // Debug configures the http debug interface, if specified. This can // include services such as pprof, expvar and other data that should // not be exposed externally. Left disabled by default. Debug struct { // Addr specifies the bind address for the debug server. Addr string `yaml:"addr,omitempty"` // TLS configuration for the debug server. TLS DebugTLS `yaml:"tls,omitempty"` // Prometheus configures the Prometheus telemetry endpoint. Prometheus struct { Enabled bool `yaml:"enabled,omitempty"` Path string `yaml:"path,omitempty"` } `yaml:"prometheus,omitempty"` // Pprof configures a pprof server, which listens at `/debug/pprof`. Pprof struct { Enabled bool `yaml:"enabled,omitempty"` } `yaml:"pprof,omitempty"` } `yaml:"debug,omitempty"` // HTTP2 configuration options HTTP2 struct { // Specifies whether the registry should disallow clients attempting // to connect via http2. If set to true, only http/1.1 is supported. Disabled bool `yaml:"disabled,omitempty"` } `yaml:"http2,omitempty"` } `yaml:"http,omitempty"` // Notifications specifies configuration about various endpoint to which // registry events are dispatched. Notifications Notifications `yaml:"notifications,omitempty"` // Redis configures the redis instance(s) available to the application. Separate Redis instances for different // persistence classes (e.g. caching) can be used. Redis Redis `yaml:"redis,omitempty"` Health Health `yaml:"health,omitempty"` // Validation configures validation options for the registry. Validation struct { // Enabled enables the other options in this section. // This field is deprecated in favor of Disabled. Enabled bool `yaml:"enabled,omitempty"` // Disabled disables the other options in this section. Disabled bool `yaml:"disabled,omitempty"` // Manifests configures manifest validation. Manifests struct { // ReferenceLimit is the maximum number of blobs or manifests that manifests may reference. Set to zero to disable. ReferenceLimit int `yaml:"referencelimit,omitempty"` // PayloadSizeLimit is the maximum data size in bytes of manifest payloads. Set to zero to disable. PayloadSizeLimit int `yaml:"payloadsizelimit,omitempty"` // URLs configures validation for URLs in pushed manifests. URLs struct { // Allow specifies regular expressions (https://godoc.org/regexp/syntax) // that URLs in pushed manifests must match. Allow []string `yaml:"allow,omitempty"` // Deny specifies regular expressions (https://godoc.org/regexp/syntax) // that URLs in pushed manifests must not match. Deny []string `yaml:"deny,omitempty"` } `yaml:"urls,omitempty"` } `yaml:"manifests,omitempty"` } `yaml:"validation,omitempty"` // Policy configures registry policy options. Policy struct { // Repository configures policies for repositories Repository struct { // Classes is a list of repository classes which the // registry allows content for. This class is matched // against the configuration media type inside uploaded // manifests. When non-empty, the registry will enforce // the class in authorized resources. Classes []string `yaml:"classes"` } `yaml:"repository,omitempty"` } `yaml:"policy,omitempty"` GC GC `yaml:"gc,omitempty"` RateLimiter RateLimiter `yaml:"rate_limiter,omitempty"` } // TLS specifies the settings for the http server to listen with a TLS configuration. type TLS struct { // Certificate specifies the path to an x509 certificate file to // be used for TLS. Certificate string `yaml:"certificate,omitempty"` // Key specifies the path to the x509 key file, which should // contain the private portion for the file specified in // Certificate. Key string `yaml:"key,omitempty"` // Specifies the CA certs for client authentication // A file may contain multiple CA certificates encoded as PEM ClientCAs []string `yaml:"clientcas,omitempty"` // Specifies the lowest TLS version allowed MinimumTLS string `yaml:"minimumtls,omitempty"` // Specifies a list of cipher suites allowed CipherSuites []string `yaml:"ciphersuites,omitempty"` // LetsEncrypt is used to configuration setting up TLS through // Let's Encrypt instead of manually specifying certificate and // key. If a TLS certificate is specified, the Let's Encrypt // section will not be used. LetsEncrypt struct { // CacheFile specifies cache file to use for lets encrypt // certificates and keys. CacheFile string `yaml:"cachefile,omitempty"` // Email is the email to use during Let's Encrypt registration Email string `yaml:"email,omitempty"` // Hosts specifies the hosts which are allowed to obtain Let's // Encrypt certificates. Hosts []string `yaml:"hosts,omitempty"` } `yaml:"letsencrypt,omitempty"` } // DebugTLS specifies the TLS settings for the HTTP Debug server type DebugTLS struct { // Enabled is only used to check if TLS is enabled for the debug monitoring service Enabled bool `yaml:"enabled,omitempty"` // Certificate specifies the path to an x509 certificate file to // be used for TLS. Certificate string `yaml:"certificate,omitempty"` // Key specifies the path to the x509 key file, which should // contain the private portion for the file specified in // Certificate. Key string `yaml:"key,omitempty"` // Specifies the CA certs for client authentication // A file may contain multiple CA certificates encoded as PEM ClientCAs []string `yaml:"clientcas,omitempty"` // Specifies the lowest TLS version allowed MinimumTLS string `yaml:"minimumtls,omitempty"` } // RedisTLS specifies settings for Redis TLS connections. type RedisTLS struct { // Enabled enables TLS when connecting to the server. Enabled bool `yaml:"enabled,omitempty"` // Insecure disables server name verification when connecting over TLS. Insecure bool `yaml:"insecure,omitempty"` } // RedisPool configures the behavior of the redis connection pool. type RedisPool struct { // Size is the maximum number of socket connections. Default is 10 connections. Size int `yaml:"size,omitempty"` // MaxLifetime is the connection age at which client retires a connection. Default is to not close aged // connections. MaxLifetime time.Duration `yaml:"maxlifetime,omitempty"` // IdleTimeout sets the amount time to wait before closing inactive connections. IdleTimeout time.Duration `yaml:"idletimeout,omitempty"` } type RedisCommon struct { // Enabled is a simple toggle for the Redis connection. Defaults to false. Enabled bool `yaml:"enabled,omitempty"` // Addr specifies the redis instance available to the application. For Sentinel, it should be a list of // addresses separated by commas. Addr string `yaml:"addr,omitempty"` // MainName specifies the main server name. Only for Sentinel connections. MainName string `yaml:"mainname,omitempty"` // Username string to connect as to the Redis instance or cluster. Username string `yaml:"username,omitempty"` // Password string to use when making a connection. Password string `yaml:"password,omitempty"` // DB specifies the database to connect to on the redis instance. DB int `yaml:"db,omitempty"` // DialTimeout is the timeout for establishing connections. DialTimeout time.Duration `yaml:"dialtimeout,omitempty"` // ReadTimeout is the timeout for reading data. ReadTimeout time.Duration `yaml:"readtimeout,omitempty"` // WriteTimeout is the timeout for writing data. WriteTimeout time.Duration `yaml:"writetimeout,omitempty"` // TLS specifies settings for TLS connections. TLS RedisTLS `yaml:"tls,omitempty"` // Pool configures the behavior of the redis connection pool. Pool RedisPool `yaml:"pool,omitempty"` // SentinelUsername configures the username for Sentinel authentication. SentinelUsername string `yaml:"sentinelusername,omitempty"` // SentinelPassword configures the password for Sentinel authentication. SentinelPassword string `yaml:"sentinelpassword,omitempty"` } // Redis configures the redis instance(s) available to the application. Separate Redis instances for different // persistence classes (e.g. caching) can be used. type Redis struct { // The RedisCommon attributes are embedded here to specify settings for a Redis connection for the legacy blob // descriptor cache feature. Although the `RedisCommon.Enabled` attribute is not supported at this level, we simply // ignore it instead of making the config structure even more complex with yet another Redis related struct. RedisCommon `yaml:",inline"` // Cache specifies settings for a Redis connection for caching purposes. Cache RedisCommon `yaml:"cache,omitempty"` // RateLimiter specifies settings for a Redis connection for rate limiting purposes. RateLimiter RedisCommon `yaml:"ratelimiter,omitempty"` // LoadBalancing specifies settings for a Redis connection for database load balancing purposes. LoadBalancing RedisCommon `yaml:"loadbalancing,omitempty"` } // GC configures online Garbage Collection. type GC struct { // Disabled disables the online GC workers. Disabled bool `yaml:"disabled,omitempty"` // NoIdleBackoff disables exponential backoff between worker runs when there was no task to be processed. NoIdleBackoff bool `yaml:"noidlebackoff,omitempty"` // MaxBackoff is the maximum exponential backoff duration used to sleep between worker runs when an error occurs. // Also applied when there are no tasks to be processed unless NoIdleBackoff is `true`. Please note that this is // not the absolute maximum, as a randomized jitter factor of up to 33% is always added. MaxBackoff time.Duration `yaml:"maxbackoff,omitempty"` // ErrorCooldownPeriod is the period of time after an error occurs that the GC workers will continue to // exponentially backoff. If the worker encounters an error while cooling down, the cool down period is extended // again by the configured value. This is useful to ensure that GC workers in multiple registry deployments will // slow down during periods of intermittent errors. Defaults to 0 (no cooldown) by default. ErrorCooldownPeriod time.Duration `yaml:"errorcooldownperiod,omitempty"` // TransactionTimeout is the database transaction timeout for each worker run. Each worker starts a database transaction // at the start. The worker run is canceled if this timeout is exceeded to avoid stalled or long-running // transactions. TransactionTimeout time.Duration `yaml:"transactiontimeout,omitempty"` // Blobs configures the blob worker. Blobs GCBlobs `yaml:"blobs,omitempty"` // Manifests configures the manifest worker. Manifests GCManifests `yaml:"manifests,omitempty"` // ReviewAfter is the minimum amount of time after which the garbage collector should pick up a record for review. // -1 means no wait. Defaults to 24h. ReviewAfter time.Duration `yaml:"reviewafter,omitempty"` } // GCBlobs configures the blob worker. type GCBlobs struct { // Disabled disables the blob worker. Disabled bool `yaml:"disabled,omitempty"` // Interval is the initial sleep interval between each worker run. Interval time.Duration `yaml:"interval,omitempty"` // StorageTimeout is the timeout for storage operations. Used to limit the duration of requests to delete // dangling blobs on the storage backend. StorageTimeout time.Duration `yaml:"storagetimeout,omitempty"` } // GCManifests configures the manifest worker. type GCManifests struct { // Disabled disables the manifest worker. Disabled bool `yaml:"disabled,omitempty"` // Interval is the initial sleep interval between each worker run. Interval time.Duration `yaml:"interval,omitempty"` } // Profiling configures external profiling services. type Profiling struct { Stackdriver StackdriverProfiler `yaml:"stackdriver,omitempty"` } // StackdriverProfiler configures the integration with the Google Stackdriver Profiler. // See https://pkg.go.dev/cloud.google.com/go/profiler?tab=doc#Config for more details about configuration // options. type StackdriverProfiler struct { // Enabled can be set to `true` to enable the Stackdriver profiler. Enabled bool `yaml:"enabled,omitempty"` // Service is the name of the service under which the profiled data will be recorded and exposed. Defaults to the // value of the `GAE_SERVICE` environment variable or instance metadata. Service string `yaml:"service,omitempty"` // ServiceVersion is the version of the service. Defaults to the `GAE_VERSION` environment variable if that is set, // or to empty string otherwise. ServiceVersion string `yaml:"serviceversion,omitempty"` // ProjectID is the project ID. Defaults to the `GOOGLE_CLOUD_PROJECT` environment variable or instance metadata. ProjectID string `yaml:"projectid,omitempty"` // KeyFile is the path of a private service account key file in JSON format used for Service Account Authentication. // Defaults to the `GOOGLE_APPLICATION_CREDENTIALS` environment variable or instance metadata. KeyFile string `yaml:"keyfile,omitempty"` } // Database is the configuration for the registry's metadata database type Database struct { // Enabled can be used to enable or bypass the metadata database Enabled bool `yaml:"enabled"` // Host is the database server hostname Host string `yaml:"host"` // Port is the database server port Port int `yaml:"port"` // Username is the database username User string `yaml:"user"` // Password is the database password Password string `yaml:"password"` // Name is the database name DBName string `yaml:"dbname"` // SSLMode is the SSL mode: // http://www.postgresql.cn/docs/current/libpq-ssl.html#LIBPQ-SSL-SSLMODE-STATEMENTS SSLMode string `yaml:"sslmode"` // SSLCert is the PEM encoded certificate file path. SSLCert string `yaml:"sslcert"` // SSLKey is the PEM encoded key file path. SSLKey string `yaml:"sslkey"` // SSLRootCert is the PEM encoded root certificate file path. SSLRootCert string `yaml:"sslrootcert"` // Pool configures the behavior of the database connection pool. Pool struct { // MaxIdle sets the maximum number of connections in the idle connection pool. If MaxOpen is less than MaxIdle, // then MaxIdle is reduced to match the MaxOpen limit. Defaults to 0 (no idle connections). MaxIdle int `yaml:"maxidle,omitempty"` // MaxOpen sets the maximum number of open connections to the database. If MaxOpen is less than MaxIdle, then // MaxIdle is reduced to match the MaxOpen limit. Defaults to 0 (unlimited). MaxOpen int `yaml:"maxopen,omitempty"` // MaxLifetime sets the maximum amount of time a connection may be reused. Expired connections may be closed // lazily before reuse. Defaults to 0 (unlimited). MaxLifetime time.Duration `yaml:"maxlifetime,omitempty"` // MaxIdleTime is the maximum amount of time a connection may be idle. Expired connections may be closed lazily // before reuse. Defaults to 0 (unlimited). MaxIdleTime time.Duration `yaml:"maxidletime,omitempty"` } `yaml:"pool,omitempty"` // Maximum time to wait for a connection. Zero or not specified means waiting indefinitely. ConnectTimeout time.Duration `yaml:"connecttimeout,omitempty"` // DrainTimeout time to wait to drain all connections on shutdown. Zero or not specified means waiting indefinitely. DrainTimeout time.Duration `yaml:"draintimeout,omitempty"` // PreparedStatements can be used to enable prepared statements. Defaults to false. PreparedStatements bool `yaml:"preparedstatements,omitempty"` // Primary is the primary database server's hostname Primary string `yaml:"primary,omitempty"` BackgroundMigrations BackgroundMigrations `yaml:"backgroundmigrations,omitempty"` // LoadBalancing can be used to enable and configure database load balancing. LoadBalancing DatabaseLoadBalancing `yaml:"loadbalancing,omitempty"` } // BackgroundMigrations represents the configuration for the asynchronous batched background migrations in the registry. type BackgroundMigrations struct { // Enabled can be used to enable or bypass the asynchronous batched background migration process Enabled bool `yaml:"enabled"` // MaxJobRetries is the maximum number of times a job is tried before it is marked as failed (defaults to 0 - no job retry allowed). MaxJobRetries int `yaml:"maxjobretries,omitempty"` // JobInterval is the duration to wait between checks for eligible BBM jobs and acquiring the BBM lock (defaults to `1m` - wait at least 1 minute before checking for a job). JobInterval time.Duration `yaml:"jobinterval,omitempty"` } // DatabaseLoadBalancing can be used to enable and configure database load balancing. type DatabaseLoadBalancing struct { // Enabled can be used to enable or disable the database load balancing. Defaults to false. Enabled bool `yaml:"enabled"` // Hosts is a list of static hosts to use for load balancing. Can be used as an alternative to service discovery. // Ignored if `record` is set. Hosts []string `yaml:"hosts,omitempty"` // Nameserver is the nameserver to use for looking up the DNS record. Nameserver string `yaml:"nameserver"` // Port is the port to use for looking up the DNS record. Port int `yaml:"port"` // Record is the SRV DNS record to look up. This option is required for service discovery to work. Record string `yaml:"record"` // ReplicaCheckInterval is the minimum amount of time between checking the status of a replica. ReplicaCheckInterval time.Duration `yaml:"replicacheckinterval"` } // Regexp wraps regexp.Regexp to implement the encoding.TextMarshaler interface. type Regexp struct { *regexp.Regexp } // compileRegexp wraps the standard library's regexp.Compile. func compileRegexp(expr string) (*Regexp, error) { re, err := regexp.Compile(expr) if err != nil { return nil, err } return &Regexp{re}, nil } // UnmarshalText implements encoding.TextMarshaler. func (r *Regexp) UnmarshalText(text []byte) error { rr, err := compileRegexp(string(text)) if err != nil { return fmt.Errorf("invalid regexp %q: %w", text, err) } *r = *rr return nil } // MarshalText implements encoding.TextMarshaler. func (r *Regexp) MarshalText() ([]byte, error) { return []byte(r.String()), nil } // MailOptions provides the configuration sections to user, for specific handler. type MailOptions struct { SMTP struct { // Addr defines smtp host address Addr string `yaml:"addr,omitempty"` // Username defines user name to smtp host Username string `yaml:"username,omitempty"` // Password defines password of login user Password string `yaml:"password,omitempty"` // Insecure defines if smtp login skips the secure certification. Insecure bool `yaml:"insecure,omitempty"` } `yaml:"smtp,omitempty"` // From defines mail sending address From string `yaml:"from,omitempty"` // To defines mail receiving address To []string `yaml:"to,omitempty"` } // FileChecker is a type of entry in the health section for checking files. type FileChecker struct { // Interval is the duration in between checks Interval time.Duration `yaml:"interval,omitempty"` // File is the path to check File string `yaml:"file,omitempty"` // Threshold is the number of times a check must fail to trigger an // unhealthy state Threshold int `yaml:"threshold,omitempty"` } // HTTPChecker is a type of entry in the health section for checking HTTP URIs. type HTTPChecker struct { // Timeout is the duration to wait before timing out the HTTP request Timeout time.Duration `yaml:"timeout,omitempty"` // StatusCode is the expected status code StatusCode int // Interval is the duration in between checks Interval time.Duration `yaml:"interval,omitempty"` // URI is the HTTP URI to check URI string `yaml:"uri,omitempty"` // Headers lists static headers that should be added to all requests Headers http.Header `yaml:"headers"` // Threshold is the number of times a check must fail to trigger an // unhealthy state Threshold int `yaml:"threshold,omitempty"` } // TCPChecker is a type of entry in the health section for checking TCP servers. type TCPChecker struct { // Timeout is the duration to wait before timing out the TCP connection Timeout time.Duration `yaml:"timeout,omitempty"` // Interval is the duration in between checks Interval time.Duration `yaml:"interval,omitempty"` // Addr is the TCP address to check Addr string `yaml:"addr,omitempty"` // Threshold is the number of times a check must fail to trigger an // unhealthy state Threshold int `yaml:"threshold,omitempty"` } // Health provides the configuration section for health checks. type Health struct { // FileCheckers is a list of paths to check FileCheckers []FileChecker `yaml:"file,omitempty"` // HTTPCheckers is a list of URIs to check HTTPCheckers []HTTPChecker `yaml:"http,omitempty"` // TCPCheckers is a list of URIs to check TCPCheckers []TCPChecker `yaml:"tcp,omitempty"` // StorageDriver configures a health check on the configured storage // driver StorageDriver struct { // Enabled turns on the health check for the storage driver Enabled bool `yaml:"enabled,omitempty"` // Interval is the duration in between checks Interval time.Duration `yaml:"interval,omitempty"` // Threshold is the number of times a check must fail to trigger an // unhealthy state Threshold int `yaml:"threshold,omitempty"` } `yaml:"storagedriver,omitempty"` Database struct { // Enabled turns on the health check for the database Enabled bool `yaml:"enabled,omitempty"` // Interval is the duration in between checks Interval time.Duration `yaml:"interval,omitempty"` // Threshold is the number of times a check must fail to trigger an // unhealthy state Threshold int `yaml:"threshold,omitempty"` // Timeout is the duration to wait before timing out the db query. // Applies to primary/replicas individually - it is not cumultative! Timeout time.Duration `yaml:"timeout,omitempty"` } } // v0_1Configuration is a Version 0.1 Configuration struct // This is currently aliased to Configuration, as it is the current version type v0_1Configuration Configuration // UnmarshalYAML implements the yaml.Unmarshaler interface // Unmarshals a string of the form X.Y into a Version, validating that X and Y can represent unsigned integers func (version *Version) UnmarshalYAML(unmarshal func(any) error) error { var versionString string err := unmarshal(&versionString) if err != nil { return err } newVersion := Version(versionString) if _, err := newVersion.majorImpl(); err != nil { return err } if _, err := newVersion.minorImpl(); err != nil { return err } *version = newVersion return nil } // CurrentVersion is the most recent Version that can be parsed var CurrentVersion = MajorMinorVersion(0, 1) // Loglevel is the level at which operations are logged. This can be "error", "warn", "info", "debug" or "trace". type Loglevel string const ( LogLevelError Loglevel = "error" LogLevelWarn Loglevel = "warn" LogLevelInfo Loglevel = "info" LogLevelDebug Loglevel = "debug" LogLevelTrace Loglevel = "trace" defaultLogLevel = LogLevelInfo ) var logLevels = []Loglevel{ LogLevelError, LogLevelWarn, LogLevelInfo, LogLevelDebug, LogLevelTrace, } // String implements the Stringer interface for Loglevel. func (l Loglevel) String() string { return string(l) } func (l Loglevel) isValid() bool { for _, lvl := range logLevels { if l == lvl { return true } } return false } // UnmarshalYAML implements the yaml.Umarshaler interface for Loglevel, parsing it and validating that it represents a // valid log level. func (l *Loglevel) UnmarshalYAML(unmarshal func(any) error) error { var val string err := unmarshal(&val) if err != nil { return err } lvl := Loglevel(strings.ToLower(val)) if !lvl.isValid() { return fmt.Errorf("invalid log level %q, must be one of %q", val, logLevels) } *l = lvl return nil } // logOutput is the output destination for logs. This can be either "stdout" or "stderr". type logOutput string const ( LogOutputStdout logOutput = "stdout" LogOutputStderr logOutput = "stderr" LogOutputDiscard logOutput = "discard" defaultLogOutput = LogOutputStdout ) var logOutputs = []logOutput{LogOutputStdout, LogOutputStderr} // String implements the Stringer interface for logOutput. func (out logOutput) String() string { return string(out) } // Descriptor returns the os file descriptor of a log output. func (out logOutput) Descriptor() io.Writer { switch out { case LogOutputStderr: return os.Stderr case LogOutputDiscard: return io.Discard default: return os.Stdout } } func (out logOutput) isValid() bool { for _, output := range logOutputs { if out == output { return true } } return false } // UnmarshalYAML implements the yaml.Umarshaler interface for logOutput, parsing it and validating that it represents a // valid log output destination. func (out *logOutput) UnmarshalYAML(unmarshal func(any) error) error { var val string if err := unmarshal(&val); err != nil { return err } lo := logOutput(strings.ToLower(val)) if !lo.isValid() { return fmt.Errorf("invalid log output %q, must be one of %q", lo, logOutputs) } *out = lo return nil } // logFormat is the format of the application logs output. This can be either "text" or "json". type logFormat string const ( LogFormatText logFormat = "text" LogFormatJSON logFormat = "json" defaultLogFormat = LogFormatJSON ) var logFormats = []logFormat{ LogFormatText, LogFormatJSON, } // String implements the Stringer interface for logFormat. func (ft logFormat) String() string { return string(ft) } func (ft logFormat) isValid() bool { for _, formatter := range logFormats { if ft == formatter { return true } } return false } // UnmarshalYAML implements the yaml.Umarshaler interface for logFormat, parsing it and validating that it // represents a valid application log output format. func (ft *logFormat) UnmarshalYAML(unmarshal func(any) error) error { var val string if err := unmarshal(&val); err != nil { return err } format := logFormat(strings.ToLower(val)) if !format.isValid() { return fmt.Errorf("invalid log format %q, must be one of %q", format, logFormats) } *ft = format return nil } // accessLogFormat is the format of the access logs output. This can be either "text" or "json". type accessLogFormat string const ( AccessLogFormatText accessLogFormat = "text" AccessLogFormatJSON accessLogFormat = "json" defaultAccessLogFormat = AccessLogFormatJSON ) var accessLogFormats = []accessLogFormat{ AccessLogFormatText, AccessLogFormatJSON, } // String implements the Stringer interface for accessLogFormat. func (ft accessLogFormat) String() string { return string(ft) } func (ft accessLogFormat) isValid() bool { for _, formatter := range accessLogFormats { if ft == formatter { return true } } return false } // UnmarshalYAML implements the yaml.Umarshaler interface for accessLogFormat, parsing it and validating that it // represents a valid access log output format. func (ft *accessLogFormat) UnmarshalYAML(unmarshal func(any) error) error { var val string if err := unmarshal(&val); err != nil { return err } format := accessLogFormat(strings.ToLower(val)) if !format.isValid() { return fmt.Errorf("invalid access log format %q, must be one of %q", format, accessLogFormats) } *ft = format return nil } // Parameters defines a key-value parameters mapping type Parameters map[string]any // Storage defines the configuration for registry object storage type Storage map[string]Parameters // Type returns the storage driver type, such as filesystem or s3 func (storage Storage) Type() string { var storageType []string // Return only key in this map for k := range storage { switch k { case "maintenance": // allow configuration of maintenance case "cache": // allow configuration of caching case "delete": // allow configuration of delete case "redirect": // allow configuration of redirect default: storageType = append(storageType, k) } } if len(storageType) > 1 { panic("multiple storage drivers specified in configuration or environment: " + strings.Join(storageType, ", ")) } if len(storageType) == 1 { return storageType[0] } return "" } // Parameters returns the Parameters map for a Storage configuration func (storage Storage) Parameters() Parameters { return storage[storage.Type()] } // setParameter changes the parameter at the provided key to the new value func (storage Storage) setParameter(key string, value any) { storage[storage.Type()][key] = value } // UnmarshalYAML implements the yaml.Unmarshaler interface // Unmarshals a single item map into a Storage or a string into a Storage type with no parameters func (storage *Storage) UnmarshalYAML(unmarshal func(any) error) error { var storageMap map[string]Parameters err := unmarshal(&storageMap) if err == nil { if len(storageMap) > 1 { types := make([]string, 0, len(storageMap)) for k := range storageMap { switch k { case "maintenance": // allow for configuration of maintenance case "cache": // allow configuration of caching case "delete": // allow configuration of delete case "redirect": // allow configuration of redirect default: types = append(types, k) } } if len(types) > 1 { return fmt.Errorf("must provide exactly one storage type. Provided: %v", types) } } *storage = storageMap return nil } var storageType string err = unmarshal(&storageType) if err == nil { *storage = Storage{storageType: make(Parameters)} return nil } return err } // MarshalYAML implements the yaml.Marshaler interface func (storage Storage) MarshalYAML() (any, error) { if storage.Parameters() == nil { return storage.Type(), nil } return map[string]Parameters(storage), nil } // Auth defines the configuration for registry authorization. type Auth map[string]Parameters // Type returns the auth type, such as token func (auth Auth) Type() string { // Return only key in this map for k := range auth { return k } return "" } // Parameters returns the Parameters map for an Auth configuration func (auth Auth) Parameters() Parameters { return auth[auth.Type()] } // setParameter changes the parameter at the provided key to the new value func (auth Auth) setParameter(key string, value any) { auth[auth.Type()][key] = value } // UnmarshalYAML implements the yaml.Unmarshaler interface // Unmarshals a single item map into a Storage or a string into a Storage type with no parameters func (auth *Auth) UnmarshalYAML(unmarshal func(any) error) error { var m map[string]Parameters err := unmarshal(&m) if err == nil { if len(m) > 1 { types := make([]string, 0, len(m)) for k := range m { types = append(types, k) } return fmt.Errorf("must provide exactly one type. Provided: %v", types) } *auth = m return nil } var authType string err = unmarshal(&authType) if err == nil { *auth = Auth{authType: make(Parameters)} return nil } return err } // MarshalYAML implements the yaml.Marshaler interface func (auth Auth) MarshalYAML() (any, error) { if auth.Parameters() == nil { return auth.Type(), nil } return map[string]Parameters(auth), nil } // Notifications configures multiple http endpoints. type Notifications struct { // FanoutTimeout is the maximum amount of time registry tries to finish fanning out notifications to notification sinks after it received SIGINT FanoutTimeout time.Duration `yaml:"fanouttimeout,omitempty"` // EventConfig is the configuration for the event format that is sent to each Endpoint. EventConfig Events `yaml:"events,omitempty"` // Endpoints is a list of http configurations for endpoints that // respond to webhook notifications. In the future, we may allow other // kinds of endpoints, such as external queues. Endpoints []Endpoint `yaml:"endpoints,omitempty"` } // Endpoint describes the configuration of an http webhook notification // endpoint. type Endpoint struct { Name string `yaml:"name"` // identifies the endpoint in the registry instance. Disabled bool `yaml:"disabled"` // disables the endpoint URL string `yaml:"url"` // post url for the endpoint. Headers http.Header `yaml:"headers"` // static headers that should be added to all requests Timeout time.Duration `yaml:"timeout"` // HTTP timeout // Deprecated: use maxretries instead https://gitlab.com/gitlab-org/container-registry/-/issues/1243. Threshold int `yaml:"threshold"` // Circuit breaker threshold before backing off on failure MaxRetries int `yaml:"maxretries"` // maximum number of times to retry sending a failed event Backoff time.Duration `yaml:"backoff"` // backoff duration IgnoredMediaTypes []string `yaml:"ignoredmediatypes"` // target media types to ignore Ignore Ignore `yaml:"ignore"` // ignore event types QueuePurgeTimeout time.Duration `yaml:"queuepurgetimeout"` // the amount of time registry tries to sent unsent notifications in the buffer after it received SIGINT } // Events configures notification events. type Events struct { IncludeReferences bool `yaml:"includereferences"` // include reference data in manifest events } // Ignore configures mediaTypes and actions of the event, that it won't be propagated type Ignore struct { MediaTypes []string `yaml:"mediatypes"` // target media types to ignore Actions []string `yaml:"actions"` // ignore action types } // Reporting defines error reporting methods. type Reporting struct { // Sentry configures error reporting for Sentry (sentry.io). Sentry SentryReporting `yaml:"sentry,omitempty"` } // SentryReporting configures error reporting for Sentry (sentry.io). type SentryReporting struct { // Enabled can be set to `true` to enable the Sentry error reporting. Enabled bool `yaml:"enabled,omitempty"` // DSN is the Sentry DSN. DSN string `yaml:"dsn,omitempty"` // Environment is the Sentry environment. Environment string `yaml:"environment,omitempty"` } // Middleware configures named middlewares to be applied at injection points. type Middleware struct { // Name the middleware registers itself as Name string `yaml:"name"` // Flag to disable middleware easily Disabled bool `yaml:"disabled,omitempty"` // Map of parameters that will be passed to the middleware's initialization function Options Parameters `yaml:"options"` } // RateLimiter represents the top-level rate limiter configuration type RateLimiter struct { Enabled bool `yaml:"enabled"` Limiters []Limiter `yaml:"limiters,omitempty"` } // Limiter represents an individual rate limit configuration type Limiter struct { Name string `yaml:"name"` Description string `yaml:"description"` LogOnly bool `yaml:"log_only,omitempty"` Match Match `yaml:"match"` Precedence int64 `yaml:"precedence"` Limit Limit `yaml:"limit"` Action Action `yaml:"action"` } // Match defines how requests are matched for rate limiting type Match struct { Type string `yaml:"type"` } // Limit defines the rate limiting parameters type Limit struct { Rate int64 `yaml:"rate"` Period string `yaml:"period"` PeriodDuration time.Duration `yaml:"-"` Burst int64 `yaml:"burst"` } // Action defines actions to take when limits are approached or exceeded type Action struct { WarnThreshold float64 `yaml:"warn_threshold,omitempty"` WarnAction string `yaml:"warn_action,omitempty"` HardAction string `yaml:"hard_action"` } type parseOpts struct { noStorageRequired bool } // ParseOption is used to pass options to Parse. type ParseOption func(*parseOpts) // WithoutStorageValidation configures Parse to disable the storage parameters validation. func WithoutStorageValidation() ParseOption { return func(opts *parseOpts) { opts.noStorageRequired = true } } // Parse parses an input configuration yaml document into a Configuration struct // This should generally be capable of handling old configuration format versions // // Environment variables may be used to override configuration parameters other than version, // following the scheme below: // Configuration.Abc may be replaced by the value of REGISTRY_ABC, // Configuration.Abc.Xyz may be replaced by the value of REGISTRY_ABC_XYZ, and so forth func Parse(rd io.Reader, opts ...ParseOption) (*Configuration, error) { options := parseOpts{} for _, v := range opts { v(&options) } in, err := io.ReadAll(rd) if err != nil { return nil, err } p := NewParser("registry", []VersionedParseInfo{ { Version: MajorMinorVersion(0, 1), ParseAs: reflect.TypeOf(v0_1Configuration{}), ConversionFunc: func(c any) (any, error) { if v0_1, ok := c.(*v0_1Configuration); ok { if v0_1.Log.Level == Loglevel("") { if v0_1.Loglevel != Loglevel("") { v0_1.Log.Level = v0_1.Loglevel } } if v0_1.Loglevel != Loglevel("") { v0_1.Loglevel = Loglevel("") } if !options.noStorageRequired { if v0_1.Storage.Type() == "" { return nil, errors.New("no storage configuration provided") } } return (*Configuration)(v0_1), nil } return nil, fmt.Errorf("expected *v0_1Configuration, received %#v", c) }, }, }) config := new(Configuration) err = p.Parse(in, config) if err != nil { return nil, err } ApplyDefaults(config) return config, nil } const ( defaultBackgroundMigrationsJobInterval = 1 * time.Minute defaultDLBNameserver = "localhost" defaultDLBPort = 8600 defaultDLBDisconnectTimeout = 2 * time.Minute defaultDLBMaxReplicaLagBytes = 8 * 1024 * 1024 defaultDLBMaxReplicaLagTime = 1 * time.Minute defaultDLBReplicaCheckInterval = 1 * time.Minute defaultRateLimiterPeriodDuration = time.Second ) // defaultCipherSuites is here just to make slice "a constant" func defaultCipherSuites() []string { return []string{ "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", } } func ApplyDefaults(config *Configuration) { if config.Log.Level == "" { config.Log.Level = defaultLogLevel } if config.Log.Output == "" { config.Log.Output = defaultLogOutput } if config.Log.Formatter == "" { config.Log.Formatter = defaultLogFormat } if !config.Log.AccessLog.Disabled && config.Log.AccessLog.Formatter == "" { config.Log.AccessLog.Formatter = defaultAccessLogFormat } if config.HTTP.Debug.Prometheus.Enabled && config.HTTP.Debug.Prometheus.Path == "" { config.HTTP.Debug.Prometheus.Path = "/metrics" } if config.Redis.Addr != "" && config.Redis.Pool.Size == 0 { config.Redis.Pool.Size = 10 } // If no custom cipher suites are specified in the configuration, // default to a secure set of TLS 1.2 cipher suites. TLS 1.3 cipher // suites are automatically enabled in Go and do not need explicit // configuration. if len(config.HTTP.TLS.CipherSuites) == 0 { config.HTTP.TLS.CipherSuites = defaultCipherSuites() } // copy TLS config to debug server when enabled and debug TLS certificate is empty if config.HTTP.Debug.TLS.Enabled { if config.HTTP.Debug.TLS.Certificate == "" { config.HTTP.Debug.TLS.Certificate = config.HTTP.TLS.Certificate config.HTTP.Debug.TLS.Key = config.HTTP.TLS.Key } // Only replace if the debug section is empty which allows finer configuration settings for the // debug server, for example allowing only certain clients to access it. if len(config.HTTP.Debug.TLS.ClientCAs) == 0 { config.HTTP.Debug.TLS.ClientCAs = config.HTTP.TLS.ClientCAs } if config.HTTP.Debug.TLS.MinimumTLS == "" { config.HTTP.Debug.TLS.MinimumTLS = config.HTTP.TLS.MinimumTLS } } if config.Database.BackgroundMigrations.Enabled && config.Database.BackgroundMigrations.JobInterval == 0 { config.Database.BackgroundMigrations.JobInterval = defaultBackgroundMigrationsJobInterval } // Database Load Balancing if config.Database.LoadBalancing.Enabled { if config.Database.LoadBalancing.Nameserver == "" { config.Database.LoadBalancing.Nameserver = defaultDLBNameserver } if config.Database.LoadBalancing.Port == 0 { config.Database.LoadBalancing.Port = defaultDLBPort } if config.Database.LoadBalancing.ReplicaCheckInterval == 0 { config.Database.LoadBalancing.ReplicaCheckInterval = defaultDLBReplicaCheckInterval } } // Rate limiter if config.RateLimiter.Enabled { for _, limiter := range config.RateLimiter.Limiters { limiter.Match.Type = strings.ToLower(limiter.Match.Type) if limiter.Limit.Period == "" { limiter.Limit.Period = "second" limiter.Limit.PeriodDuration = defaultRateLimiterPeriodDuration } if limiter.Action.WarnAction == "" { limiter.Action.WarnAction = "none" } if limiter.Action.HardAction == "" { limiter.Action.HardAction = "none" } } } }