diff --git a/api/broker/broker.go b/api/broker/broker.go index dc560a25d..63257151d 100644 --- a/api/broker/broker.go +++ b/api/broker/broker.go @@ -89,15 +89,8 @@ func New( ) defaultCustomMetricsCredentialType := &models.X509Certificate - if len(conf.DefaultCustomMetricsCredentialType) > 0 { - var err error - defaultCustomMetricsCredentialType, err = models.ParseCustomMetricsBindingAuthScheme( - conf.DefaultCustomMetricsCredentialType) - if err != nil { - logger.Fatal("parse-default-credential-type", err, lager.Data{ - "default-credential-type": conf.DefaultCustomMetricsCredentialType, - }) - } + if basicAuthAvailable := conf.CustomMetricsAuthConfig != nil; basicAuthAvailable { + defaultCustomMetricsCredentialType = &conf.CustomMetricsAuthConfig.DefaultCustomMetricAuthType } pathToParserDir, err := filepath.Abs(filepath.Dir(conf.BindingRequestSchemaPath)) diff --git a/api/broker/broker_suite_test.go b/api/broker/broker_suite_test.go index 7ae4f4892..b2dd5dc93 100644 --- a/api/broker/broker_suite_test.go +++ b/api/broker/broker_suite_test.go @@ -51,7 +51,11 @@ var _ = BeforeSuite(func() { CatalogPath: "../exampleconfig/catalog-example.json", DashboardRedirectURI: dashBoardURL, BindingRequestSchemaPath: "./binding_request_parser/meta.schema.json", - DefaultCustomMetricsCredentialType: "binding-secret", + CustomMetricsAuthConfig: &config.CustomMetricsAuthConfig{ + BasicAuthHandling: config.BasicAuthHandlingOn, + DefaultCustomMetricAuthType: models.BindingSecret, + BasicAuthHandlingImplConfig: models.BasicAuthHandlingNative{}, + }, } catalogBytes, err := os.ReadFile("../exampleconfig/catalog-example.json") diff --git a/api/brokerserver/broker_server_suite_test.go b/api/brokerserver/broker_server_suite_test.go index e87ba057c..c6aa801d4 100644 --- a/api/brokerserver/broker_server_suite_test.go +++ b/api/brokerserver/broker_server_suite_test.go @@ -13,6 +13,7 @@ import ( "code.cloudfoundry.org/app-autoscaler/src/autoscaler/api/brokerserver" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/api/config" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/fakes" + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/models" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/routes" "github.com/onsi/gomega/ghttp" @@ -138,7 +139,11 @@ var _ = BeforeSuite(func() { MetricsForwarderUrl: "someURL", MetricsForwarderMtlsUrl: "Mtls-someURL", }, - DefaultCustomMetricsCredentialType: "binding-secret", + CustomMetricsAuthConfig: &config.CustomMetricsAuthConfig{ + BasicAuthHandling: config.BasicAuthHandlingOn, + DefaultCustomMetricAuthType: models.BindingSecret, + BasicAuthHandlingImplConfig: models.BasicAuthHandlingNative{}, + }, } fakeCfClient := &fakes.FakeCFClient{} fakeBindingDB := &fakes.FakeBindingDB{} diff --git a/api/cmd/api/api_suite_test.go b/api/cmd/api/api_suite_test.go index 57334d0ca..4fa3f4a5f 100644 --- a/api/cmd/api/api_suite_test.go +++ b/api/cmd/api/api_suite_test.go @@ -206,7 +206,11 @@ var _ = SynchronizedBeforeSuite(func() []byte { conf.RateLimit.MaxAmount = 10 conf.RateLimit.ValidDuration = 1 * time.Second - conf.CredHelperImpl = "default" + conf.CustomMetricsAuthConfig = &config.CustomMetricsAuthConfig{ + BasicAuthHandling: config.BasicAuthHandlingOn, + DefaultCustomMetricAuthType: models.BindingSecret, + BasicAuthHandlingImplConfig: models.BasicAuthHandlingNative{}, + } configFile = writeConfig(&conf) diff --git a/api/cmd/api/api_test.go b/api/cmd/api/api_test.go index 6e5026607..53abc3166 100644 --- a/api/cmd/api/api_test.go +++ b/api/cmd/api/api_test.go @@ -13,6 +13,7 @@ import ( "code.cloudfoundry.org/app-autoscaler/src/autoscaler/api/config" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/configutil" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/db" + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/models" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/testhelpers" . "github.com/onsi/ginkgo/v2" @@ -231,7 +232,11 @@ var _ = Describe("Api", func() { Describe("can start with default plugin", func() { BeforeEach(func() { pluginPathConfig := conf - pluginPathConfig.CredHelperImpl = "default" + pluginPathConfig.CustomMetricsAuthConfig = &config.CustomMetricsAuthConfig{ + BasicAuthHandling: config.BasicAuthHandlingOn, + DefaultCustomMetricAuthType: models.BindingSecret, + BasicAuthHandlingImplConfig: models.BasicAuthHandlingNative{}, + } runner.configPath = writeConfig(&pluginPathConfig).Name() }) AfterEach(func() { diff --git a/api/cmd/api/main.go b/api/cmd/api/main.go index abd10c641..3aa99859f 100644 --- a/api/cmd/api/main.go +++ b/api/cmd/api/main.go @@ -36,8 +36,17 @@ func main() { defer func() { _ = policyDb.Close() }() logger.Debug("Connected to PolicyDB", lager.Data{"dbConfig": conf.Db[db.PolicyDb]}) - credentialProvider := cred_helper.CredentialsProvider(conf.CredHelperImpl, conf.StoredProcedureConfig, conf.Db, 10*time.Second, 10*time.Minute, logger, policyDb) - defer func() { _ = credentialProvider.Close() }() + var credentialProvider cred_helper.Credentials + if conf.CustomMetricsAuthConfig != nil { + credentialProvider = cred_helper.CredentialsProvider( + conf.CustomMetricsAuthConfig.BasicAuthHandlingImplConfig, + conf.Db, 10*time.Second, 10*time.Minute, logger, policyDb) + } + defer func() { + if credentialProvider != nil { + _ = credentialProvider.Close() + } + }() httpStatusCollector := healthendpoint.NewHTTPStatusCollector("autoscaler", "golangapiserver") diff --git a/api/config/config.go b/api/config/config.go index 292ac371f..4ea4dcf52 100644 --- a/api/config/config.go +++ b/api/config/config.go @@ -1,18 +1,11 @@ package config import ( - "encoding/json" "errors" - "fmt" "strings" "time" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/cf" - "code.cloudfoundry.org/app-autoscaler/src/autoscaler/configutil" - - "golang.org/x/crypto/bcrypt" - - "github.com/xeipuuv/gojsonschema" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/db" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/helpers" @@ -95,251 +88,64 @@ type LowerUpperThresholdConfig struct { } type Config struct { - Logging helpers.LoggingConfig `yaml:"logging" json:"logging"` - BrokerServer helpers.ServerConfig `yaml:"broker_server" json:"broker_server"` - Server helpers.ServerConfig `yaml:"public_api_server" json:"public_api_server"` + Logging helpers.LoggingConfig + BrokerServer helpers.ServerConfig + Server helpers.ServerConfig - CFServer helpers.ServerConfig `yaml:"cf_server" json:"cf_server"` + CFServer helpers.ServerConfig - Db map[string]db.DatabaseConfig `yaml:"db" json:"db,omitempty"` - BrokerCredentials []BrokerCredentialsConfig `yaml:"broker_credentials" json:"broker_credentials"` - APIClientId string `yaml:"api_client_id" json:"api_client_id"` - PlanCheck *PlanCheckConfig `yaml:"plan_check" json:"plan_check"` - CatalogPath string `yaml:"catalog_path" json:"catalog_path"` - CatalogSchemaPath string `yaml:"catalog_schema_path" json:"catalog_schema_path"` - DashboardRedirectURI string `yaml:"dashboard_redirect_uri" json:"dashboard_redirect_uri"` - BindingRequestSchemaPath string `yaml:"policy_schema_path" json:"policy_schema_path"` - Scheduler SchedulerConfig `yaml:"scheduler" json:"scheduler"` - ScalingEngine ScalingEngineConfig `yaml:"scaling_engine" json:"scaling_engine"` - EventGenerator EventGeneratorConfig `yaml:"event_generator" json:"event_generator"` - CF cf.Config `yaml:"cf" json:"cf"` - InfoFilePath string `yaml:"info_file_path" json:"info_file_path"` - MetricsForwarder MetricsForwarderConfig `yaml:"metrics_forwarder" json:"metrics_forwarder"` - Health helpers.HealthConfig `yaml:"health" json:"health"` - RateLimit models.RateLimitConfig `yaml:"rate_limit" json:"rate_limit,omitempty"` - CredHelperImpl string `yaml:"cred_helper_impl" json:"cred_helper_impl"` - StoredProcedureConfig *models.StoredProcedureConfig `yaml:"stored_procedure_binding_credential_config" json:"stored_procedure_binding_credential_config"` - ScalingRules ScalingRulesConfig `yaml:"scaling_rules" json:"scaling_rules"` - DefaultCustomMetricsCredentialType string `yaml:"default_credential_type" json:"default_credential_type"` -} + Db map[string]db.DatabaseConfig + BrokerCredentials []BrokerCredentialsConfig + APIClientId string + PlanCheck *PlanCheckConfig + CatalogPath string + CatalogSchemaPath string + DashboardRedirectURI string + BindingRequestSchemaPath string + Scheduler SchedulerConfig + ScalingEngine ScalingEngineConfig + EventGenerator EventGeneratorConfig + CF cf.Config + InfoFilePath string + MetricsForwarder MetricsForwarderConfig + Health helpers.HealthConfig + RateLimit models.RateLimitConfig + ScalingRules ScalingRulesConfig -func (c *Config) SetLoggingLevel() { - c.Logging.Level = strings.ToLower(c.Logging.Level) + CustomMetricsAuthConfig *CustomMetricsAuthConfig } -// GetLogging returns the logging configuration -func (c *Config) GetLogging() *helpers.LoggingConfig { - return &c.Logging -} +type CustomMetricsAuthConfig struct { + // Configures if "Basic Authentication" is generally available or only available for already + // bound apps. + BasicAuthHandling BasicAuthHandling -type PlanCheckConfig struct { - PlanDefinitions map[string]PlanDefinition `yaml:"plan_definitions" json:"plan_definitions"` -} + // Configures the authentication method that is used by default for sending custom metrics. + DefaultCustomMetricAuthType models.CustomMetricsBindingAuthScheme -func defaultConfig() Config { - return Config{ - Logging: defaultLoggingConfig, - BrokerServer: defaultBrokerServerConfig, - Server: defaultPublicApiServerConfig, - CF: cf.Config{ - ClientConfig: cf.ClientConfig{ - SkipSSLValidation: false, - }, - }, - Db: make(map[string]db.DatabaseConfig), - RateLimit: models.RateLimitConfig{ - MaxAmount: DefaultMaxAmount, - ValidDuration: DefaultValidDuration, - }, - ScalingRules: ScalingRulesConfig{ - CPU: LowerUpperThresholdConfig{ - LowerThreshold: DefaultCPULowerThreshold, - UpperThreshold: DefaultCPUUpperThreshold, - }, - CPUUtil: LowerUpperThresholdConfig{ - LowerThreshold: DefaultCPUUtilLowerThreshold, - UpperThreshold: DefaultCPUUtilUpperThreshold, - }, - DiskUtil: LowerUpperThresholdConfig{ - LowerThreshold: DefaultDiskUtilLowerThreshold, - UpperThreshold: DefaultDiskUtilUpperThreshold, - }, - Disk: LowerUpperThresholdConfig{ - LowerThreshold: DefaultDiskLowerThreshold, - UpperThreshold: DefaultDiskUpperThreshold, - }, - }, - } + // Configures how "Basic Authentication" is done. + BasicAuthHandlingImplConfig models.BasicAuthHandlingImplConfig } -func LoadVcapConfig(conf *Config, vcapReader configutil.VCAPConfigurationReader) error { - if !vcapReader.IsRunningOnCF() { - return nil - } - - tlsCert := vcapReader.GetInstanceTLSCerts() - - // enable plain text logging. See src/autoscaler/helpers/logger.go - conf.Logging.PlainTextSink = true - - // Avoid port conflict: assign actual port to CF server, set BOSH server port to 0 (unused) - conf.CFServer.Port = vcapReader.GetPort() - conf.Server.Port = 0 - - if err := configutil.LoadConfig(&conf, vcapReader, "apiserver-config"); err != nil { - return err - } - - if err := vcapReader.ConfigureDatabases(&conf.Db, conf.StoredProcedureConfig, conf.CredHelperImpl); err != nil { - return err - } - - if err := configureCatalog(conf, vcapReader); err != nil { - return err - } - - conf.ScalingEngine.TLSClientCerts = tlsCert - conf.EventGenerator.TLSClientCerts = tlsCert - conf.Scheduler.TLSClientCerts = tlsCert - - return nil -} - -func configureCatalog(conf *Config, vcapReader configutil.VCAPConfigurationReader) error { - catalog, err := vcapReader.GetServiceCredentialContent("broker-catalog", "broker-catalog") - if err != nil { - return err - } - - catalogPath, err := configutil.MaterializeContentInTmpFile("publicapi", "catalog.json", string(catalog)) - if err != nil { - return err - } +type BasicAuthHandling int - conf.CatalogPath = catalogPath +const ( + BasicAuthHandlingOn BasicAuthHandling = iota + BasicAuthHandlingOnlyExistingBindings - return err -} + // // The follwing gets encoded by having or not having a `CustomMetricsAuthConfig` at all. + // BasicAuthHandlingOff +) -func LoadConfig(filepath string, vcapReader configutil.VCAPConfigurationReader) (*Config, error) { - return configutil.GenericLoadConfig(filepath, vcapReader, defaultConfig, configutil.VCAPConfigurableFunc[Config](LoadVcapConfig)) +func (c *Config) SetLoggingLevel() { + c.Logging.Level = strings.ToLower(c.Logging.Level) } -func FromJSON(data []byte) (*Config, error) { - result := &Config{} - err := json.Unmarshal(data, result) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal config from json: %s", err) - } - return result, nil +// GetLogging returns the logging configuration +func (c *Config) GetLogging() *helpers.LoggingConfig { + return &c.Logging } -func (c *Config) Validate() error { - err := c.CF.Validate() - if err != nil { - return err - } - - if c.Db[db.PolicyDb].URL == "" { - return fmt.Errorf("Configuration error: PolicyDB URL is empty") - } - if c.Scheduler.SchedulerURL == "" { - return fmt.Errorf("Configuration error: scheduler.scheduler_url is empty") - } - if c.ScalingEngine.ScalingEngineUrl == "" { - return fmt.Errorf("Configuration error: scaling_engine.scaling_engine_url is empty") - } - if c.EventGenerator.EventGeneratorUrl == "" { - return fmt.Errorf("Configuration error: event_generator.event_generator_url is empty") - } - if c.MetricsForwarder.MetricsForwarderUrl == "" { - return fmt.Errorf("Configuration error: metrics_forwarder.metrics_forwarder_url is empty") - } - if c.BindingRequestSchemaPath == "" { - return fmt.Errorf("Configuration error: PolicySchemaPath is empty") - } - if c.RateLimit.MaxAmount <= 0 { - return fmt.Errorf("Configuration error: RateLimit.MaxAmount is equal or less than zero") - } - if c.RateLimit.ValidDuration <= 0*time.Nanosecond { - return fmt.Errorf("Configuration error: RateLimit.ValidDuration is equal or less than zero nanosecond") - } - if err := c.Health.Validate(); err != nil { - return err - } - - if c.InfoFilePath == "" { - return fmt.Errorf("Configuration error: InfoFilePath is empty") - } - - if c.ScalingRules.CPU.LowerThreshold < 0 { - return fmt.Errorf("Configuration error: ScalingRules.CPU.LowerThreshold is less than zero") - } - - if c.ScalingRules.CPU.UpperThreshold < 0 { - return fmt.Errorf("Configuration error: ScalingRules.CPU.UpperThreshold is less than zero") - } - - if c.Db[db.BindingDb].URL == "" { - return fmt.Errorf("Configuration error: BindingDB URL is empty") - } - - for _, brokerCredential := range c.BrokerCredentials { - if brokerCredential.BrokerUsername == "" && string(brokerCredential.BrokerUsernameHash) == "" { - return fmt.Errorf("Configuration error: both broker_username and broker_username_hash are empty, please provide one of them") - } - if brokerCredential.BrokerUsername != "" && string(brokerCredential.BrokerUsernameHash) != "" { - return fmt.Errorf("Configuration error: both broker_username and broker_username_hash are set, please provide only one of them") - } - if string(brokerCredential.BrokerUsernameHash) != "" { - if _, err := bcrypt.Cost(brokerCredential.BrokerUsernameHash); err != nil { - return fmt.Errorf("Configuration error: broker_username_hash is not a valid bcrypt hash") - } - } - if brokerCredential.BrokerPassword == "" && string(brokerCredential.BrokerPasswordHash) == "" { - return fmt.Errorf("Configuration error: both broker_password and broker_password_hash are empty, please provide one of them") - } - - if brokerCredential.BrokerPassword != "" && string(brokerCredential.BrokerPasswordHash) != "" { - return fmt.Errorf("Configuration error: both broker_password and broker_password_hash are set, please provide only one of them") - } - - if string(brokerCredential.BrokerPasswordHash) != "" { - if _, err := bcrypt.Cost(brokerCredential.BrokerPasswordHash); err != nil { - return fmt.Errorf("Configuration error: broker_password_hash is not a valid bcrypt hash") - } - } - } - - if c.CatalogSchemaPath == "" { - return fmt.Errorf("Configuration error: CatalogSchemaPath is empty") - } - if c.CatalogPath == "" { - return fmt.Errorf("Configuration error: CatalogPath is empty") - } - if c.CredHelperImpl == "" { - return fmt.Errorf("Configuration error: CredHelperImpl is empty") - } - - catalogSchemaLoader := gojsonschema.NewReferenceLoader("file://" + c.CatalogSchemaPath) - catalogLoader := gojsonschema.NewReferenceLoader("file://" + c.CatalogPath) - - result, err := gojsonschema.Validate(catalogSchemaLoader, catalogLoader) - if err != nil { - return err - } - if !result.Valid() { - errString := "{" - for index, desc := range result.Errors() { - if index == len(result.Errors())-1 { - errString += fmt.Sprintf("\"%s\"", desc.Description()) - } else { - errString += fmt.Sprintf("\"%s\",", desc.Description()) - } - } - errString += "}" - return errors.New(errString) - } - - return nil +type PlanCheckConfig struct { + PlanDefinitions map[string]PlanDefinition `yaml:"plan_definitions" json:"plan_definitions"` } diff --git a/api/config/config_test.go b/api/config/config_test.go index 97a79ddf7..f3de50149 100644 --- a/api/config/config_test.go +++ b/api/config/config_test.go @@ -78,7 +78,7 @@ var _ = Describe("Config", func() { When("handling available databases", func() { It("calls vcapReader ConfigureDatabases with the right arguments", func() { - testhelpers.ExpectConfigureDatabasesCalledOnce(err, mockVCAPConfigurationReader, conf.CredHelperImpl) + testhelpers.ExpectConfigureDatabasesCalledOnce(err, mockVCAPConfigurationReader) }) }) @@ -203,7 +203,8 @@ var _ = Describe("Config", func() { }, }, )) - Expect(conf.CredHelperImpl).To(Equal("default")) + Expect((*conf.CustomMetricsAuthConfig).BasicAuthHandlingImplConfig). + To(BeAssignableToTypeOf(models.BasicAuthHandlingNative{})) Expect(conf.ScalingRules.CPU.LowerThreshold).To(Equal(22)) Expect(conf.ScalingRules.CPU.UpperThreshold).To(Equal(33)) Expect(conf.ScalingRules.CPUUtil.LowerThreshold).To(Equal(22)) @@ -321,7 +322,11 @@ rate_limit: conf.RateLimit.MaxAmount = 10 conf.RateLimit.ValidDuration = 1 * time.Second - conf.CredHelperImpl = "path/to/plugin" + conf.CustomMetricsAuthConfig = &CustomMetricsAuthConfig{ + BasicAuthHandling: BasicAuthHandlingOn, + DefaultCustomMetricAuthType: models.X509Certificate, + BasicAuthHandlingImplConfig: models.BasicAuthHandlingNative{}, + } }) JustBeforeEach(func() { err = conf.Validate() diff --git a/api/config/parser.go b/api/config/parser.go new file mode 100644 index 000000000..07c3899ee --- /dev/null +++ b/api/config/parser.go @@ -0,0 +1,286 @@ +package config + +import ( + "errors" + "fmt" + "time" + + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/cf" + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/configutil" + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/db" + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/helpers" + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/models" + "github.com/xeipuuv/gojsonschema" + "golang.org/x/crypto/bcrypt" +) + +type rawConfig struct { + Logging helpers.LoggingConfig `yaml:"logging" json:"logging"` + BrokerServer helpers.ServerConfig `yaml:"broker_server" json:"broker_server"` + Server helpers.ServerConfig `yaml:"public_api_server" json:"public_api_server"` + + CFServer helpers.ServerConfig `yaml:"cf_server" json:"cf_server"` + + Db map[string]db.DatabaseConfig `yaml:"db" json:"db,omitempty"` + BrokerCredentials []BrokerCredentialsConfig `yaml:"broker_credentials" json:"broker_credentials"` + APIClientId string `yaml:"api_client_id" json:"api_client_id"` + PlanCheck *PlanCheckConfig `yaml:"plan_check" json:"plan_check"` + CatalogPath string `yaml:"catalog_path" json:"catalog_path"` + CatalogSchemaPath string `yaml:"catalog_schema_path" json:"catalog_schema_path"` + DashboardRedirectURI string `yaml:"dashboard_redirect_uri" json:"dashboard_redirect_uri"` + BindingRequestSchemaPath string `yaml:"policy_schema_path" json:"policy_schema_path"` + Scheduler SchedulerConfig `yaml:"scheduler" json:"scheduler"` + ScalingEngine ScalingEngineConfig `yaml:"scaling_engine" json:"scaling_engine"` + EventGenerator EventGeneratorConfig `yaml:"event_generator" json:"event_generator"` + CF cf.Config `yaml:"cf" json:"cf"` + InfoFilePath string `yaml:"info_file_path" json:"info_file_path"` + MetricsForwarder MetricsForwarderConfig `yaml:"metrics_forwarder" json:"metrics_forwarder"` + Health helpers.HealthConfig `yaml:"health" json:"health"` + RateLimit models.RateLimitConfig `yaml:"rate_limit" json:"rate_limit,omitempty"` + CredHelperImpl string `yaml:"cred_helper_impl" json:"cred_helper_impl"` + StoredProcedureConfig *models.StoredProcedureConfig `yaml:"stored_procedure_binding_credential_config" json:"stored_procedure_binding_credential_config"` + ScalingRules ScalingRulesConfig `yaml:"scaling_rules" json:"scaling_rules"` + DefaultCustomMetricsCredentialType string `yaml:"default_credential_type" json:"default_credential_type"` + BasicAuthForCustomMetrics string `yaml:"basic_auth_for_custom_metrics" json:"basic_auth_for_custom_metrics"` +} + +func toConfig(rawConfig rawConfig) (Config, error) { + return Config{}, models.ErrUnimplemented +} + +// 🚧 To-do: Should go to =parser.go=, in particular it should be private and called from +// `toConfig`. +func (c *Config) Validate() error { + err := c.CF.Validate() + if err != nil { + return err + } + + if c.Db[db.PolicyDb].URL == "" { + return fmt.Errorf("Configuration error: PolicyDB URL is empty") + } + if c.Scheduler.SchedulerURL == "" { + return fmt.Errorf("Configuration error: scheduler.scheduler_url is empty") + } + if c.ScalingEngine.ScalingEngineUrl == "" { + return fmt.Errorf("Configuration error: scaling_engine.scaling_engine_url is empty") + } + if c.EventGenerator.EventGeneratorUrl == "" { + return fmt.Errorf("Configuration error: event_generator.event_generator_url is empty") + } + if c.MetricsForwarder.MetricsForwarderUrl == "" { + return fmt.Errorf("Configuration error: metrics_forwarder.metrics_forwarder_url is empty") + } + if c.BindingRequestSchemaPath == "" { + return fmt.Errorf("Configuration error: PolicySchemaPath is empty") + } + if c.RateLimit.MaxAmount <= 0 { + return fmt.Errorf("Configuration error: RateLimit.MaxAmount is equal or less than zero") + } + if c.RateLimit.ValidDuration <= 0*time.Nanosecond { + return fmt.Errorf("Configuration error: RateLimit.ValidDuration is equal or less than zero nanosecond") + } + if err := c.Health.Validate(); err != nil { + return err + } + + if c.InfoFilePath == "" { + return fmt.Errorf("Configuration error: InfoFilePath is empty") + } + + if c.ScalingRules.CPU.LowerThreshold < 0 { + return fmt.Errorf("Configuration error: ScalingRules.CPU.LowerThreshold is less than zero") + } + + if c.ScalingRules.CPU.UpperThreshold < 0 { + return fmt.Errorf("Configuration error: ScalingRules.CPU.UpperThreshold is less than zero") + } + + if c.Db[db.BindingDb].URL == "" { + return fmt.Errorf("Configuration error: BindingDB URL is empty") + } + + for _, brokerCredential := range c.BrokerCredentials { + if brokerCredential.BrokerUsername == "" && string(brokerCredential.BrokerUsernameHash) == "" { + return fmt.Errorf("Configuration error: both broker_username and broker_username_hash are empty, please provide one of them") + } + if brokerCredential.BrokerUsername != "" && string(brokerCredential.BrokerUsernameHash) != "" { + return fmt.Errorf("Configuration error: both broker_username and broker_username_hash are set, please provide only one of them") + } + if string(brokerCredential.BrokerUsernameHash) != "" { + if _, err := bcrypt.Cost(brokerCredential.BrokerUsernameHash); err != nil { + return fmt.Errorf("Configuration error: broker_username_hash is not a valid bcrypt hash") + } + } + if brokerCredential.BrokerPassword == "" && string(brokerCredential.BrokerPasswordHash) == "" { + return fmt.Errorf("Configuration error: both broker_password and broker_password_hash are empty, please provide one of them") + } + + if brokerCredential.BrokerPassword != "" && string(brokerCredential.BrokerPasswordHash) != "" { + return fmt.Errorf("Configuration error: both broker_password and broker_password_hash are set, please provide only one of them") + } + + if string(brokerCredential.BrokerPasswordHash) != "" { + if _, err := bcrypt.Cost(brokerCredential.BrokerPasswordHash); err != nil { + return fmt.Errorf("Configuration error: broker_password_hash is not a valid bcrypt hash") + } + } + } + + if c.CatalogSchemaPath == "" { + return fmt.Errorf("Configuration error: CatalogSchemaPath is empty") + } + if c.CatalogPath == "" { + return fmt.Errorf("Configuration error: CatalogPath is empty") + } + // // Temporarily commented – after adaption still required. + // if c.CredHelperImpl == "" { + // return fmt.Errorf("Configuration error: CredHelperImpl is empty") + // } + + catalogSchemaLoader := gojsonschema.NewReferenceLoader("file://" + c.CatalogSchemaPath) + catalogLoader := gojsonschema.NewReferenceLoader("file://" + c.CatalogPath) + + result, err := gojsonschema.Validate(catalogSchemaLoader, catalogLoader) + if err != nil { + return err + } + if !result.Valid() { + errString := "{" + for index, desc := range result.Errors() { + if index == len(result.Errors())-1 { + errString += fmt.Sprintf("\"%s\"", desc.Description()) + } else { + errString += fmt.Sprintf("\"%s\",", desc.Description()) + } + } + errString += "}" + return errors.New(errString) + } + + return nil +} + +// ================================================================================ +// Legacy parsing-machinery +// ================================================================================ +// +// 🏚️ This is legacy spaghetti-code from the migration from "Bosh" to the "Cloud Controller". Afer removing the support for "Bosh", revisiting makes sense. Ideally, a signature like `func FromCFEnv(env map[string]string) (Config, error)` would be best. + +func LoadConfig(filepath string, vcapReader configutil.VCAPConfigurationReader) (*Config, error) { + rawConfig, err := configutil.GenericLoadConfig(filepath, vcapReader, defaultConfig, configutil.VCAPConfigurableFunc[rawConfig](LoadVcapConfig)) + if err != nil { + return nil, err + } + + config, err := toConfig(*rawConfig) + return &config, err +} + +func LoadVcapConfig(conf *rawConfig, vcapReader configutil.VCAPConfigurationReader) error { + if !vcapReader.IsRunningOnCF() { + return nil + } + + tlsCert := vcapReader.GetInstanceTLSCerts() + + // enable plain text logging. See src/autoscaler/helpers/logger.go + conf.Logging.PlainTextSink = true + + // Avoid port conflict: assign actual port to CF server, set BOSH server port to 0 (unused) + conf.CFServer.Port = vcapReader.GetPort() + conf.Server.Port = 0 + + if err := configutil.LoadConfig(&conf, vcapReader, "apiserver-config"); err != nil { + return err + } + + var basicAuthImplCfg *models.BasicAuthHandlingImplConfig + if conf.CredHelperImpl != "" { + var impl models.BasicAuthHandlingImplConfig + if conf.CredHelperImpl == "stored_procedure" && conf.StoredProcedureConfig != nil { + impl = models.BasicAuthHandlingStoredProc{Config: *conf.StoredProcedureConfig} + } else { + impl = models.BasicAuthHandlingNative{} + } + basicAuthImplCfg = &impl + } + if err := vcapReader.ConfigureDatabases(&conf.Db, basicAuthImplCfg); err != nil { + return err + } + + // var storedProcConfig *models.StoredProcedureConfig + // var credHelperImpl string + // switch authImpl := conf.CustomMetricsAuthConfig.BasicAuthHandlingImplConfig.(type) { + // case BasicAuthHandlingStoredProc: + // storedProcConfig = &authImpl.config + // credHelperImpl = "stored_procedure" + // default: + // storedProcConfig = nil + // } + // if err := vcapReader.ConfigureDatabases(&conf.Db, storedProcConfig, credHelperImpl); err != nil { + // return err + // } + + if err := configureCatalog(conf, vcapReader); err != nil { + return err + } + + conf.ScalingEngine.TLSClientCerts = tlsCert + conf.EventGenerator.TLSClientCerts = tlsCert + conf.Scheduler.TLSClientCerts = tlsCert + + return nil +} + +func configureCatalog(conf *rawConfig, vcapReader configutil.VCAPConfigurationReader) error { + catalog, err := vcapReader.GetServiceCredentialContent("broker-catalog", "broker-catalog") + if err != nil { + return err + } + + catalogPath, err := configutil.MaterializeContentInTmpFile("publicapi", "catalog.json", string(catalog)) + if err != nil { + return err + } + + conf.CatalogPath = catalogPath + + return err +} + +func defaultConfig() rawConfig { + return rawConfig{ + Logging: defaultLoggingConfig, + BrokerServer: defaultBrokerServerConfig, + Server: defaultPublicApiServerConfig, + CF: cf.Config{ + ClientConfig: cf.ClientConfig{ + SkipSSLValidation: false, + }, + }, + Db: make(map[string]db.DatabaseConfig), + RateLimit: models.RateLimitConfig{ + MaxAmount: DefaultMaxAmount, + ValidDuration: DefaultValidDuration, + }, + ScalingRules: ScalingRulesConfig{ + CPU: LowerUpperThresholdConfig{ + LowerThreshold: DefaultCPULowerThreshold, + UpperThreshold: DefaultCPUUpperThreshold, + }, + CPUUtil: LowerUpperThresholdConfig{ + LowerThreshold: DefaultCPUUtilLowerThreshold, + UpperThreshold: DefaultCPUUtilUpperThreshold, + }, + DiskUtil: LowerUpperThresholdConfig{ + LowerThreshold: DefaultDiskUtilLowerThreshold, + UpperThreshold: DefaultDiskUtilUpperThreshold, + }, + Disk: LowerUpperThresholdConfig{ + LowerThreshold: DefaultDiskLowerThreshold, + UpperThreshold: DefaultDiskUpperThreshold, + }, + }, + } +} diff --git a/configutil/cf.go b/configutil/cf.go index f40573947..b1ea35db4 100644 --- a/configutil/cf.go +++ b/configutil/cf.go @@ -37,7 +37,7 @@ type VCAPConfigurationReader interface { GetInstanceIndex() int IsRunningOnCF() bool - ConfigureDatabases(confDb *map[string]db.DatabaseConfig, storedProcedureConfig *models.StoredProcedureConfig, credHelperImpl string) error + ConfigureDatabases(confDb *map[string]db.DatabaseConfig, basicAuthImplCfg *models.BasicAuthHandlingImplConfig) error } type VCAPConfiguration struct { @@ -305,16 +305,19 @@ func LoadConfig[T any](conf *T, vcapReader VCAPConfigurationReader, credentialNa return yaml.Unmarshal(data, conf) } -func (vc *VCAPConfiguration) ConfigureDatabases(confDb *map[string]db.DatabaseConfig, storedProcedureConfig *models.StoredProcedureConfig, credHelperImpl string) error { +func (vc *VCAPConfiguration) ConfigureDatabases(confDb *map[string]db.DatabaseConfig, basicAuthImplCfg *models.BasicAuthHandlingImplConfig) error { for _, dbName := range AvailableDatabases { if err := vc.configureDb(dbName, confDb); err != nil { return err } } - if storedProcedureConfig != nil && credHelperImpl == "stored_procedure" { - if err := vc.configureStoredProcedureDb(db.StoredProcedureDb, confDb, storedProcedureConfig); err != nil { - return err + if basicAuthAvailable := basicAuthImplCfg != nil; basicAuthAvailable { + impl, isWithStoredProc := (*basicAuthImplCfg).(models.BasicAuthHandlingStoredProc) + if isWithStoredProc { + if err := vc.configureStoredProcedureDb(db.StoredProcedureDb, confDb, &impl.Config); err != nil { + return err + } } } @@ -394,7 +397,7 @@ func ApplyCommonVCAPConfiguration[T any, PT interface { return err } - if err := vcapReader.ConfigureDatabases(conf.GetDatabaseConfig(), nil, ""); err != nil { + if err := vcapReader.ConfigureDatabases(conf.GetDatabaseConfig(), nil); err != nil { return err } diff --git a/configutil/configutil_test.go b/configutil/configutil_test.go index f02f036b6..7678cebde 100644 --- a/configutil/configutil_test.go +++ b/configutil/configutil_test.go @@ -169,7 +169,7 @@ var _ = Describe("Configutil", func() { vcapApplicationJson = `{}` actualDbs = &map[string]db.DatabaseConfig{} }) - When("stored procedure implementation is set to stored_procedure", func() { + When("stored procedure implementation is set to stored_procedure", func() { var actualProcedureConfig *models.StoredProcedureConfig BeforeEach(func() { @@ -207,7 +207,8 @@ var _ = Describe("Configutil", func() { }) It("loads the db config from VCAP_SERVICES successfully", func() { - err := vcapConfiguration.ConfigureDatabases(actualDbs, actualProcedureConfig, "stored_procedure") + var implConfig models.BasicAuthHandlingImplConfig = models.BasicAuthHandlingStoredProc{Config: *actualProcedureConfig} + err := vcapConfiguration.ConfigureDatabases(actualDbs, &implConfig) Expect(err).NotTo(HaveOccurred()) Expect(*actualDbs).To(Equal(*expectedDbs)) }) @@ -238,7 +239,8 @@ var _ = Describe("Configutil", func() { db.SchedulerDb: {}, } - err := vcapConfiguration.ConfigureDatabases(actualDbs, actualProcedureConfig, "stored_procedure") + var implConfig models.BasicAuthHandlingImplConfig = models.BasicAuthHandlingStoredProc{Config: *actualProcedureConfig} + err := vcapConfiguration.ConfigureDatabases(actualDbs, &implConfig) Expect(err).NotTo(HaveOccurred()) Expect(*actualDbs).To(Equal(*expectedDbs)) }) @@ -284,7 +286,7 @@ var _ = Describe("Configutil", func() { }) It("loads the db config from VCAP_SERVICES successfully", func() { - err := vcapConfiguration.ConfigureDatabases(actualDbs, nil, "default") + err := vcapConfiguration.ConfigureDatabases(actualDbs, nil) Expect(err).NotTo(HaveOccurred()) Expect(*actualDbs).To(Equal(*expectedDbs)) }) diff --git a/cred_helper/custom_metrics_cred_helper.go b/cred_helper/custom_metrics_cred_helper.go index 2aa4e3dfc..5b1f3a95c 100644 --- a/cred_helper/custom_metrics_cred_helper.go +++ b/cred_helper/custom_metrics_cred_helper.go @@ -36,15 +36,11 @@ func (c *customMetricsCredentials) Close() error { return c.policyDB.Close() } -func CredentialsProvider(credHelperImpl string, storedProcedureConfig *models.StoredProcedureConfig, dbConf map[string]db.DatabaseConfig, cacheTTL time.Duration, cacheCleanupInterval time.Duration, logger lager.Logger, policyDB db.PolicyDB) Credentials { +func CredentialsProvider(implConfig models.BasicAuthHandlingImplConfig, dbConf map[string]db.DatabaseConfig, cacheTTL time.Duration, cacheCleanupInterval time.Duration, logger lager.Logger, policyDB db.PolicyDB) Credentials { var credentials Credentials - switch credHelperImpl { - case "stored_procedure": - if storedProcedureConfig == nil { - logger.Fatal("cannot create a storedProcedureCredHelper without StoredProcedureConfig", nil) - os.Exit(1) - } - storedProcedureDb, err := sqldb.NewStoredProcedureSQLDb(*storedProcedureConfig, dbConf[db.StoredProcedureDb], logger.Session("storedprocedure-db")) + switch impl := implConfig.(type) { + case models.BasicAuthHandlingStoredProc: + storedProcedureDb, err := sqldb.NewStoredProcedureSQLDb(impl.Config, dbConf[db.StoredProcedureDb], logger.Session("storedprocedure-db")) if err != nil { logger.Fatal("failed to connect to storedProcedureDb database", err, lager.Data{"dbConfig": dbConf[db.StoredProcedureDb]}) os.Exit(1) diff --git a/eventgenerator/config/config_test.go b/eventgenerator/config/config_test.go index d6b55283c..77b1643e2 100644 --- a/eventgenerator/config/config_test.go +++ b/eventgenerator/config/config_test.go @@ -97,7 +97,7 @@ var _ = Describe("Config", func() { When("handling available databases", func() { It("calls vcapReader ConfigureDatabases with the right arguments", func() { - testhelpers.ExpectConfigureDatabasesCalledOnce(err, mockVCAPConfigurationReader, "") + testhelpers.ExpectConfigureDatabasesCalledOnce(err, mockVCAPConfigurationReader) }) }) diff --git a/metricsforwarder/cmd/metricsforwarder/main.go b/metricsforwarder/cmd/metricsforwarder/main.go index 6b8d07761..4e97ec3da 100644 --- a/metricsforwarder/cmd/metricsforwarder/main.go +++ b/metricsforwarder/cmd/metricsforwarder/main.go @@ -41,8 +41,17 @@ func main() { bindingDB := sqldb.CreateBindingDB(conf.Db[db.BindingDb], logger) defer func() { _ = bindingDB.Close() }() - credentialProvider := cred_helper.CredentialsProvider(conf.CredHelperImpl, conf.StoredProcedureConfig, conf.Db, conf.CacheTTL, conf.CacheCleanupInterval, logger, policyDb) - defer func() { _ = credentialProvider.Close() }() + var credentialProvider cred_helper.Credentials + if conf.CredentialHelperConfig != nil { + credentialProvider = cred_helper.CredentialsProvider( + conf.CredentialHelperConfig, + conf.Db, conf.CacheTTL, conf.CacheCleanupInterval, logger, policyDb) + } + defer func() { + if credentialProvider != nil { + _ = credentialProvider.Close() + } + }() httpStatusCollector := healthendpoint.NewHTTPStatusCollector("autoscaler", "metricsforwarder") diff --git a/metricsforwarder/cmd/metricsforwarder/metricsforwarder_suite_test.go b/metricsforwarder/cmd/metricsforwarder/metricsforwarder_suite_test.go index 0ace101db..8a3890794 100644 --- a/metricsforwarder/cmd/metricsforwarder/metricsforwarder_suite_test.go +++ b/metricsforwarder/cmd/metricsforwarder/metricsforwarder_suite_test.go @@ -25,6 +25,7 @@ import ( "code.cloudfoundry.org/app-autoscaler/src/autoscaler/db" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/metricsforwarder/config" + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/models" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/metricsforwarder/testhelpers" ) @@ -116,7 +117,7 @@ var _ = SynchronizedBeforeSuite(func() []byte { ConnectionMaxLifetime: 10 * time.Second, } - cfg.CredHelperImpl = "default" + cfg.CredentialHelperConfig = models.BasicAuthHandlingNative{} configFile = writeConfig(&cfg) diff --git a/metricsforwarder/config/config.go b/metricsforwarder/config/config.go index 6e03ed97d..39a167153 100644 --- a/metricsforwarder/config/config.go +++ b/metricsforwarder/config/config.go @@ -4,7 +4,6 @@ import ( "errors" "time" - "code.cloudfoundry.org/app-autoscaler/src/autoscaler/configutil" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/db" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/helpers" "code.cloudfoundry.org/app-autoscaler/src/autoscaler/models" @@ -34,74 +33,20 @@ type SyslogConfig struct { } type Config struct { - Logging helpers.LoggingConfig `yaml:"logging"` - Server helpers.ServerConfig `yaml:"server"` - LoggregatorConfig LoggregatorConfig `yaml:"loggregator"` - SyslogConfig SyslogConfig `yaml:"syslog"` - Db map[string]db.DatabaseConfig `yaml:"db"` - CacheTTL time.Duration `yaml:"cache_ttl"` - CacheCleanupInterval time.Duration `yaml:"cache_cleanup_interval"` - PolicyPollerInterval time.Duration `yaml:"policy_poller_interval"` - Health helpers.HealthConfig `yaml:"health"` - RateLimit models.RateLimitConfig `yaml:"rate_limit"` - CredHelperImpl string `yaml:"cred_helper_impl"` - StoredProcedureConfig *models.StoredProcedureConfig `yaml:"stored_procedure_binding_credential_config"` -} - -func LoadConfig(filepath string, vcapReader configutil.VCAPConfigurationReader) (*Config, error) { - conf := defaultConfig() - - if err := helpers.LoadYamlFile(filepath, &conf); err != nil { - return nil, err - } - - if err := loadVcapConfig(&conf, vcapReader); err != nil { - return nil, err - } - - return &conf, nil -} - -func defaultConfig() Config { - return Config{ - Server: helpers.ServerConfig{Port: 6110}, - Logging: helpers.LoggingConfig{Level: "info"}, - LoggregatorConfig: LoggregatorConfig{ - MetronAddress: DefaultMetronAddress, - }, - Health: helpers.HealthConfig{ServerConfig: helpers.ServerConfig{Port: 8081}}, - CacheTTL: DefaultCacheTTL, - Db: make(map[string]db.DatabaseConfig), - CacheCleanupInterval: DefaultCacheCleanupInterval, - PolicyPollerInterval: DefaultPolicyPollerInterval, - RateLimit: models.RateLimitConfig{ - MaxAmount: DefaultMaxAmount, - ValidDuration: DefaultValidDuration, - }, - } -} - -func loadVcapConfig(conf *Config, vcapReader configutil.VCAPConfigurationReader) error { - if !vcapReader.IsRunningOnCF() { - return nil - } - - conf.Server.Port = vcapReader.GetPort() - if err := configutil.LoadConfig(&conf, vcapReader, "metricsforwarder-config"); err != nil { - return err - } - - if err := vcapReader.ConfigureDatabases(&conf.Db, conf.StoredProcedureConfig, conf.CredHelperImpl); err != nil { - return err - } - - tls, err := vcapReader.MaterializeTLSConfigFromService("syslog-client") - if err != nil { - return err - } - conf.SyslogConfig.TLS = tls - - return nil + Logging helpers.LoggingConfig + Server helpers.ServerConfig + LoggregatorConfig LoggregatorConfig + SyslogConfig SyslogConfig + Db map[string]db.DatabaseConfig + CacheTTL time.Duration + CacheCleanupInterval time.Duration + PolicyPollerInterval time.Duration + Health helpers.HealthConfig + RateLimit models.RateLimitConfig + + // CredentialHelperConfig configures how credentials for "Basic Authentication" are managed. + // nil means no credential helper is configured (Basic Auth disabled). + CredentialHelperConfig models.BasicAuthHandlingImplConfig } func (c *Config) Validate() error { @@ -175,8 +120,8 @@ func (c *Config) validateRateLimit() error { } func (c *Config) validateCredHelperImpl() error { - if c.CredHelperImpl == "" { - return errors.New("CredHelperImpl is empty") + if c.CredentialHelperConfig == nil { + return errors.New("CredentialHelperConfig is not configured") } return nil } diff --git a/metricsforwarder/config/config_test.go b/metricsforwarder/config/config_test.go index 317e80dbf..72a9daa09 100644 --- a/metricsforwarder/config/config_test.go +++ b/metricsforwarder/config/config_test.go @@ -82,7 +82,7 @@ var _ = Describe("Config", func() { When("handling available databases", func() { It("calls vcapReader ConfigureDatabases with the right arguments", func() { - testhelpers.ExpectConfigureDatabasesCalledOnce(err, mockVCAPConfigurationReader, conf.CredHelperImpl) + testhelpers.ExpectConfigureDatabasesCalledOnce(err, mockVCAPConfigurationReader) }) }) @@ -181,7 +181,8 @@ cred_helper_impl: default MaxIdleConnections: 5, ConnectionMaxLifetime: 60 * time.Second, })) - Expect(conf.CredHelperImpl).To(Equal("default")) + Expect(conf.CredentialHelperConfig). + To(BeAssignableToTypeOf(models.BasicAuthHandlingNative{})) }) }) @@ -244,7 +245,7 @@ health: conf.RateLimit.MaxAmount = 10 conf.RateLimit.ValidDuration = 1 * time.Second - conf.CredHelperImpl = "path/to/plugin" + conf.CredentialHelperConfig = models.BasicAuthHandlingNative{} }) JustBeforeEach(func() { diff --git a/metricsforwarder/config/parser.go b/metricsforwarder/config/parser.go new file mode 100644 index 000000000..88c554820 --- /dev/null +++ b/metricsforwarder/config/parser.go @@ -0,0 +1,100 @@ +package config + +import ( + "time" + + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/configutil" + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/db" + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/helpers" + "code.cloudfoundry.org/app-autoscaler/src/autoscaler/models" +) + +type rawConfig struct { + Logging helpers.LoggingConfig `yaml:"logging"` + Server helpers.ServerConfig `yaml:"server"` + LoggregatorConfig LoggregatorConfig `yaml:"loggregator"` + SyslogConfig SyslogConfig `yaml:"syslog"` + Db map[string]db.DatabaseConfig `yaml:"db"` + CacheTTL time.Duration `yaml:"cache_ttl"` + CacheCleanupInterval time.Duration `yaml:"cache_cleanup_interval"` + PolicyPollerInterval time.Duration `yaml:"policy_poller_interval"` + Health helpers.HealthConfig `yaml:"health"` + RateLimit models.RateLimitConfig `yaml:"rate_limit"` + CredHelperImpl string `yaml:"cred_helper_impl"` + StoredProcedureConfig *models.StoredProcedureConfig `yaml:"stored_procedure_binding_credential_config"` +} + +func toConfig(raw rawConfig) (Config, error) { + return Config{}, models.ErrUnimplemented +} + +func LoadConfig(filepath string, vcapReader configutil.VCAPConfigurationReader) (*Config, error) { + raw := defaultConfig() + + if err := helpers.LoadYamlFile(filepath, &raw); err != nil { + return nil, err + } + + if err := loadVcapConfig(&raw, vcapReader); err != nil { + return nil, err + } + + config, err := toConfig(raw) + if err != nil { + return nil, err + } + + return &config, nil +} + +func loadVcapConfig(conf *rawConfig, vcapReader configutil.VCAPConfigurationReader) error { + if !vcapReader.IsRunningOnCF() { + return nil + } + + conf.Server.Port = vcapReader.GetPort() + if err := configutil.LoadConfig(&conf, vcapReader, "metricsforwarder-config"); err != nil { + return err + } + + var basicAuthImplCfg *models.BasicAuthHandlingImplConfig + if conf.CredHelperImpl != "" { + var impl models.BasicAuthHandlingImplConfig + if conf.CredHelperImpl == "stored_procedure" && conf.StoredProcedureConfig != nil { + impl = models.BasicAuthHandlingStoredProc{Config: *conf.StoredProcedureConfig} + } else { + impl = models.BasicAuthHandlingNative{} + } + basicAuthImplCfg = &impl + } + if err := vcapReader.ConfigureDatabases(&conf.Db, basicAuthImplCfg); err != nil { + return err + } + + tls, err := vcapReader.MaterializeTLSConfigFromService("syslog-client") + if err != nil { + return err + } + conf.SyslogConfig.TLS = tls + + return nil +} + +func defaultConfig() rawConfig { + return rawConfig{ + Server: helpers.ServerConfig{Port: 6110}, + Logging: helpers.LoggingConfig{Level: "info"}, + LoggregatorConfig: LoggregatorConfig{ + MetronAddress: DefaultMetronAddress, + }, + Health: helpers.HealthConfig{ServerConfig: helpers.ServerConfig{Port: 8081}}, + CacheTTL: DefaultCacheTTL, + Db: make(map[string]db.DatabaseConfig), + CacheCleanupInterval: DefaultCacheCleanupInterval, + PolicyPollerInterval: DefaultPolicyPollerInterval, + RateLimit: models.RateLimitConfig{ + MaxAmount: DefaultMaxAmount, + ValidDuration: DefaultValidDuration, + }, + } +} diff --git a/models/basic_auth_config.go b/models/basic_auth_config.go new file mode 100644 index 000000000..7e05306da --- /dev/null +++ b/models/basic_auth_config.go @@ -0,0 +1,27 @@ +package models + +// BasicAuthHandlingImplConfig defines how "Basic Authentication" is implemented. +// This is a sealed interface with two implementations: +// - BasicAuthHandlingNative: native implementation by Application Autoscaler +// - BasicAuthHandlingStoredProc: custom implementation via a stored procedure +type BasicAuthHandlingImplConfig interface { + isBasicAuthHandlingImplConfig() +} + +// BasicAuthHandlingNative states that "Basic Authentication" is implemented natively +// by Application Autoscaler. +type BasicAuthHandlingNative struct{} + +func (b BasicAuthHandlingNative) isBasicAuthHandlingImplConfig() {} + +var _ BasicAuthHandlingImplConfig = BasicAuthHandlingNative{} + +// BasicAuthHandlingStoredProc configures a custom handling of "Basic Authentication" +// via a stored procedure. +type BasicAuthHandlingStoredProc struct { + Config StoredProcedureConfig +} + +func (b BasicAuthHandlingStoredProc) isBasicAuthHandlingImplConfig() {} + +var _ BasicAuthHandlingImplConfig = BasicAuthHandlingStoredProc{} diff --git a/operator/config/config.go b/operator/config/config.go index d1fa1f303..2b21e96aa 100644 --- a/operator/config/config.go +++ b/operator/config/config.go @@ -141,7 +141,7 @@ func LoadVcapConfig(conf *Config, vcapReader configutil.VCAPConfigurationReader) return err } - if err := vcapReader.ConfigureDatabases(&conf.Db, nil, ""); err != nil { + if err := vcapReader.ConfigureDatabases(&conf.Db, nil); err != nil { return err } diff --git a/operator/config/config_test.go b/operator/config/config_test.go index df33bd6b3..51df79d1b 100644 --- a/operator/config/config_test.go +++ b/operator/config/config_test.go @@ -69,7 +69,7 @@ var _ = Describe("Config", func() { When("handling available databases", func() { It("calls vcapReader ConfigureDatabases with the right arguments", func() { - testhelpers.ExpectConfigureDatabasesCalledOnce(err, mockVCAPConfigurationReader, "") + testhelpers.ExpectConfigureDatabasesCalledOnce(err, mockVCAPConfigurationReader) }) }) diff --git a/scalingengine/config/config_test.go b/scalingengine/config/config_test.go index b7ca848e9..a4b354e6f 100644 --- a/scalingengine/config/config_test.go +++ b/scalingengine/config/config_test.go @@ -65,7 +65,7 @@ var _ = Describe("Config", func() { When("handling available databases", func() { It("calls vcapReader ConfigureDatabases with the right arguments", func() { - testhelpers.ExpectConfigureDatabasesCalledOnce(err, mockVCAPConfigurationReader, "") + testhelpers.ExpectConfigureDatabasesCalledOnce(err, mockVCAPConfigurationReader) }) }) diff --git a/testhelpers/db.go b/testhelpers/db.go index ff95dc05f..fb3c7fa3e 100644 --- a/testhelpers/db.go +++ b/testhelpers/db.go @@ -17,12 +17,11 @@ func GetDbUrl() string { return dbUrl } -func ExpectConfigureDatabasesCalledOnce(err error, fakeVcapReader *fakes.FakeVCAPConfigurationReader, expectedCredHelperImpl string) { +func ExpectConfigureDatabasesCalledOnce(err error, fakeVcapReader *fakes.FakeVCAPConfigurationReader) { Expect(err).NotTo(HaveOccurred()) Expect(fakeVcapReader.ConfigureDatabasesCallCount()).To(Equal(1)) - receivedDbConfig, receivedStoredProcedureConfig, receivedCredHelperImpl := + receivedDbConfig, receivedBasicAuthImplCfg := fakeVcapReader.ConfigureDatabasesArgsForCall(0) Expect(*receivedDbConfig).To(Equal(map[string]db.DatabaseConfig{})) - Expect(receivedStoredProcedureConfig).To(BeNil()) - Expect(receivedCredHelperImpl).To(Equal(expectedCredHelperImpl)) + Expect(receivedBasicAuthImplCfg).To(BeNil()) }