diff --git a/base/bootstrap.go b/base/bootstrap.go index 3d28540c95..c1b3f43c21 100644 --- a/base/bootstrap.go +++ b/base/bootstrap.go @@ -569,8 +569,12 @@ func (cc *CouchbaseCluster) connectToBucket(ctx context.Context, bucketName stri type PerBucketCredentialsConfig map[string]*CredentialsConfig type CredentialsConfig struct { - Username string `json:"username,omitempty" help:"Username for authenticating to the bucket"` - Password string `json:"password,omitempty" help:"Password for authenticating to the bucket"` + Username string `json:"username,omitempty" help:"Username for authenticating to the bucket"` + Password string `json:"password,omitempty" help:"Password for authenticating to the bucket"` + CredentialsConfigX509 +} + +type CredentialsConfigX509 struct { X509CertPath string `json:"x509_cert_path,omitempty" help:"Cert path (public key) for X.509 bucket auth"` X509KeyPath string `json:"x509_key_path,omitempty" help:"Key path (private key) for X.509 bucket auth"` } diff --git a/rest/config_flags.go b/rest/config_flags.go index 3777ba9c50..b036f29732 100644 --- a/rest/config_flags.go +++ b/rest/config_flags.go @@ -21,153 +21,156 @@ import ( // configFlag stores the config value, and the corresponding flag value type configFlag struct { - config interface{} - flagValue interface{} + config interface{} + flagValue interface{} + disabled bool // disabled can be true to disable the flag - if set this will error and force users to stop using the flag. + disabledErrorMessage string // disabledErrorMessage can be set to provide additional error message information } // registerConfigFlags holds a map of the flag values with the configFlag it goes with // (which stores the corresponding config field pointer and flag value). func registerConfigFlags(config *StartupConfig, fs *flag.FlagSet) map[string]configFlag { return map[string]configFlag{ - "bootstrap.group_id": {&config.Bootstrap.ConfigGroupID, fs.String("bootstrap.group_id", "", "The config group ID to use when discovering databases. Allows for non-homogenous configuration")}, - "bootstrap.config_update_frequency": {&config.Bootstrap.ConfigUpdateFrequency, fs.String("bootstrap.config_update_frequency", persistentConfigDefaultUpdateFrequency.String(), "How often to poll Couchbase Server for new config changes")}, - "bootstrap.server": {&config.Bootstrap.Server, fs.String("bootstrap.server", "", "Couchbase Server connection string/URL")}, - "bootstrap.username": {&config.Bootstrap.Username, fs.String("bootstrap.username", "", "Username for authenticating to server")}, - "bootstrap.password": {&config.Bootstrap.Password, fs.String("bootstrap.password", "", "Password for authenticating to server")}, - "bootstrap.ca_cert_path": {&config.Bootstrap.CACertPath, fs.String("bootstrap.ca_cert_path", "", "Root CA cert path for TLS connection")}, - "bootstrap.server_tls_skip_verify": {&config.Bootstrap.ServerTLSSkipVerify, fs.Bool("bootstrap.server_tls_skip_verify", false, "Allow empty server CA Cert Path without attempting to use system root pool")}, - "bootstrap.x509_cert_path": {&config.Bootstrap.X509CertPath, fs.String("bootstrap.x509_cert_path", "", "Cert path (public key) for X.509 bucket auth")}, - "bootstrap.x509_key_path": {&config.Bootstrap.X509KeyPath, fs.String("bootstrap.x509_key_path", "", "Key path (private key) for X.509 bucket auth")}, - "bootstrap.use_tls_server": {&config.Bootstrap.UseTLSServer, fs.Bool("bootstrap.use_tls_server", false, "Forces the connection to Couchbase Server to use TLS")}, + "bootstrap.group_id": {config: &config.Bootstrap.ConfigGroupID, flagValue: fs.String("bootstrap.group_id", "", "The config group ID to use when discovering databases. Allows for non-homogenous configuration")}, + "bootstrap.config_update_frequency": {config: &config.Bootstrap.ConfigUpdateFrequency, flagValue: fs.String("bootstrap.config_update_frequency", persistentConfigDefaultUpdateFrequency.String(), "How often to poll Couchbase Server for new config changes")}, + "bootstrap.server": {config: &config.Bootstrap.Server, flagValue: fs.String("bootstrap.server", "", "Couchbase Server connection string/URL")}, + "bootstrap.username": {config: &config.Bootstrap.Username, flagValue: fs.String("bootstrap.username", "", "Username for authenticating to server")}, + "bootstrap.password": {config: nil, disabled: true, disabledErrorMessage: "Use config file to specify bootstrap password, or use X.509 cert/key path flags instead.", flagValue: fs.String("bootstrap.password", "", "Deprecated and disabled. Do not use. Use config file or X.509 auth.")}, + "bootstrap.ca_cert_path": {config: &config.Bootstrap.CACertPath, flagValue: fs.String("bootstrap.ca_cert_path", "", "Root CA cert path for TLS connection")}, + "bootstrap.server_tls_skip_verify": {config: &config.Bootstrap.ServerTLSSkipVerify, flagValue: fs.Bool("bootstrap.server_tls_skip_verify", false, "Allow empty server CA Cert Path without attempting to use system root pool")}, + "bootstrap.x509_cert_path": {config: &config.Bootstrap.X509CertPath, flagValue: fs.String("bootstrap.x509_cert_path", "", "Cert path (public key) for X.509 bucket auth")}, + "bootstrap.x509_key_path": {config: &config.Bootstrap.X509KeyPath, flagValue: fs.String("bootstrap.x509_key_path", "", "Key path (private key) for X.509 bucket auth")}, + "bootstrap.use_tls_server": {config: &config.Bootstrap.UseTLSServer, flagValue: fs.Bool("bootstrap.use_tls_server", false, "Forces the connection to Couchbase Server to use TLS")}, - "api.public_interface": {&config.API.PublicInterface, fs.String("api.public_interface", "", "Network interface to bind public API to")}, - "api.admin_interface": {&config.API.AdminInterface, fs.String("api.admin_interface", "", "Network interface to bind admin API to")}, - "api.metrics_interface": {&config.API.MetricsInterface, fs.String("api.metrics_interface", "", "Network interface to bind metrics API to")}, - "api.profile_interface": {&config.API.ProfileInterface, fs.String("api.profile_interface", "", "Network interface to bind profiling API to")}, - "api.admin_interface_authentication": {&config.API.AdminInterfaceAuthentication, fs.Bool("api.admin_interface_authentication", false, "Whether the admin API requires authentication")}, - "api.metrics_interface_authentication": {&config.API.MetricsInterfaceAuthentication, fs.Bool("api.metrics_interface_authentication", false, "Whether the metrics API requires authentication")}, - "api.enable_admin_authentication_permissions_check": {&config.API.EnableAdminAuthenticationPermissionsCheck, fs.Bool("api.enable_admin_authentication_permissions_check", false, "Whether to enable the DP permissions check feature of admin auth")}, - "api.server_read_timeout": {&config.API.ServerReadTimeout, fs.String("api.server_read_timeout", "", "Maximum duration.Second before timing out read of the HTTP(S) request")}, - "api.server_write_timeout": {&config.API.ServerWriteTimeout, fs.String("api.server_write_timeout", "", "Maximum duration.Second before timing out write of the HTTP(S) response")}, - "api.read_header_timeout": {&config.API.ReadHeaderTimeout, fs.String("api.read_header_timeout", "", "The amount of time allowed to read request headers")}, - "api.idle_timeout": {&config.API.IdleTimeout, fs.String("api.idle_timeout", "", "The maximum amount of time to wait for the next request when keep-alives are enabled")}, - "api.pretty": {&config.API.Pretty, fs.Bool("api.pretty", false, "Pretty-print JSON responses")}, - "api.max_connections": {&config.API.MaximumConnections, fs.Uint("api.max_connections", 0, "Max # of incoming HTTP connections to accept")}, - "api.compress_responses": {&config.API.CompressResponses, fs.Bool("api.compress_responses", false, "If false, disables compression of HTTP responses")}, - "api.hide_product_version": {&config.API.CompressResponses, fs.Bool("api.hide_product_version", false, "Whether product versions removed from Server headers and REST API responses")}, + "api.public_interface": {config: &config.API.PublicInterface, flagValue: fs.String("api.public_interface", "", "Network interface to bind public API to")}, + "api.admin_interface": {config: &config.API.AdminInterface, flagValue: fs.String("api.admin_interface", "", "Network interface to bind admin API to")}, + "api.metrics_interface": {config: &config.API.MetricsInterface, flagValue: fs.String("api.metrics_interface", "", "Network interface to bind metrics API to")}, + "api.profile_interface": {config: &config.API.ProfileInterface, flagValue: fs.String("api.profile_interface", "", "Network interface to bind profiling API to")}, + "api.admin_interface_authentication": {config: &config.API.AdminInterfaceAuthentication, flagValue: fs.Bool("api.admin_interface_authentication", false, "Whether the admin API requires authentication")}, + "api.metrics_interface_authentication": {config: &config.API.MetricsInterfaceAuthentication, flagValue: fs.Bool("api.metrics_interface_authentication", false, "Whether the metrics API requires authentication")}, + "api.enable_admin_authentication_permissions_check": {config: &config.API.EnableAdminAuthenticationPermissionsCheck, flagValue: fs.Bool("api.enable_admin_authentication_permissions_check", false, "Whether to enable the DP permissions check feature of admin auth")}, + "api.server_read_timeout": {config: &config.API.ServerReadTimeout, flagValue: fs.String("api.server_read_timeout", "", "Maximum duration.Second before timing out read of the HTTP(S) request")}, + "api.server_write_timeout": {config: &config.API.ServerWriteTimeout, flagValue: fs.String("api.server_write_timeout", "", "Maximum duration.Second before timing out write of the HTTP(S) response")}, + "api.read_header_timeout": {config: &config.API.ReadHeaderTimeout, flagValue: fs.String("api.read_header_timeout", "", "The amount of time allowed to read request headers")}, + "api.idle_timeout": {config: &config.API.IdleTimeout, flagValue: fs.String("api.idle_timeout", "", "The maximum amount of time to wait for the next request when keep-alives are enabled")}, + "api.pretty": {config: &config.API.Pretty, flagValue: fs.Bool("api.pretty", false, "Pretty-print JSON responses")}, + "api.max_connections": {config: &config.API.MaximumConnections, flagValue: fs.Uint("api.max_connections", 0, "Max # of incoming HTTP connections to accept")}, + "api.compress_responses": {config: &config.API.CompressResponses, flagValue: fs.Bool("api.compress_responses", false, "If false, disables compression of HTTP responses")}, + "api.hide_product_version": {config: &config.API.CompressResponses, flagValue: fs.Bool("api.hide_product_version", false, "Whether product versions removed from Server headers and REST API responses")}, - "api.https.tls_minimum_version": {&config.API.HTTPS.TLSMinimumVersion, fs.String("api.https.tls_minimum_version", "", "The minimum allowable TLS version for the REST APIs")}, - "api.https.tls_cert_path": {&config.API.HTTPS.TLSCertPath, fs.String("api.https.tls_cert_path", "", "The TLS cert file to use for the REST APIs")}, - "api.https.tls_key_path": {&config.API.HTTPS.TLSKeyPath, fs.String("api.https.tls_key_path", "", "The TLS key file to use for the REST APIs")}, + "api.https.tls_minimum_version": {config: &config.API.HTTPS.TLSMinimumVersion, flagValue: fs.String("api.https.tls_minimum_version", "", "The minimum allowable TLS version for the REST APIs")}, + "api.https.tls_cert_path": {config: &config.API.HTTPS.TLSCertPath, flagValue: fs.String("api.https.tls_cert_path", "", "The TLS cert file to use for the REST APIs")}, + "api.https.tls_key_path": {config: &config.API.HTTPS.TLSKeyPath, flagValue: fs.String("api.https.tls_key_path", "", "The TLS key file to use for the REST APIs")}, - "api.cors.origin": {&config.API.CORS.Origin, fs.String("api.cors.origin", "", "List of comma separated allowed origins. Use '*' to allow access from everywhere")}, - "api.cors.login_origin": {&config.API.CORS.LoginOrigin, fs.String("api.cors.login_origin", "", "List of comma separated allowed login origins")}, - "api.cors.headers": {&config.API.CORS.Headers, fs.String("api.cors.headers", "", "List of comma separated allowed headers")}, - "api.cors.max_age": {&config.API.CORS.MaxAge, fs.Int("api.cors.max_age", 0, "Maximum age of the CORS Options request")}, + "api.cors.origin": {config: &config.API.CORS.Origin, flagValue: fs.String("api.cors.origin", "", "List of comma separated allowed origins. Use '*' to allow access from everywhere")}, + "api.cors.login_origin": {config: &config.API.CORS.LoginOrigin, flagValue: fs.String("api.cors.login_origin", "", "List of comma separated allowed login origins")}, + "api.cors.headers": {config: &config.API.CORS.Headers, flagValue: fs.String("api.cors.headers", "", "List of comma separated allowed headers")}, + "api.cors.max_age": {config: &config.API.CORS.MaxAge, flagValue: fs.Int("api.cors.max_age", 0, "Maximum age of the CORS Options request")}, - "logging.log_file_path": {&config.Logging.LogFilePath, fs.String("logging.log_file_path", "", "Absolute or relative path on the filesystem to the log file directory. A relative path is from the directory that contains the Sync Gateway executable file")}, - "logging.redaction_level": {&config.Logging.RedactionLevel, fs.String("logging.redaction_level", "", "Redaction level to apply to log output. Options: none, partial, full, unset")}, + "logging.log_file_path": {config: &config.Logging.LogFilePath, flagValue: fs.String("logging.log_file_path", "", "Absolute or relative path on the filesystem to the log file directory. A relative path is from the directory that contains the Sync Gateway executable file")}, + "logging.redaction_level": {config: &config.Logging.RedactionLevel, flagValue: fs.String("logging.redaction_level", "", "Redaction level to apply to log output. Options: none, partial, full, unset")}, - "logging.console.enabled": {&config.Logging.Console.Enabled, fs.Bool("logging.console.enabled", false, "")}, - "logging.console.rotation.max_size": {&config.Logging.Console.Rotation.MaxSize, fs.Int("logging.console.rotation.max_size", 0, "")}, - "logging.console.rotation.max_age": {&config.Logging.Console.Rotation.MaxAge, fs.Int("logging.console.rotation.max_age", 0, "")}, - "logging.console.rotation.localtime": {&config.Logging.Console.Rotation.LocalTime, fs.Bool("logging.console.rotation.localtime", false, "")}, - "logging.console.rotation.rotated_logs_size_limit": {&config.Logging.Console.Rotation.RotatedLogsSizeLimit, fs.Int("logging.console.rotation.rotated_logs_size_limit", 0, "")}, - "logging.console.rotation.rotation_interval": {&config.Logging.Console.Rotation.RotationInterval, fs.String("logging.console.rotation.rotation_interval", "", "")}, - "logging.console.collation_buffer_size": {&config.Logging.Console.CollationBufferSize, fs.Int("logging.console.collation_buffer_size", 0, "")}, - "logging.console.log_level": {&config.Logging.Console.LogLevel, fs.String("logging.console.log_level", "", "Options: none, error, warn, info, debug, trace")}, - "logging.console.log_keys": {&config.Logging.Console.LogKeys, fs.String("logging.console.log_keys", "", "Comma separated log keys")}, - "logging.console.color_enabled": {&config.Logging.Console.ColorEnabled, fs.Bool("logging.console.color_enabled", false, "")}, - "logging.console.file_output": {&config.Logging.Console.FileOutput, fs.String("logging.console.file_output", "", "")}, // can be used to override the default stderr output, and write to the file specified instead. + "logging.console.enabled": {config: &config.Logging.Console.Enabled, flagValue: fs.Bool("logging.console.enabled", false, "")}, + "logging.console.rotation.max_size": {config: &config.Logging.Console.Rotation.MaxSize, flagValue: fs.Int("logging.console.rotation.max_size", 0, "")}, + "logging.console.rotation.max_age": {config: &config.Logging.Console.Rotation.MaxAge, flagValue: fs.Int("logging.console.rotation.max_age", 0, "")}, + "logging.console.rotation.localtime": {config: &config.Logging.Console.Rotation.LocalTime, flagValue: fs.Bool("logging.console.rotation.localtime", false, "")}, + "logging.console.rotation.rotated_logs_size_limit": {config: &config.Logging.Console.Rotation.RotatedLogsSizeLimit, flagValue: fs.Int("logging.console.rotation.rotated_logs_size_limit", 0, "")}, + "logging.console.rotation.rotation_interval": {config: &config.Logging.Console.Rotation.RotationInterval, flagValue: fs.String("logging.console.rotation.rotation_interval", "", "")}, + "logging.console.collation_buffer_size": {config: &config.Logging.Console.CollationBufferSize, flagValue: fs.Int("logging.console.collation_buffer_size", 0, "")}, + "logging.console.log_level": {config: &config.Logging.Console.LogLevel, flagValue: fs.String("logging.console.log_level", "", "Options: none, error, warn, info, debug, trace")}, + "logging.console.log_keys": {config: &config.Logging.Console.LogKeys, flagValue: fs.String("logging.console.log_keys", "", "Comma separated log keys")}, + "logging.console.color_enabled": {config: &config.Logging.Console.ColorEnabled, flagValue: fs.Bool("logging.console.color_enabled", false, "")}, + "logging.console.file_output": {config: &config.Logging.Console.FileOutput, flagValue: fs.String("logging.console.file_output", "", "")}, - "logging.error.enabled": {&config.Logging.Error.Enabled, fs.Bool("logging.error.enabled", false, "")}, - "logging.error.rotation.max_size": {&config.Logging.Error.Rotation.MaxSize, fs.Int("logging.error.rotation.max_size", 0, "")}, - "logging.error.rotation.max_age": {&config.Logging.Error.Rotation.MaxAge, fs.Int("logging.error.rotation.max_age", 0, "")}, - "logging.error.rotation.localtime": {&config.Logging.Error.Rotation.LocalTime, fs.Bool("logging.error.rotation.localtime", false, "")}, - "logging.error.rotation.rotated_logs_size_limit": {&config.Logging.Error.Rotation.RotatedLogsSizeLimit, fs.Int("logging.error.rotation.rotated_logs_size_limit", 0, "")}, - "logging.error.rotation.rotation_interval": {&config.Logging.Error.Rotation.RotationInterval, fs.String("logging.error.rotation.rotation_interval", "", "")}, - "logging.error.collation_buffer_size": {&config.Logging.Error.CollationBufferSize, fs.Int("logging.error.collation_buffer_size", 0, "")}, + "logging.error.enabled": {config: &config.Logging.Error.Enabled, flagValue: fs.Bool("logging.error.enabled", false, "")}, + "logging.error.rotation.max_size": {config: &config.Logging.Error.Rotation.MaxSize, flagValue: fs.Int("logging.error.rotation.max_size", 0, "")}, + "logging.error.rotation.max_age": {config: &config.Logging.Error.Rotation.MaxAge, flagValue: fs.Int("logging.error.rotation.max_age", 0, "")}, + "logging.error.rotation.localtime": {config: &config.Logging.Error.Rotation.LocalTime, flagValue: fs.Bool("logging.error.rotation.localtime", false, "")}, + "logging.error.rotation.rotated_logs_size_limit": {config: &config.Logging.Error.Rotation.RotatedLogsSizeLimit, flagValue: fs.Int("logging.error.rotation.rotated_logs_size_limit", 0, "")}, + "logging.error.rotation.rotation_interval": {config: &config.Logging.Error.Rotation.RotationInterval, flagValue: fs.String("logging.error.rotation.rotation_interval", "", "")}, + "logging.error.collation_buffer_size": {config: &config.Logging.Error.CollationBufferSize, flagValue: fs.Int("logging.error.collation_buffer_size", 0, "")}, - "logging.warn.enabled": {&config.Logging.Warn.Enabled, fs.Bool("logging.warn.enabled", false, "")}, - "logging.warn.rotation.max_size": {&config.Logging.Warn.Rotation.MaxSize, fs.Int("logging.warn.rotation.max_size", 0, "")}, - "logging.warn.rotation.max_age": {&config.Logging.Warn.Rotation.MaxAge, fs.Int("logging.warn.rotation.max_age", 0, "")}, - "logging.warn.rotation.localtime": {&config.Logging.Warn.Rotation.LocalTime, fs.Bool("logging.warn.rotation.localtime", false, "")}, - "logging.warn.rotation.rotated_logs_size_limit": {&config.Logging.Warn.Rotation.RotatedLogsSizeLimit, fs.Int("logging.warn.rotation.rotated_logs_size_limit", 0, "")}, - "logging.warn.rotation.rotation_interval": {&config.Logging.Warn.Rotation.RotationInterval, fs.String("logging.warn.rotation.rotation_interval", "", "")}, - "logging.warn.collation_buffer_size": {&config.Logging.Warn.CollationBufferSize, fs.Int("logging.warn.collation_buffer_size", 0, "")}, + "logging.warn.enabled": {config: &config.Logging.Warn.Enabled, flagValue: fs.Bool("logging.warn.enabled", false, "")}, + "logging.warn.rotation.max_size": {config: &config.Logging.Warn.Rotation.MaxSize, flagValue: fs.Int("logging.warn.rotation.max_size", 0, "")}, + "logging.warn.rotation.max_age": {config: &config.Logging.Warn.Rotation.MaxAge, flagValue: fs.Int("logging.warn.rotation.max_age", 0, "")}, + "logging.warn.rotation.localtime": {config: &config.Logging.Warn.Rotation.LocalTime, flagValue: fs.Bool("logging.warn.rotation.localtime", false, "")}, + "logging.warn.rotation.rotated_logs_size_limit": {config: &config.Logging.Warn.Rotation.RotatedLogsSizeLimit, flagValue: fs.Int("logging.warn.rotation.rotated_logs_size_limit", 0, "")}, + "logging.warn.rotation.rotation_interval": {config: &config.Logging.Warn.Rotation.RotationInterval, flagValue: fs.String("logging.warn.rotation.rotation_interval", "", "")}, + "logging.warn.collation_buffer_size": {config: &config.Logging.Warn.CollationBufferSize, flagValue: fs.Int("logging.warn.collation_buffer_size", 0, "")}, - "logging.info.enabled": {&config.Logging.Info.Enabled, fs.Bool("logging.info.enabled", false, "")}, - "logging.info.rotation.max_size": {&config.Logging.Info.Rotation.MaxSize, fs.Int("logging.info.rotation.max_size", 0, "")}, - "logging.info.rotation.max_age": {&config.Logging.Info.Rotation.MaxAge, fs.Int("logging.info.rotation.max_age", 0, "")}, - "logging.info.rotation.localtime": {&config.Logging.Info.Rotation.LocalTime, fs.Bool("logging.info.rotation.localtime", false, "")}, - "logging.info.rotation.rotated_logs_size_limit": {&config.Logging.Info.Rotation.RotatedLogsSizeLimit, fs.Int("logging.info.rotation.rotated_logs_size_limit", 0, "")}, - "logging.info.rotation.rotation_interval": {&config.Logging.Info.Rotation.RotationInterval, fs.String("logging.info.rotation.rotation_interval", "", "")}, - "logging.info.collation_buffer_size": {&config.Logging.Info.CollationBufferSize, fs.Int("logging.info.collation_buffer_size", 0, "")}, + "logging.info.enabled": {config: &config.Logging.Info.Enabled, flagValue: fs.Bool("logging.info.enabled", false, "")}, + "logging.info.rotation.max_size": {config: &config.Logging.Info.Rotation.MaxSize, flagValue: fs.Int("logging.info.rotation.max_size", 0, "")}, + "logging.info.rotation.max_age": {config: &config.Logging.Info.Rotation.MaxAge, flagValue: fs.Int("logging.info.rotation.max_age", 0, "")}, + "logging.info.rotation.localtime": {config: &config.Logging.Info.Rotation.LocalTime, flagValue: fs.Bool("logging.info.rotation.localtime", false, "")}, + "logging.info.rotation.rotated_logs_size_limit": {config: &config.Logging.Info.Rotation.RotatedLogsSizeLimit, flagValue: fs.Int("logging.info.rotation.rotated_logs_size_limit", 0, "")}, + "logging.info.rotation.rotation_interval": {config: &config.Logging.Info.Rotation.RotationInterval, flagValue: fs.String("logging.info.rotation.rotation_interval", "", "")}, + "logging.info.collation_buffer_size": {config: &config.Logging.Info.CollationBufferSize, flagValue: fs.Int("logging.info.collation_buffer_size", 0, "")}, - "logging.debug.enabled": {&config.Logging.Debug.Enabled, fs.Bool("logging.debug.enabled", false, "")}, - "logging.debug.rotation.max_size": {&config.Logging.Debug.Rotation.MaxSize, fs.Int("logging.debug.rotation.max_size", 0, "")}, - "logging.debug.rotation.max_age": {&config.Logging.Debug.Rotation.MaxAge, fs.Int("logging.debug.rotation.max_age", 0, "")}, - "logging.debug.rotation.localtime": {&config.Logging.Debug.Rotation.LocalTime, fs.Bool("logging.debug.rotation.localtime", false, "")}, - "logging.debug.rotation.rotated_logs_size_limit": {&config.Logging.Debug.Rotation.RotatedLogsSizeLimit, fs.Int("logging.debug.rotation.rotated_logs_size_limit", 0, "")}, - "logging.debug.rotation.rotation_interval": {&config.Logging.Debug.Rotation.RotationInterval, fs.String("logging.debug.rotation.rotation_interval", "", "")}, - "logging.debug.collation_buffer_size": {&config.Logging.Debug.CollationBufferSize, fs.Int("logging.debug.collation_buffer_size", 0, "")}, + "logging.debug.enabled": {config: &config.Logging.Debug.Enabled, flagValue: fs.Bool("logging.debug.enabled", false, "")}, + "logging.debug.rotation.max_size": {config: &config.Logging.Debug.Rotation.MaxSize, flagValue: fs.Int("logging.debug.rotation.max_size", 0, "")}, + "logging.debug.rotation.max_age": {config: &config.Logging.Debug.Rotation.MaxAge, flagValue: fs.Int("logging.debug.rotation.max_age", 0, "")}, + "logging.debug.rotation.localtime": {config: &config.Logging.Debug.Rotation.LocalTime, flagValue: fs.Bool("logging.debug.rotation.localtime", false, "")}, + "logging.debug.rotation.rotated_logs_size_limit": {config: &config.Logging.Debug.Rotation.RotatedLogsSizeLimit, flagValue: fs.Int("logging.debug.rotation.rotated_logs_size_limit", 0, "")}, + "logging.debug.rotation.rotation_interval": {config: &config.Logging.Debug.Rotation.RotationInterval, flagValue: fs.String("logging.debug.rotation.rotation_interval", "", "")}, + "logging.debug.collation_buffer_size": {config: &config.Logging.Debug.CollationBufferSize, flagValue: fs.Int("logging.debug.collation_buffer_size", 0, "")}, - "logging.trace.enabled": {&config.Logging.Trace.Enabled, fs.Bool("logging.trace.enabled", false, "")}, - "logging.trace.rotation.max_size": {&config.Logging.Trace.Rotation.MaxSize, fs.Int("logging.trace.rotation.max_size", 0, "")}, - "logging.trace.rotation.max_age": {&config.Logging.Trace.Rotation.MaxAge, fs.Int("logging.trace.rotation.max_age", 0, "")}, - "logging.trace.rotation.localtime": {&config.Logging.Trace.Rotation.LocalTime, fs.Bool("logging.trace.rotation.localtime", false, "")}, - "logging.trace.rotation.rotated_logs_size_limit": {&config.Logging.Trace.Rotation.RotatedLogsSizeLimit, fs.Int("logging.trace.rotation.rotated_logs_size_limit", 0, "")}, - "logging.trace.rotation.rotation_interval": {&config.Logging.Trace.Rotation.RotationInterval, fs.String("logging.trace.rotation.rotation_interval", "", "")}, - "logging.trace.collation_buffer_size": {&config.Logging.Trace.CollationBufferSize, fs.Int("logging.trace.collation_buffer_size", 0, "")}, + "logging.trace.enabled": {config: &config.Logging.Trace.Enabled, flagValue: fs.Bool("logging.trace.enabled", false, "")}, + "logging.trace.rotation.max_size": {config: &config.Logging.Trace.Rotation.MaxSize, flagValue: fs.Int("logging.trace.rotation.max_size", 0, "")}, + "logging.trace.rotation.max_age": {config: &config.Logging.Trace.Rotation.MaxAge, flagValue: fs.Int("logging.trace.rotation.max_age", 0, "")}, + "logging.trace.rotation.localtime": {config: &config.Logging.Trace.Rotation.LocalTime, flagValue: fs.Bool("logging.trace.rotation.localtime", false, "")}, + "logging.trace.rotation.rotated_logs_size_limit": {config: &config.Logging.Trace.Rotation.RotatedLogsSizeLimit, flagValue: fs.Int("logging.trace.rotation.rotated_logs_size_limit", 0, "")}, + "logging.trace.rotation.rotation_interval": {config: &config.Logging.Trace.Rotation.RotationInterval, flagValue: fs.String("logging.trace.rotation.rotation_interval", "", "")}, + "logging.trace.collation_buffer_size": {config: &config.Logging.Trace.CollationBufferSize, flagValue: fs.Int("logging.trace.collation_buffer_size", 0, "")}, - "logging.stats.enabled": {&config.Logging.Stats.Enabled, fs.Bool("logging.stats.enabled", false, "")}, - "logging.stats.rotation.max_size": {&config.Logging.Stats.Rotation.MaxSize, fs.Int("logging.stats.rotation.max_size", 0, "")}, - "logging.stats.rotation.max_age": {&config.Logging.Stats.Rotation.MaxAge, fs.Int("logging.stats.rotation.max_age", 0, "")}, - "logging.stats.rotation.localtime": {&config.Logging.Stats.Rotation.LocalTime, fs.Bool("logging.stats.rotation.localtime", false, "")}, - "logging.stats.rotation.rotated_logs_size_limit": {&config.Logging.Stats.Rotation.RotatedLogsSizeLimit, fs.Int("logging.stats.rotation.rotated_logs_size_limit", 0, "")}, - "logging.stats.rotation.rotation_interval": {&config.Logging.Stats.Rotation.RotationInterval, fs.String("logging.stats.rotation.rotation_interval", "", "")}, - "logging.stats.collation_buffer_size": {&config.Logging.Stats.CollationBufferSize, fs.Int("logging.stats.collation_buffer_size", 0, "")}, + "logging.stats.enabled": {config: &config.Logging.Stats.Enabled, flagValue: fs.Bool("logging.stats.enabled", false, "")}, + "logging.stats.rotation.max_size": {config: &config.Logging.Stats.Rotation.MaxSize, flagValue: fs.Int("logging.stats.rotation.max_size", 0, "")}, + "logging.stats.rotation.max_age": {config: &config.Logging.Stats.Rotation.MaxAge, flagValue: fs.Int("logging.stats.rotation.max_age", 0, "")}, + "logging.stats.rotation.localtime": {config: &config.Logging.Stats.Rotation.LocalTime, flagValue: fs.Bool("logging.stats.rotation.localtime", false, "")}, + "logging.stats.rotation.rotated_logs_size_limit": {config: &config.Logging.Stats.Rotation.RotatedLogsSizeLimit, flagValue: fs.Int("logging.stats.rotation.rotated_logs_size_limit", 0, "")}, + "logging.stats.rotation.rotation_interval": {config: &config.Logging.Stats.Rotation.RotationInterval, flagValue: fs.String("logging.stats.rotation.rotation_interval", "", "")}, + "logging.stats.collation_buffer_size": {config: &config.Logging.Stats.CollationBufferSize, flagValue: fs.Int("logging.stats.collation_buffer_size", 0, "")}, - "logging.audit.enabled": {&config.Logging.Audit.Enabled, fs.Bool("logging.audit.enabled", false, "")}, - "logging.audit.rotation.max_size": {&config.Logging.Audit.Rotation.MaxSize, fs.Int("logging.audit.rotation.max_size", 0, "")}, - "logging.audit.rotation.max_age": {&config.Logging.Audit.Rotation.MaxAge, fs.Int("logging.audit.rotation.max_age", 0, "")}, - "logging.audit.rotation.localtime": {&config.Logging.Audit.Rotation.LocalTime, fs.Bool("logging.audit.rotation.localtime", false, "")}, - "logging.audit.rotation.rotated_logs_size_limit": {&config.Logging.Audit.Rotation.RotatedLogsSizeLimit, fs.Int("logging.audit.rotation.rotated_logs_size_limit", 0, "")}, - "logging.audit.collation_buffer_size": {&config.Logging.Audit.CollationBufferSize, fs.Int("logging.audit.collation_buffer_size", 0, "")}, - "logging.audit.rotation.rotation_interval": {&config.Logging.Audit.Rotation.RotationInterval, fs.String("logging.audit.rotation.rotation_interval", "", "")}, - "logging.audit.audit_log_file_path": {&config.Logging.Audit.AuditLogFilePath, fs.String("logging.audit.audit_log_file_path", "", "")}, - "logging.audit.enabled_events": {&config.Logging.Audit.EnabledEvents, fs.String("logging.audit.enabled_events", "", "")}, + "logging.audit.enabled": {config: &config.Logging.Audit.Enabled, flagValue: fs.Bool("logging.audit.enabled", false, "")}, + "logging.audit.rotation.max_size": {config: &config.Logging.Audit.Rotation.MaxSize, flagValue: fs.Int("logging.audit.rotation.max_size", 0, "")}, + "logging.audit.rotation.max_age": {config: &config.Logging.Audit.Rotation.MaxAge, flagValue: fs.Int("logging.audit.rotation.max_age", 0, "")}, + "logging.audit.rotation.localtime": {config: &config.Logging.Audit.Rotation.LocalTime, flagValue: fs.Bool("logging.audit.rotation.localtime", false, "")}, + "logging.audit.rotation.rotated_logs_size_limit": {config: &config.Logging.Audit.Rotation.RotatedLogsSizeLimit, flagValue: fs.Int("logging.audit.rotation.rotated_logs_size_limit", 0, "")}, + "logging.audit.collation_buffer_size": {config: &config.Logging.Audit.CollationBufferSize, flagValue: fs.Int("logging.audit.collation_buffer_size", 0, "")}, + "logging.audit.rotation.rotation_interval": {config: &config.Logging.Audit.Rotation.RotationInterval, flagValue: fs.String("logging.audit.rotation.rotation_interval", "", "")}, + "logging.audit.audit_log_file_path": {config: &config.Logging.Audit.AuditLogFilePath, flagValue: fs.String("logging.audit.audit_log_file_path", "", "")}, + "logging.audit.enabled_events": {config: &config.Logging.Audit.EnabledEvents, flagValue: fs.String("logging.audit.enabled_events", "", "")}, - "auth.bcrypt_cost": {&config.Auth.BcryptCost, fs.Int("auth.bcrypt_cost", 0, "Cost to use for bcrypt password hashes")}, + "auth.bcrypt_cost": {config: &config.Auth.BcryptCost, flagValue: fs.Int("auth.bcrypt_cost", 0, "Cost to use for bcrypt password hashes")}, - "replicator.max_heartbeat": {&config.Replicator.MaxHeartbeat, fs.String("replicator.max_heartbeat", "", "Max heartbeat value for _changes request")}, - "replicator.blip_compression": {&config.Replicator.BLIPCompression, fs.Int("replicator.blip_compression", 0, "BLIP data compression level (0-9)")}, - "replicator.max_concurrent_replications": {&config.Replicator.MaxConcurrentReplications, fs.Int("replicator.max_concurrent_replications", 0, "Maximum number of replication connections to the node")}, - "replicator.max_concurrent_changes_batches": {&config.Replicator.MaxConcurrentChangesBatches, fs.Int("replicator.max_concurrent_changes_batches", 0, "Maximum number of changes batches to process concurrently per replication")}, - "replicator.max_concurrent_revs": {&config.Replicator.MaxConcurrentRevs, fs.Int("replicator.max_concurrent_revs", 0, "Maximum number of revs to process concurrently per replication")}, + "replicator.max_heartbeat": {config: &config.Replicator.MaxHeartbeat, flagValue: fs.String("replicator.max_heartbeat", "", "Max heartbeat value for _changes request")}, + "replicator.blip_compression": {config: &config.Replicator.BLIPCompression, flagValue: fs.Int("replicator.blip_compression", 0, "BLIP data compression level (0-9)")}, + "replicator.max_concurrent_replications": {config: &config.Replicator.MaxConcurrentReplications, flagValue: fs.Int("replicator.max_concurrent_replications", 0, "Maximum number of replication connections to the node")}, + "replicator.max_concurrent_changes_batches": {config: &config.Replicator.MaxConcurrentChangesBatches, flagValue: fs.Int("replicator.max_concurrent_changes_batches", 0, "Maximum number of changes batches to process concurrently per replication")}, + "replicator.max_concurrent_revs": {config: &config.Replicator.MaxConcurrentRevs, flagValue: fs.Int("replicator.max_concurrent_revs", 0, "Maximum number of revs to process concurrently per replication")}, - "heap_profile_collection_threshold": {&config.HeapProfileCollectionThreshold, fs.Uint64("heap_profile_collection_threshold", 0, "Threshold in bytes for collecting heap profiles automatically. If set, Sync Gateway will collect a memory profile when it exceeds this value. The default value will be set to 85% of the lesser of cgroup or system memory.")}, - "heap_profile_disable_collection": {&config.HeapProfileDisableCollection, fs.Bool("heap_profile_disable_collection", false, "Disables automatic heap profile collection.")}, + "heap_profile_collection_threshold": {config: &config.HeapProfileCollectionThreshold, flagValue: fs.Uint64("heap_profile_collection_threshold", 0, "Threshold in bytes for collecting heap profiles automatically. If set, Sync Gateway will collect a memory profile when it exceeds this value. The default value will be set to 85% of the lesser of cgroup or system memory.")}, + "heap_profile_disable_collection": {config: &config.HeapProfileDisableCollection, flagValue: fs.Bool("heap_profile_disable_collection", false, "Disables automatic heap profile collection.")}, - "unsupported.diagnostic_interface": {&config.Unsupported.DiagnosticInterface, fs.String("unsupported.diagnostic_interface", "", "Network interface to bind diagnostic API to")}, - "unsupported.stats_log_frequency": {&config.Unsupported.StatsLogFrequency, fs.String("unsupported.stats_log_frequency", "", "How often should stats be written to stats logs")}, - "unsupported.use_stdlib_json": {&config.Unsupported.UseStdlibJSON, fs.Bool("unsupported.use_stdlib_json", false, "Bypass the jsoniter package and use Go's stdlib instead")}, - "unsupported.http2.enabled": {&config.Unsupported.HTTP2.Enabled, fs.Bool("unsupported.http2.enabled", false, "Whether HTTP2 support is enabled")}, - "unsupported.serverless.enabled": {&config.Unsupported.Serverless.Enabled, fs.Bool("unsupported.serverless.enabled", false, "Settings for running Sync Gateway in serverless mode.")}, - "unsupported.serverless.min_config_fetch_interval": {&config.Unsupported.Serverless.MinConfigFetchInterval, fs.String("unsupported.serverless.min_config_fetch_interval", "", "How long to cache configs fetched from the buckets for. This cache is used for requested databases that SG does not know about.")}, - "unsupported.use_xattr_config": {&config.Unsupported.UseXattrConfig, fs.Bool("unsupported.use_xattr_config", false, "Store database configurations in system xattrs")}, - "unsupported.allow_dbconfig_env_vars": {&config.Unsupported.AllowDbConfigEnvVars, fs.Bool("unsupported.allow_dbconfig_env_vars", true, "Can be set to false to skip environment variable expansion in database configs")}, - "unsupported.user_queries": {&config.Unsupported.UserQueries, fs.Bool("unsupported.user_queries", false, "Whether user-query APIs are enabled")}, - "unsupported.audit_info_provider.global_info_env_var_name": {&config.Unsupported.AuditInfoProvider.GlobalInfoEnvVarName, fs.String("unsupported.audit_info_provider.global_info_env_var_name", "", "Environment variable name to get global audit event info from")}, - "unsupported.audit_info_provider.request_info_header_name": {&config.Unsupported.AuditInfoProvider.RequestInfoHeaderName, fs.String("unsupported.audit_info_provider.request_info_header_name", "", "Header name to get request audit event info from")}, - "unsupported.effective_user_header_name": {&config.Unsupported.EffectiveUserHeaderName, fs.String("unsupported.effective_user_header_name", "", "HTTP header name to get effective user id from")}, + "unsupported.diagnostic_interface": {config: &config.Unsupported.DiagnosticInterface, flagValue: fs.String("unsupported.diagnostic_interface", "", "Network interface to bind diagnostic API to")}, + "unsupported.stats_log_frequency": {config: &config.Unsupported.StatsLogFrequency, flagValue: fs.String("unsupported.stats_log_frequency", "", "How often should stats be written to stats logs")}, + "unsupported.use_stdlib_json": {config: &config.Unsupported.UseStdlibJSON, flagValue: fs.Bool("unsupported.use_stdlib_json", false, "Bypass the jsoniter package and use Go's stdlib instead")}, + "unsupported.http2.enabled": {config: &config.Unsupported.HTTP2.Enabled, flagValue: fs.Bool("unsupported.http2.enabled", false, "Whether HTTP2 support is enabled")}, + "unsupported.serverless.enabled": {config: &config.Unsupported.Serverless.Enabled, flagValue: fs.Bool("unsupported.serverless.enabled", false, "Settings for running Sync Gateway in serverless mode.")}, + "unsupported.serverless.min_config_fetch_interval": {config: &config.Unsupported.Serverless.MinConfigFetchInterval, flagValue: fs.String("unsupported.serverless.min_config_fetch_interval", "", "How long to cache configs fetched from the buckets for. This cache is used for requested databases that SG does not know about.")}, + "unsupported.use_xattr_config": {config: &config.Unsupported.UseXattrConfig, flagValue: fs.Bool("unsupported.use_xattr_config", false, "Store database configurations in system xattrs")}, + "unsupported.allow_dbconfig_env_vars": {config: &config.Unsupported.AllowDbConfigEnvVars, flagValue: fs.Bool("unsupported.allow_dbconfig_env_vars", true, "Can be set to false to skip environment variable expansion in database configs")}, + "unsupported.user_queries": {config: &config.Unsupported.UserQueries, flagValue: fs.Bool("unsupported.user_queries", false, "Whether user-query APIs are enabled")}, + "unsupported.audit_info_provider.global_info_env_var_name": {config: &config.Unsupported.AuditInfoProvider.GlobalInfoEnvVarName, flagValue: fs.String("unsupported.audit_info_provider.global_info_env_var_name", "", "Environment variable name to get global audit event info from")}, + "unsupported.audit_info_provider.request_info_header_name": {config: &config.Unsupported.AuditInfoProvider.RequestInfoHeaderName, flagValue: fs.String("unsupported.audit_info_provider.request_info_header_name", "", "Header name to get request audit event info from")}, + "unsupported.effective_user_header_name": {config: &config.Unsupported.EffectiveUserHeaderName, flagValue: fs.String("unsupported.effective_user_header_name", "", "HTTP header name to get effective user id from")}, - "database_credentials": {&config.DatabaseCredentials, fs.String("database_credentials", "null", "JSON-encoded per-database credentials, that can be used instead of the bootstrap ones. This will override bucket_credentials that target the bucket that the database is in.")}, - "bucket_credentials": {&config.BucketCredentials, fs.String("bucket_credentials", "null", "JSON-encoded per-bucket credentials, that can be used instead of the bootstrap ones.")}, + // Note: These flags are X.509-only. Username/passwords are rejected if specified, as we are not allowing them to be used in the command line flags, only the config file. + "database_credentials": {config: &config.DatabaseCredentials, flagValue: fs.String("database_credentials", "null", "JSON-encoded per-database credentials (X.509 only), that can be used instead of the bootstrap ones. This will override bucket_credentials that target the bucket that the database is in.")}, + "bucket_credentials": {config: &config.BucketCredentials, flagValue: fs.String("bucket_credentials", "null", "JSON-encoded per-bucket credentials (X.509 only), that can be used instead of the bootstrap ones.")}, - "max_file_descriptors": {&config.MaxFileDescriptors, fs.Uint64("max_file_descriptors", 0, "Max # of open file descriptors (RLIMIT_NOFILE)")}, + "max_file_descriptors": {config: &config.MaxFileDescriptors, flagValue: fs.Uint64("max_file_descriptors", 0, "Max # of open file descriptors (RLIMIT_NOFILE)")}, - "couchbase_keepalive_interval": {&config.CouchbaseKeepaliveInterval, fs.Int("couchbase_keepalive_interval", 0, "TCP keep-alive interval between SG and Couchbase server")}, + "couchbase_keepalive_interval": {config: &config.CouchbaseKeepaliveInterval, flagValue: fs.Int("couchbase_keepalive_interval", 0, "TCP keep-alive interval between SG and Couchbase server")}, } } @@ -177,6 +180,11 @@ func fillConfigWithFlags(fs *flag.FlagSet, flags map[string]configFlag) error { var errorMessages *base.MultiError fs.Visit(func(f *flag.Flag) { if val, exists := flags[f.Name]; exists { + // force disabled flags to error - we don't want users specifying them at all, even if they are non-functional + if val.disabled { + errorMessages = errorMessages.Append(fmt.Errorf("command line flag %q is no longer supported and must be removed %s", f.Name, val.disabledErrorMessage)) + return + } rval := reflect.ValueOf(val.config).Elem() pointer := true // Distinguish if to use rval.Set or *val.config @@ -272,32 +280,70 @@ func fillConfigWithFlags(fs *flag.FlagSet, flags map[string]configFlag) error { rval.Set(reflect.ValueOf(&ll)) case *PerDatabaseCredentialsConfig: str := *val.flagValue.(*string) - var dbCredentials PerDatabaseCredentialsConfig + // Decode X.509-only JSON and map into generic credentials config + var dbCredentials PerDatabaseCredentialsConfigX509 d := base.JSONDecoder(strings.NewReader(str)) d.DisallowUnknownFields() err := d.Decode(&dbCredentials) if err != nil { - err = fmt.Errorf("flag %s for value %q error: %w", f.Name, str, err) + err = fmt.Errorf( + "flag %s for value %q error: only X.509 cert/key paths are supported (fields: x509_cert_path, x509_key_path); username/password are not allowed: %w", + f.Name, str, err, + ) errorMessages = errorMessages.Append(err) return } - *val.config.(*PerDatabaseCredentialsConfig) = dbCredentials + *val.config.(*PerDatabaseCredentialsConfig) = *dbCredentials.asPerDatabaseCredentialsConfig() case *base.PerBucketCredentialsConfig: str := *val.flagValue.(*string) - var bucketCredentials base.PerBucketCredentialsConfig + // Decode X.509-only JSON and map into generic credentials config + var bucketCredentials PerBucketCredentialsConfigX509 d := base.JSONDecoder(strings.NewReader(str)) d.DisallowUnknownFields() err := d.Decode(&bucketCredentials) if err != nil { - err = fmt.Errorf("flag %s for value %q error: %w", f.Name, str, err) + err = fmt.Errorf( + "flag %s for value %q error: only X.509 cert/key paths are supported (fields: x509_cert_path, x509_key_path); username/password are not allowed: %w", + f.Name, str, err, + ) errorMessages = errorMessages.Append(err) return } - *val.config.(*base.PerBucketCredentialsConfig) = bucketCredentials + *val.config.(*base.PerBucketCredentialsConfig) = *bucketCredentials.asPerBucketCredentialsConfig() default: - errorMessages = errorMessages.Append(fmt.Errorf("Unknown type %v for flag %v\n", rval.Type(), f.Name)) + errorMessages = errorMessages.Append(fmt.Errorf("unknown type %v for flag %v", rval.Type(), f.Name)) } } }) return errorMessages.ErrorOrNil() } + +type PerDatabaseCredentialsConfigX509 map[string]*base.CredentialsConfigX509 +type PerBucketCredentialsConfigX509 map[string]*base.CredentialsConfigX509 + +// asPerDatabaseCredentialsConfig converts an X.509 specific PerDatabaseCredentialsConfig struct into a generic credentials config +func (dbCredsX509 *PerDatabaseCredentialsConfigX509) asPerDatabaseCredentialsConfig() *PerDatabaseCredentialsConfig { + perDatabaseCredentialsConfig := make(PerDatabaseCredentialsConfig) + for k, v := range *dbCredsX509 { + perDatabaseCredentialsConfig[k] = &base.CredentialsConfig{ + CredentialsConfigX509: base.CredentialsConfigX509{ + X509CertPath: v.X509CertPath, + X509KeyPath: v.X509KeyPath, + }, + } + } + return &perDatabaseCredentialsConfig +} + +func (bucketCredsX509 *PerBucketCredentialsConfigX509) asPerBucketCredentialsConfig() *base.PerBucketCredentialsConfig { + perBucketCredentialsConfig := make(base.PerBucketCredentialsConfig) + for k, v := range *bucketCredsX509 { + perBucketCredentialsConfig[k] = &base.CredentialsConfig{ + CredentialsConfigX509: base.CredentialsConfigX509{ + X509CertPath: v.X509CertPath, + X509KeyPath: v.X509KeyPath, + }, + } + } + return &perBucketCredentialsConfig +} diff --git a/rest/config_flags_test.go b/rest/config_flags_test.go index 938619f637..f4b2f382af 100644 --- a/rest/config_flags_test.go +++ b/rest/config_flags_test.go @@ -29,6 +29,10 @@ func TestAllConfigFlags(t *testing.T) { flags := []string{} for name, flagConfig := range flagMap { + // Skip disabled flags in this test - they will intentionally error when set + if flagConfig.disabled { + continue + } rFlagVal := reflect.ValueOf(flagConfig.flagValue).Elem() switch rFlagVal.Interface().(type) { case string: // Test different types of strings @@ -44,12 +48,12 @@ func TestAllConfigFlags(t *testing.T) { val = "partial" case *base.LogLevel: val = "trace" - case *PerDatabaseCredentialsConfig: - val = `{"db1":{"password":"foo"}}` - case *base.PerBucketCredentialsConfig: - val = `{"bucket":{"password":"foo"}}` case *[]uint: val = `123,456,789` + case *PerDatabaseCredentialsConfig: + val = `{"db1":{"x509_cert_path":"cert","x509_key_path":"key"}}` + case *base.PerBucketCredentialsConfig: + val = `{"bucket":{"x509_cert_path":"cert","x509_key_path":"key"}}` } flags = append(flags, "-"+name, val) case bool: @@ -167,3 +171,86 @@ func countFields(cfg interface{}) (fields int) { } return fields } + +// TestDisabledFlagsErrorAndDoNotMutateConfig ensures disabled flags error and values are not wired into StartupConfig. +func TestDisabledFlagsErrorAndDoNotMutateConfig(t *testing.T) { + fs := flag.NewFlagSet("test", flag.ContinueOnError) + config := NewEmptyStartupConfig() + + flags := registerConfigFlags(&config, fs) + + // Only disabled flag now is bootstrap.password + err := fs.Parse([]string{"-bootstrap.password", "sup3rsecret"}) + require.NoError(t, err) + + err = fillConfigWithFlags(fs, flags) + require.Error(t, err) + + // All disabled flags should be mentioned in the error + assert.Contains(t, err.Error(), "bootstrap.password") + assert.Contains(t, err.Error(), "Use config file to specify bootstrap password") + assert.Contains(t, err.Error(), "use X.509 cert/key path flags instead.") + + // And none should have modified the config + assert.Equal(t, "", config.Bootstrap.Password) +} + +// Validate x509-only JSON for per-db and per-bucket flags +func TestPerCredsFlagsX509Only(t *testing.T) { + fs := flag.NewFlagSet("test", flag.ContinueOnError) + config := NewEmptyStartupConfig() + flags := registerConfigFlags(&config, fs) + + // Valid X.509 only JSON should work + err := fs.Parse([]string{ + "-database_credentials", `{"db1":{"x509_cert_path":"cert","x509_key_path":"key"}}`, + "-bucket_credentials", `{"bucket":{"x509_cert_path":"cert","x509_key_path":"key"}}`, + }) + require.NoError(t, err) + require.NoError(t, fillConfigWithFlags(fs, flags)) + require.NotNil(t, config.DatabaseCredentials) + require.NotNil(t, config.BucketCredentials) + require.NotNil(t, config.DatabaseCredentials["db1"]) + require.NotNil(t, config.BucketCredentials["bucket"]) + assert.Equal(t, "cert", config.DatabaseCredentials["db1"].X509CertPath) + assert.Equal(t, "key", config.DatabaseCredentials["db1"].X509KeyPath) + assert.Equal(t, "cert", config.BucketCredentials["bucket"].X509CertPath) + assert.Equal(t, "key", config.BucketCredentials["bucket"].X509KeyPath) + + // Username/password must be rejected by JSON decoding (unknown fields) + fs = flag.NewFlagSet("test", flag.ContinueOnError) + config = NewEmptyStartupConfig() + flags = registerConfigFlags(&config, fs) + err = fs.Parse([]string{ + "-database_credentials", `{"db1":{"username":"u","password":"p"}}`, + "-bucket_credentials", `{"bucket":{"username":"u","password":"p"}}`, + }) + require.NoError(t, err) + err = fillConfigWithFlags(fs, flags) + require.Error(t, err) + assert.Contains(t, err.Error(), "database_credentials") + assert.Contains(t, err.Error(), "bucket_credentials") +} + +// TestPerCredsFlagsRejectBasicAuthWithHelpfulError ensures that per-db and per-bucket flags reject username/password and return clear X.509-only errors. +func TestPerCredsFlagsRejectBasicAuthWithHelpfulError(t *testing.T) { + fs := flag.NewFlagSet("test", flag.ContinueOnError) + config := NewEmptyStartupConfig() + flags := registerConfigFlags(&config, fs) + + // Provide username/password in JSON for both flags + err := fs.Parse([]string{ + "-database_credentials", `{"db1":{"username":"u","password":"p"}}`, + "-bucket_credentials", `{"bucket":{"username":"u","password":"p"}}`, + }) + require.NoError(t, err) + + err = fillConfigWithFlags(fs, flags) + require.Error(t, err) + // Check flag names are present + assert.Contains(t, err.Error(), "database_credentials") + assert.Contains(t, err.Error(), "bucket_credentials") + // Check helpful X.509-only guidance present + assert.Contains(t, err.Error(), "only X.509 cert/key paths are supported") + assert.Contains(t, err.Error(), "username/password are not allowed") +} diff --git a/rest/config_test.go b/rest/config_test.go index 199520db45..1bce37ac11 100644 --- a/rest/config_test.go +++ b/rest/config_test.go @@ -1805,14 +1805,14 @@ func TestSetupDbConfigCredentials(t *testing.T) { name: "db x509 override from username/password", dbConfig: DbConfig{Name: "db"}, bootstrapConfig: BootstrapConfig{Server: "couchbase://example.org", Username: "bob", Password: "foobar"}, - credentialsConfig: &base.CredentialsConfig{X509CertPath: expectedX509Cert, X509KeyPath: expectedX509Key}, + credentialsConfig: &base.CredentialsConfig{CredentialsConfigX509: base.CredentialsConfigX509{X509CertPath: expectedX509Cert, X509KeyPath: expectedX509Key}}, expectX509: true, }, { name: "db x509 override", dbConfig: DbConfig{Name: "db"}, bootstrapConfig: BootstrapConfig{Server: "couchbase://example.org", X509CertPath: "/tmp/bs-x509cert", X509KeyPath: "/tmp/bs-x509key"}, - credentialsConfig: &base.CredentialsConfig{X509CertPath: expectedX509Cert, X509KeyPath: expectedX509Key}, + credentialsConfig: &base.CredentialsConfig{CredentialsConfigX509: base.CredentialsConfigX509{X509CertPath: expectedX509Cert, X509KeyPath: expectedX509Key}}, expectX509: true, }, } @@ -2566,7 +2566,7 @@ func TestBucketCredentialsValidation(t *testing.T) { { name: "Valid bucket using x509", startupConfig: StartupConfig{ - BucketCredentials: base.PerBucketCredentialsConfig{"bucket": &base.CredentialsConfig{X509CertPath: "cert", X509KeyPath: "key"}}, + BucketCredentials: base.PerBucketCredentialsConfig{"bucket": &base.CredentialsConfig{CredentialsConfigX509: base.CredentialsConfigX509{X509CertPath: "cert", X509KeyPath: "key"}}}, }, }, { @@ -2584,48 +2584,48 @@ func TestBucketCredentialsValidation(t *testing.T) { name: "Blank database and filled bucket creds provided", startupConfig: StartupConfig{ DatabaseCredentials: PerDatabaseCredentialsConfig{}, - BucketCredentials: base.PerBucketCredentialsConfig{"bucket": &base.CredentialsConfig{X509CertPath: "cert", X509KeyPath: "key"}}, + BucketCredentials: base.PerBucketCredentialsConfig{"bucket": &base.CredentialsConfig{CredentialsConfigX509: base.CredentialsConfigX509{X509CertPath: "cert", X509KeyPath: "key"}}}, }, }, { name: "Filled database and blank bucket creds provided", startupConfig: StartupConfig{ BucketCredentials: base.PerBucketCredentialsConfig{}, - DatabaseCredentials: PerDatabaseCredentialsConfig{"bucket": &base.CredentialsConfig{X509CertPath: "cert", X509KeyPath: "key"}}, + DatabaseCredentials: PerDatabaseCredentialsConfig{"bucket": &base.CredentialsConfig{CredentialsConfigX509: base.CredentialsConfigX509{X509CertPath: "cert", X509KeyPath: "key"}}}, }, }, { name: "Database and bucket creds provided", startupConfig: StartupConfig{ - DatabaseCredentials: PerDatabaseCredentialsConfig{"bucket": &base.CredentialsConfig{X509CertPath: "cert", X509KeyPath: "key"}}, - BucketCredentials: base.PerBucketCredentialsConfig{"bucket": &base.CredentialsConfig{X509CertPath: "cert", X509KeyPath: "key"}}, + DatabaseCredentials: PerDatabaseCredentialsConfig{"bucket": &base.CredentialsConfig{CredentialsConfigX509: base.CredentialsConfigX509{X509CertPath: "cert", X509KeyPath: "key"}}}, + BucketCredentials: base.PerBucketCredentialsConfig{"bucket": &base.CredentialsConfig{CredentialsConfigX509: base.CredentialsConfigX509{X509CertPath: "cert", X509KeyPath: "key"}}}, }, }, { name: "Bucket creds for x509 and basic auth", startupConfig: StartupConfig{ - BucketCredentials: base.PerBucketCredentialsConfig{"bucket": &base.CredentialsConfig{X509CertPath: "cert", X509KeyPath: "key", Username: "uname", Password: "pword"}}, + BucketCredentials: base.PerBucketCredentialsConfig{"bucket": &base.CredentialsConfig{CredentialsConfigX509: base.CredentialsConfigX509{X509CertPath: "cert", X509KeyPath: "key"}, Username: "uname", Password: "pword"}}, }, expectedError: &bucketCredsError, }, { name: "Bucket creds for x509 key and basic auth password only", startupConfig: StartupConfig{ - BucketCredentials: base.PerBucketCredentialsConfig{"bucket": &base.CredentialsConfig{X509KeyPath: "key", Password: "pword"}}, + BucketCredentials: base.PerBucketCredentialsConfig{"bucket": &base.CredentialsConfig{CredentialsConfigX509: base.CredentialsConfigX509{X509KeyPath: "key"}, Password: "pword"}}, }, expectedError: &bucketCredsError, }, { name: "Bucket creds for x509 cert and basic auth username only", startupConfig: StartupConfig{ - BucketCredentials: base.PerBucketCredentialsConfig{"bucket": &base.CredentialsConfig{X509CertPath: "cert", Username: "uname"}}, + BucketCredentials: base.PerBucketCredentialsConfig{"bucket": &base.CredentialsConfig{CredentialsConfigX509: base.CredentialsConfigX509{X509CertPath: "cert"}, Username: "uname"}}, }, expectedError: &bucketCredsError, }, { name: "Bucket creds for x509 cert and basic auth username only", startupConfig: StartupConfig{ - BucketCredentials: base.PerBucketCredentialsConfig{"bucket": &base.CredentialsConfig{X509CertPath: "cert", Username: "uname"}}, + BucketCredentials: base.PerBucketCredentialsConfig{"bucket": &base.CredentialsConfig{CredentialsConfigX509: base.CredentialsConfigX509{X509CertPath: "cert"}, Username: "uname"}}, }, expectedError: &bucketCredsError, }, diff --git a/rest/main_test.go b/rest/main_test.go index a7500c041c..74678f4dff 100644 --- a/rest/main_test.go +++ b/rest/main_test.go @@ -171,7 +171,6 @@ func TestSanitizeDbConfigs(t *testing.T) { assert.Nil(t, dbConfigMap) require.Error(t, err) assert.Contains(t, err.Error(), test.expectedError) - return }) }