From 8443e1aaee2250692e0f57e41947d3f6189dbc95 Mon Sep 17 00:00:00 2001 From: Nick Benoit Date: Fri, 24 Oct 2025 18:40:20 -0600 Subject: [PATCH 1/4] Apply Kibana config defaults respecting mutually exclusive auth values Instead of using the defaults as a starting point we apply them after building the config object to any fields that are not set. In doing so we respect fields that are mutually exclusive like api_key and username. Applying defaults at the end makes it easier to check we aren't accidentally building an invalid config. --- internal/clients/config/kibana.go | 39 ++- internal/clients/config/kibana_test.go | 353 +++++++++++++++++++++++++ 2 files changed, 387 insertions(+), 5 deletions(-) diff --git a/internal/clients/config/kibana.go b/internal/clients/config/kibana.go index e7a8529f7..ee8824a36 100644 --- a/internal/clients/config/kibana.go +++ b/internal/clients/config/kibana.go @@ -18,10 +18,11 @@ func newKibanaConfigFromSDK(d *schema.ResourceData, base baseConfig) (kibanaConf var diags sdkdiags.Diagnostics // Use ES details by default - config := base.toKibanaConfig() + config := kibanaConfig{} + kibConn, ok := d.GetOk("kibana") if !ok { - return config, diags + return config.withDefaultsApplied(base.toKibanaConfig()), diags } // if defined, then we only have a single entry @@ -59,11 +60,11 @@ func newKibanaConfigFromSDK(d *schema.ResourceData, base baseConfig) (kibanaConf } } - return config.withEnvironmentOverrides(), nil + return config.withEnvironmentOverrides().withDefaultsApplied(base.toKibanaConfig()), nil } func newKibanaConfigFromFramework(ctx context.Context, cfg ProviderConfiguration, base baseConfig) (kibanaConfig, fwdiags.Diagnostics) { - config := base.toKibanaConfig() + config := kibanaConfig{} if len(cfg.Kibana) > 0 { kibConfig := cfg.Kibana[0] @@ -95,8 +96,36 @@ func newKibanaConfigFromFramework(ctx context.Context, cfg ProviderConfiguration config.DisableVerifySSL = kibConfig.Insecure.ValueBool() } + return config.withEnvironmentOverrides().withDefaultsApplied(base.toKibanaConfig()), nil +} + +func (config kibanaConfig) withDefaultsApplied(defaults kibanaConfig) kibanaConfig { + + // Apply defaults for non-auth fields + if config.Address == "" { + config.Address = defaults.Address + } + if config.DisableVerifySSL == false { + config.DisableVerifySSL = defaults.DisableVerifySSL + } + if config.CAs == nil { + config.CAs = defaults.CAs + } + + // Handle auth defaults. ApiKey and Username are mutually exclusive. If one is already set don't apply any auth defaults + if config.ApiKey != "" || config.Username != "" { + return config + } + + if defaults.ApiKey != "" { + config.ApiKey = defaults.ApiKey + } else if defaults.Username != "" { + config.Username = defaults.Username + config.Password = defaults.Password + } + + return config - return config.withEnvironmentOverrides(), nil } func (k kibanaConfig) withEnvironmentOverrides() kibanaConfig { diff --git a/internal/clients/config/kibana_test.go b/internal/clients/config/kibana_test.go index c5dc22352..5e4a92bab 100644 --- a/internal/clients/config/kibana_test.go +++ b/internal/clients/config/kibana_test.go @@ -110,6 +110,173 @@ func Test_newKibanaConfigFromSDK(t *testing.T) { } }, }, + { + name: "should fallback to elasticsearch username/password when no kibana credentials provided", + args: func() args { + baseCfg := baseConfig{ + Username: "es-user", + Password: "es-password", + } + + return args{ + baseCfg: baseCfg, + resourceData: map[string]interface{}{ + "kibana": []interface{}{ + map[string]interface{}{ + "endpoints": []interface{}{"example.com/kibana"}, + // No username/password provided in kibana config + "ca_certs": []interface{}{"internal"}, + "insecure": false, + }, + }, + }, + expectedConfig: kibanaConfig{ + Address: "example.com/kibana", + Username: "es-user", // Falls back to ES credentials + Password: "es-password", // Falls back to ES credentials + CAs: []string{"internal"}, + DisableVerifySSL: false, + }, + } + }, + }, + { + name: "should fallback to elasticsearch api_key when no kibana credentials provided", + args: func() args { + baseCfg := baseConfig{ + ApiKey: "es-api-key-123", + } + + return args{ + baseCfg: baseCfg, + resourceData: map[string]interface{}{ + "kibana": []interface{}{ + map[string]interface{}{ + "endpoints": []interface{}{"example.com/kibana"}, + // No api_key provided in kibana config + "insecure": true, + }, + }, + }, + expectedConfig: kibanaConfig{ + Address: "example.com/kibana", + ApiKey: "es-api-key-123", // Falls back to ES api_key + DisableVerifySSL: true, + }, + } + }, + }, + { + name: "should not override kibana credentials when they are explicitly provided", + args: func() args { + baseCfg := baseConfig{ + Username: "es-user", + Password: "es-password", + } + + return args{ + baseCfg: baseCfg, + resourceData: map[string]interface{}{ + "kibana": []interface{}{ + map[string]interface{}{ + "endpoints": []interface{}{"example.com/kibana"}, + "username": "kibana-user", + "password": "kibana-password", + "insecure": false, + }, + }, + }, + expectedConfig: kibanaConfig{ + Address: "example.com/kibana", + Username: "kibana-user", // Uses kibana-specific credentials + Password: "kibana-password", // Uses kibana-specific credentials + DisableVerifySSL: false, + }, + } + }, + }, + { + name: "should not override kibana api_key when explicitly provided", + args: func() args { + baseCfg := baseConfig{ + ApiKey: "es-api-key-123", + } + + return args{ + baseCfg: baseCfg, + resourceData: map[string]interface{}{ + "kibana": []interface{}{ + map[string]interface{}{ + "endpoints": []interface{}{"example.com/kibana"}, + "api_key": "es-api-key-456", + "insecure": false, + }, + }, + }, + expectedConfig: kibanaConfig{ + Address: "example.com/kibana", + ApiKey: "es-api-key-456", // Uses kibana-specific api_key + DisableVerifySSL: false, + }, + } + }, + }, + { + name: "should not add elasticsearch api key when kibana credentials are explicitly provided", + args: func() args { + baseCfg := baseConfig{ + ApiKey: "es-api-key-123", + } + + return args{ + baseCfg: baseCfg, + resourceData: map[string]interface{}{ + "kibana": []interface{}{ + map[string]interface{}{ + "endpoints": []interface{}{"example.com/kibana"}, + "username": "kibana-user", + "password": "kibana-password", + "insecure": false, + }, + }, + }, + env: map[string]string{}, + expectedConfig: kibanaConfig{ + Address: "example.com/kibana", + Username: "kibana-user", // Uses kibana-specific credentials + Password: "kibana-password", // Uses kibana-specific credentials + DisableVerifySSL: false, + }, + } + }, + }, + { + name: "should not add elasticsearch credentials when kibana api key are explicitly provided", + args: func() args { + baseCfg := baseConfig{ + Username: "es-user", + Password: "es-password", + } + + return args{ + baseCfg: baseCfg, + resourceData: map[string]interface{}{ + "kibana": []interface{}{ + map[string]interface{}{ + "endpoints": []interface{}{"example.com/kibana"}, + "insecure": false, + "api_key": "es-api-key-123", + }, + }, + }, + expectedConfig: kibanaConfig{ + Address: "example.com/kibana", + ApiKey: "es-api-key-123", + DisableVerifySSL: false, + }, + } + }, + }, } for _, tt := range tests { @@ -273,6 +440,192 @@ func Test_newKibanaConfigFromFramework(t *testing.T) { } }, }, + { + name: "should fallback to elasticsearch username/password when no kibana credentials provided", + args: func() args { + baseCfg := baseConfig{ + Username: "es-user", + Password: "es-password", + } + + return args{ + baseCfg: baseCfg, + providerConfig: ProviderConfiguration{ + Kibana: []KibanaConnection{ + { + Endpoints: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("example.com/kibana"), + }), + // No username/password provided in kibana config + CACerts: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("internal"), + }), + Insecure: types.BoolValue(false), + }, + }, + }, + expectedConfig: kibanaConfig{ + Address: "example.com/kibana", + Username: "es-user", // Falls back to ES credentials + Password: "es-password", // Falls back to ES credentials + CAs: []string{"internal"}, + DisableVerifySSL: false, + }, + } + }, + }, + { + name: "should fallback to elasticsearch api_key when no kibana credentials provided", + args: func() args { + baseCfg := baseConfig{ + ApiKey: "es-api-key-123", + } + + return args{ + baseCfg: baseCfg, + providerConfig: ProviderConfiguration{ + Kibana: []KibanaConnection{ + { + Endpoints: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("example.com/kibana"), + }), + // No api_key provided in kibana config + CACerts: types.ListValueMust(types.StringType, []attr.Value{}), + Insecure: types.BoolValue(true), + }, + }, + }, + expectedConfig: kibanaConfig{ + Address: "example.com/kibana", + ApiKey: "es-api-key-123", // Falls back to ES api_key + DisableVerifySSL: true, + }, + } + }, + }, + { + name: "should not override kibana credentials when they are explicitly provided", + args: func() args { + baseCfg := baseConfig{ + Username: "es-user", + Password: "es-password", + } + + return args{ + baseCfg: baseCfg, + providerConfig: ProviderConfiguration{ + Kibana: []KibanaConnection{ + { + Username: types.StringValue("kibana-user"), + Password: types.StringValue("kibana-password"), + Endpoints: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("example.com/kibana"), + }), + CACerts: types.ListValueMust(types.StringType, []attr.Value{}), + Insecure: types.BoolValue(false), + }, + }, + }, + expectedConfig: kibanaConfig{ + Address: "example.com/kibana", + Username: "kibana-user", // Uses kibana-specific credentials + Password: "kibana-password", // Uses kibana-specific credentials + DisableVerifySSL: false, + }, + } + }, + }, + { + name: "should not override kibana api_key when explicitly provided", + args: func() args { + baseCfg := baseConfig{ + ApiKey: "es-api-key-123", + } + + return args{ + baseCfg: baseCfg, + providerConfig: ProviderConfiguration{ + Kibana: []KibanaConnection{ + { + ApiKey: types.StringValue("kibana-api-key-456"), + Endpoints: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("example.com/kibana"), + }), + CACerts: types.ListValueMust(types.StringType, []attr.Value{}), + Insecure: types.BoolValue(false), + }, + }, + }, + expectedConfig: kibanaConfig{ + Address: "example.com/kibana", + ApiKey: "kibana-api-key-456", // Uses kibana-specific api_key + DisableVerifySSL: false, + }, + } + }, + }, + { + name: "should not add elasticsearch api key when kibana credentials are explicitly provided", + args: func() args { + baseCfg := baseConfig{ + ApiKey: "es-api-key-123", + } + + return args{ + baseCfg: baseCfg, + providerConfig: ProviderConfiguration{ + Kibana: []KibanaConnection{ + { + Username: types.StringValue("kibana-user"), + Password: types.StringValue("kibana-password"), + Endpoints: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("example.com/kibana"), + }), + CACerts: types.ListValueMust(types.StringType, []attr.Value{}), + Insecure: types.BoolValue(false), + }, + }, + }, + env: map[string]string{}, + expectedConfig: kibanaConfig{ + Address: "example.com/kibana", + Username: "kibana-user", // Uses kibana-specific credentials + Password: "kibana-password", // Uses kibana-specific credentials + DisableVerifySSL: false, + }, + } + }, + }, + { + name: "should not add elasticsearch credentials when kibana api key are explicitly provided", + args: func() args { + baseCfg := baseConfig{ + Username: "es-user", + Password: "es-password", + } + + return args{ + baseCfg: baseCfg, + providerConfig: ProviderConfiguration{ + Kibana: []KibanaConnection{ + { + ApiKey: types.StringValue("kibana-api-key-456"), + Endpoints: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("example.com/kibana"), + }), + CACerts: types.ListValueMust(types.StringType, []attr.Value{}), + Insecure: types.BoolValue(false), + }, + }, + }, + expectedConfig: kibanaConfig{ + Address: "example.com/kibana", + ApiKey: "kibana-api-key-456", + DisableVerifySSL: false, + }, + } + }, + }, } for _, tt := range tests { From 5f5cf1f13460075d314a983a89b618ae51dc37fd Mon Sep 17 00:00:00 2001 From: Nick Benoit Date: Fri, 24 Oct 2025 19:38:55 -0600 Subject: [PATCH 2/4] Add KnownKeys to track explicitly set fields --- internal/clients/config/kibana.go | 45 +++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/internal/clients/config/kibana.go b/internal/clients/config/kibana.go index ee8824a36..10234fdf9 100644 --- a/internal/clients/config/kibana.go +++ b/internal/clients/config/kibana.go @@ -14,15 +14,31 @@ import ( type kibanaConfig kibana.Config +// Structure to keep track of which keys were explicitly set in the config. +// This allows us to determine the difference between explicitly set empty +// values and values that were not set at all. Building this intermediate +// representation allows for compability with plugin framework and sdkv2. +type kibanaConfigKeys struct { + Address bool + Username bool + Password bool + ApiKey bool + DisableVerifySSL bool + CAs bool +} + func newKibanaConfigFromSDK(d *schema.ResourceData, base baseConfig) (kibanaConfig, sdkdiags.Diagnostics) { var diags sdkdiags.Diagnostics // Use ES details by default config := kibanaConfig{} + // Keep track of keys that are explicitly set in the config. + knownKeys := kibanaConfigKeys{} + kibConn, ok := d.GetOk("kibana") if !ok { - return config.withDefaultsApplied(base.toKibanaConfig()), diags + return config.withDefaultsApplied(base.toKibanaConfig(), knownKeys), diags } // if defined, then we only have a single entry @@ -31,19 +47,23 @@ func newKibanaConfigFromSDK(d *schema.ResourceData, base baseConfig) (kibanaConf if username, ok := kibConfig["username"]; ok && username != "" { config.Username = username.(string) + knownKeys.Username = true } if password, ok := kibConfig["password"]; ok && password != "" { config.Password = password.(string) + knownKeys.Password = true } if apiKey, ok := kibConfig["api_key"]; ok && apiKey != "" { config.ApiKey = apiKey.(string) + knownKeys.ApiKey = true } if endpoints, ok := kibConfig["endpoints"]; ok && len(endpoints.([]interface{})) > 0 { // We're curently limited by the API to a single endpoint if endpoint := endpoints.([]interface{})[0]; endpoint != nil { config.Address = endpoint.(string) + knownKeys.Address = true } } @@ -53,29 +73,35 @@ func newKibanaConfigFromSDK(d *schema.ResourceData, base baseConfig) (kibanaConf config.CAs = append(config.CAs, vStr) } } + knownKeys.CAs = true } if insecure, ok := kibConfig["insecure"]; ok && insecure.(bool) { config.DisableVerifySSL = true + knownKeys.DisableVerifySSL = true } } - return config.withEnvironmentOverrides().withDefaultsApplied(base.toKibanaConfig()), nil + return config.withEnvironmentOverrides().withDefaultsApplied(base.toKibanaConfig(), knownKeys), nil } func newKibanaConfigFromFramework(ctx context.Context, cfg ProviderConfiguration, base baseConfig) (kibanaConfig, fwdiags.Diagnostics) { config := kibanaConfig{} + knownKeys := kibanaConfigKeys{} if len(cfg.Kibana) > 0 { kibConfig := cfg.Kibana[0] if kibConfig.Username.ValueString() != "" { config.Username = kibConfig.Username.ValueString() + knownKeys.Username = true } if kibConfig.Password.ValueString() != "" { config.Password = kibConfig.Password.ValueString() + knownKeys.Password = true } if kibConfig.ApiKey.ValueString() != "" { config.ApiKey = kibConfig.ApiKey.ValueString() + knownKeys.ApiKey = true } var endpoints []string diags := kibConfig.Endpoints.ElementsAs(ctx, &endpoints, true) @@ -88,32 +114,35 @@ func newKibanaConfigFromFramework(ctx context.Context, cfg ProviderConfiguration if len(endpoints) > 0 { config.Address = endpoints[0] + knownKeys.Address = true } if len(cas) > 0 { config.CAs = cas + knownKeys.CAs = true } config.DisableVerifySSL = kibConfig.Insecure.ValueBool() + knownKeys.DisableVerifySSL = true } - return config.withEnvironmentOverrides().withDefaultsApplied(base.toKibanaConfig()), nil + return config.withEnvironmentOverrides().withDefaultsApplied(base.toKibanaConfig(), knownKeys), nil } -func (config kibanaConfig) withDefaultsApplied(defaults kibanaConfig) kibanaConfig { +func (config kibanaConfig) withDefaultsApplied(defaults kibanaConfig, knownKeys kibanaConfigKeys) kibanaConfig { // Apply defaults for non-auth fields - if config.Address == "" { + if !knownKeys.Address { config.Address = defaults.Address } - if config.DisableVerifySSL == false { + if !knownKeys.DisableVerifySSL { config.DisableVerifySSL = defaults.DisableVerifySSL } - if config.CAs == nil { + if !knownKeys.CAs { config.CAs = defaults.CAs } // Handle auth defaults. ApiKey and Username are mutually exclusive. If one is already set don't apply any auth defaults - if config.ApiKey != "" || config.Username != "" { + if knownKeys.ApiKey || knownKeys.Username { return config } From c8531e931da75ede0f3b0e72f97a66e7e40cfbcf Mon Sep 17 00:00:00 2001 From: Nick Benoit Date: Fri, 24 Oct 2025 19:41:18 -0600 Subject: [PATCH 3/4] Apply similar pattern to Fleet --- internal/clients/config/fleet.go | 68 ++++- internal/clients/config/fleet_test.go | 365 ++++++++++++++++++++++++++ 2 files changed, 429 insertions(+), 4 deletions(-) diff --git a/internal/clients/config/fleet.go b/internal/clients/config/fleet.go index 893642387..21ae03901 100644 --- a/internal/clients/config/fleet.go +++ b/internal/clients/config/fleet.go @@ -13,8 +13,24 @@ import ( type fleetConfig fleet.Config +// Structure to keep track of which keys were explicitly set in the config. +// This allows us to determine the difference between explicitly set empty +// values and values that were not set at all. Building this intermediate +// representation allows for compability with plugin framework and sdkv2. +type fleetConfigKeys struct { + URL bool + Username bool + Password bool + APIKey bool + Insecure bool + CACerts bool +} + func newFleetConfigFromSDK(d *schema.ResourceData, kibanaCfg kibanaOapiConfig) (fleetConfig, sdkdiags.Diagnostics) { - config := kibanaCfg.toFleetConfig() + config := fleetConfig{} + + // Keep track of keys that are explicitly set in the config. + knownKeys := fleetConfigKeys{} // Set variables from resource config. if fleetDataRaw, ok := d.GetOk("fleet"); ok { @@ -31,15 +47,19 @@ func newFleetConfigFromSDK(d *schema.ResourceData, kibanaCfg kibanaOapiConfig) ( } if v, ok := fleetData["endpoint"].(string); ok && v != "" { config.URL = v + knownKeys.URL = true } if v, ok := fleetData["username"].(string); ok && v != "" { config.Username = v + knownKeys.Username = true } if v, ok := fleetData["password"].(string); ok && v != "" { config.Password = v + knownKeys.Password = true } if v, ok := fleetData["api_key"].(string); ok && v != "" { config.APIKey = v + knownKeys.APIKey = true } if v, ok := fleetData["ca_certs"].([]interface{}); ok && len(v) > 0 { for _, elem := range v { @@ -47,35 +67,44 @@ func newFleetConfigFromSDK(d *schema.ResourceData, kibanaCfg kibanaOapiConfig) ( config.CACerts = append(config.CACerts, vStr) } } + knownKeys.CACerts = true } if v, ok := fleetData["insecure"].(bool); ok { config.Insecure = v + knownKeys.Insecure = true } } - return config.withEnvironmentOverrides(), nil + return config.withEnvironmentOverrides().withDefaultsApplied(kibanaCfg.toFleetConfig(), knownKeys), nil } func newFleetConfigFromFramework(ctx context.Context, cfg ProviderConfiguration, kibanaCfg kibanaOapiConfig) (fleetConfig, fwdiags.Diagnostics) { - config := kibanaCfg.toFleetConfig() + config := fleetConfig{} + // Keep track of keys that are explicitly set in the config + knownKeys := fleetConfigKeys{} if len(cfg.Fleet) > 0 { fleetCfg := cfg.Fleet[0] if fleetCfg.Username.ValueString() != "" { config.Username = fleetCfg.Username.ValueString() + knownKeys.Username = true } if fleetCfg.Password.ValueString() != "" { config.Password = fleetCfg.Password.ValueString() + knownKeys.Password = true } if fleetCfg.Endpoint.ValueString() != "" { config.URL = fleetCfg.Endpoint.ValueString() + knownKeys.URL = true } if fleetCfg.APIKey.ValueString() != "" { config.APIKey = fleetCfg.APIKey.ValueString() + knownKeys.APIKey = true } if !fleetCfg.Insecure.IsNull() && !fleetCfg.Insecure.IsUnknown() { config.Insecure = fleetCfg.Insecure.ValueBool() + knownKeys.Insecure = true } var caCerts []string @@ -86,10 +115,41 @@ func newFleetConfigFromFramework(ctx context.Context, cfg ProviderConfiguration, if len(caCerts) > 0 { config.CACerts = caCerts + knownKeys.CACerts = true } } - return config.withEnvironmentOverrides(), nil + return config.withEnvironmentOverrides().withDefaultsApplied(kibanaCfg.toFleetConfig(), knownKeys), nil +} + +func (config fleetConfig) withDefaultsApplied(defaults fleetConfig, knownKeys fleetConfigKeys) fleetConfig { + + // Apply defaults for non-auth fields + if !knownKeys.URL { + config.URL = defaults.URL + } + if !knownKeys.Insecure { + config.Insecure = defaults.Insecure + } + + if !knownKeys.CACerts { + config.CACerts = defaults.CACerts + } + + // Handle auth defaults. APIKey and Username are mutually exclusive. If one is already set don't apply any auth defaults + if knownKeys.APIKey || knownKeys.Username { + return config + } + + // Only apply a single provided auth default + if defaults.APIKey != "" { + config.APIKey = defaults.APIKey + } else if defaults.Username != "" { + config.Username = defaults.Username + config.Password = defaults.Password + } + + return config } func (c fleetConfig) withEnvironmentOverrides() fleetConfig { diff --git a/internal/clients/config/fleet_test.go b/internal/clients/config/fleet_test.go index 021b746fd..63ea7af1f 100644 --- a/internal/clients/config/fleet_test.go +++ b/internal/clients/config/fleet_test.go @@ -120,6 +120,185 @@ func Test_newFleetConfigFromSDK(t *testing.T) { } }, }, + { + name: "should fallback to kibana username/password when no fleet credentials provided", + args: func() args { + kibanaCfg := kibanaOapiConfig{ + URL: "example.com/kibana", + Username: "kibana-user", + Password: "kibana-password", + Insecure: false, + } + + return args{ + kibanaCfg: kibanaCfg, + resourceData: map[string]interface{}{ + "fleet": []interface{}{ + map[string]interface{}{ + "endpoint": "example.com/fleet", + // No username/password provided in fleet config + "ca_certs": []interface{}{"internal"}, + "insecure": false, + }, + }, + }, + expectedConfig: fleetConfig{ + URL: "example.com/fleet", + Username: "kibana-user", // Falls back to kibana credentials + Password: "kibana-password", // Falls back to kibana credentials + CACerts: []string{"internal"}, + Insecure: false, + }, + } + }, + }, + { + name: "should fallback to kibana api_key when no fleet credentials provided", + args: func() args { + kibanaCfg := kibanaOapiConfig{ + URL: "example.com/kibana", + APIKey: "kibana-api-key-123", + Insecure: true, + } + + return args{ + kibanaCfg: kibanaCfg, + resourceData: map[string]interface{}{ + "fleet": []interface{}{ + map[string]interface{}{ + "endpoint": "example.com/fleet", + // No api_key provided in fleet config + "insecure": true, + }, + }, + }, + expectedConfig: fleetConfig{ + URL: "example.com/fleet", + APIKey: "kibana-api-key-123", // Falls back to kibana api_key + Insecure: true, + }, + } + }, + }, + { + name: "should not override fleet credentials when they are explicitly provided", + args: func() args { + kibanaCfg := kibanaOapiConfig{ + URL: "example.com/kibana", + Username: "kibana-user", + Password: "kibana-password", + Insecure: false, + } + + return args{ + kibanaCfg: kibanaCfg, + resourceData: map[string]interface{}{ + "fleet": []interface{}{ + map[string]interface{}{ + "endpoint": "example.com/fleet", + "username": "fleet-user", + "password": "fleet-password", + "insecure": false, + }, + }, + }, + expectedConfig: fleetConfig{ + URL: "example.com/fleet", + Username: "fleet-user", // Uses fleet-specific credentials + Password: "fleet-password", // Uses fleet-specific credentials + Insecure: false, + }, + } + }, + }, + { + name: "should not override fleet api_key when explicitly provided", + args: func() args { + kibanaCfg := kibanaOapiConfig{ + URL: "example.com/kibana", + APIKey: "kibana-api-key-123", + Insecure: false, + } + + return args{ + kibanaCfg: kibanaCfg, + resourceData: map[string]interface{}{ + "fleet": []interface{}{ + map[string]interface{}{ + "endpoint": "example.com/fleet", + "api_key": "fleet-api-key-456", + "insecure": false, + }, + }, + }, + expectedConfig: fleetConfig{ + URL: "example.com/fleet", + APIKey: "fleet-api-key-456", // Uses fleet-specific api_key + Insecure: false, + }, + } + }, + }, + { + name: "should not add kibana api key when fleet credentials are explicitly provided", + args: func() args { + kibanaCfg := kibanaOapiConfig{ + URL: "example.com/kibana", + APIKey: "kibana-api-key-123", + Insecure: false, + } + + return args{ + kibanaCfg: kibanaCfg, + resourceData: map[string]interface{}{ + "fleet": []interface{}{ + map[string]interface{}{ + "endpoint": "example.com/fleet", + "username": "fleet-user", + "password": "fleet-password", + "insecure": false, + }, + }, + }, + env: map[string]string{}, + expectedConfig: fleetConfig{ + URL: "example.com/fleet", + Username: "fleet-user", // Uses fleet-specific credentials + Password: "fleet-password", // Uses fleet-specific credentials + Insecure: false, + }, + } + }, + }, + { + name: "should not add kibana credentials when fleet api key is explicitly provided", + args: func() args { + kibanaCfg := kibanaOapiConfig{ + URL: "example.com/kibana", + Username: "kibana-user", + Password: "kibana-password", + Insecure: false, + } + + return args{ + kibanaCfg: kibanaCfg, + resourceData: map[string]interface{}{ + "fleet": []interface{}{ + map[string]interface{}{ + "endpoint": "example.com/fleet", + "insecure": false, + "api_key": "fleet-api-key-123", + }, + }, + }, + expectedConfig: fleetConfig{ + URL: "example.com/fleet", + APIKey: "fleet-api-key-123", + Insecure: false, + }, + } + }, + }, } for _, tt := range tests { @@ -259,6 +438,192 @@ func Test_newFleetConfigFromFramework(t *testing.T) { } }, }, + { + name: "should fallback to kibana username/password when no fleet credentials provided", + args: func() args { + kibanaCfg := kibanaOapiConfig{ + URL: "example.com/kibana", + Username: "kibana-user", + Password: "kibana-password", + Insecure: false, + } + + return args{ + kibanaCfg: kibanaCfg, + providerConfig: ProviderConfiguration{ + Fleet: []FleetConnection{ + { + Endpoint: types.StringValue("example.com/fleet"), + // No username/password provided in fleet config + CACerts: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("internal"), + }), + Insecure: types.BoolValue(false), + }, + }, + }, + expectedConfig: fleetConfig{ + URL: "example.com/fleet", + Username: "kibana-user", // Falls back to kibana credentials + Password: "kibana-password", // Falls back to kibana credentials + CACerts: []string{"internal"}, + Insecure: false, + }, + } + }, + }, + { + name: "should fallback to kibana api_key when no fleet credentials provided", + args: func() args { + kibanaCfg := kibanaOapiConfig{ + URL: "example.com/kibana", + APIKey: "kibana-api-key-123", + Insecure: true, + } + + return args{ + kibanaCfg: kibanaCfg, + providerConfig: ProviderConfiguration{ + Fleet: []FleetConnection{ + { + Endpoint: types.StringValue("example.com/fleet"), + // No api_key provided in fleet config + CACerts: types.ListValueMust(types.StringType, []attr.Value{}), + Insecure: types.BoolValue(true), + }, + }, + }, + expectedConfig: fleetConfig{ + URL: "example.com/fleet", + APIKey: "kibana-api-key-123", // Falls back to kibana api_key + Insecure: true, + }, + } + }, + }, + { + name: "should not override fleet credentials when they are explicitly provided", + args: func() args { + kibanaCfg := kibanaOapiConfig{ + URL: "example.com/kibana", + Username: "kibana-user", + Password: "kibana-password", + Insecure: false, + } + + return args{ + kibanaCfg: kibanaCfg, + providerConfig: ProviderConfiguration{ + Fleet: []FleetConnection{ + { + Username: types.StringValue("fleet-user"), + Password: types.StringValue("fleet-password"), + Endpoint: types.StringValue("example.com/fleet"), + CACerts: types.ListValueMust(types.StringType, []attr.Value{}), + Insecure: types.BoolValue(false), + }, + }, + }, + expectedConfig: fleetConfig{ + URL: "example.com/fleet", + Username: "fleet-user", // Uses fleet-specific credentials + Password: "fleet-password", // Uses fleet-specific credentials + Insecure: false, + }, + } + }, + }, + { + name: "should not override fleet api_key when explicitly provided", + args: func() args { + kibanaCfg := kibanaOapiConfig{ + URL: "example.com/kibana", + APIKey: "kibana-api-key-123", + Insecure: false, + } + + return args{ + kibanaCfg: kibanaCfg, + providerConfig: ProviderConfiguration{ + Fleet: []FleetConnection{ + { + APIKey: types.StringValue("fleet-api-key-456"), + Endpoint: types.StringValue("example.com/fleet"), + CACerts: types.ListValueMust(types.StringType, []attr.Value{}), + Insecure: types.BoolValue(false), + }, + }, + }, + expectedConfig: fleetConfig{ + URL: "example.com/fleet", + APIKey: "fleet-api-key-456", // Uses fleet-specific api_key + Insecure: false, + }, + } + }, + }, + { + name: "should not add kibana api key when fleet credentials are explicitly provided", + args: func() args { + kibanaCfg := kibanaOapiConfig{ + URL: "example.com/kibana", + APIKey: "kibana-api-key-123", + Insecure: false, + } + + return args{ + kibanaCfg: kibanaCfg, + providerConfig: ProviderConfiguration{ + Fleet: []FleetConnection{ + { + Username: types.StringValue("fleet-user"), + Password: types.StringValue("fleet-password"), + Endpoint: types.StringValue("example.com/fleet"), + CACerts: types.ListValueMust(types.StringType, []attr.Value{}), + Insecure: types.BoolValue(false), + }, + }, + }, + env: map[string]string{}, + expectedConfig: fleetConfig{ + URL: "example.com/fleet", + Username: "fleet-user", // Uses fleet-specific credentials + Password: "fleet-password", // Uses fleet-specific credentials + Insecure: false, + }, + } + }, + }, + { + name: "should not add kibana credentials when fleet api key is explicitly provided", + args: func() args { + kibanaCfg := kibanaOapiConfig{ + URL: "example.com/kibana", + Username: "kibana-user", + Password: "kibana-password", + Insecure: false, + } + + return args{ + kibanaCfg: kibanaCfg, + providerConfig: ProviderConfiguration{ + Fleet: []FleetConnection{ + { + APIKey: types.StringValue("fleet-api-key-456"), + Endpoint: types.StringValue("example.com/fleet"), + CACerts: types.ListValueMust(types.StringType, []attr.Value{}), + Insecure: types.BoolValue(false), + }, + }, + }, + expectedConfig: fleetConfig{ + URL: "example.com/fleet", + APIKey: "fleet-api-key-456", + Insecure: false, + }, + } + }, + }, } for _, tt := range tests { From 73771c32733d9dcf3f69a046a46f6a7e13d72ad0 Mon Sep 17 00:00:00 2001 From: Nick Benoit Date: Fri, 24 Oct 2025 19:47:55 -0600 Subject: [PATCH 4/4] Fix spelling --- internal/clients/config/fleet.go | 2 +- internal/clients/config/kibana.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/clients/config/fleet.go b/internal/clients/config/fleet.go index 21ae03901..a3db97a05 100644 --- a/internal/clients/config/fleet.go +++ b/internal/clients/config/fleet.go @@ -16,7 +16,7 @@ type fleetConfig fleet.Config // Structure to keep track of which keys were explicitly set in the config. // This allows us to determine the difference between explicitly set empty // values and values that were not set at all. Building this intermediate -// representation allows for compability with plugin framework and sdkv2. +// representation allows for compatibility with plugin framework and sdkv2. type fleetConfigKeys struct { URL bool Username bool diff --git a/internal/clients/config/kibana.go b/internal/clients/config/kibana.go index 10234fdf9..30a9e5c5e 100644 --- a/internal/clients/config/kibana.go +++ b/internal/clients/config/kibana.go @@ -17,7 +17,7 @@ type kibanaConfig kibana.Config // Structure to keep track of which keys were explicitly set in the config. // This allows us to determine the difference between explicitly set empty // values and values that were not set at all. Building this intermediate -// representation allows for compability with plugin framework and sdkv2. +// representation allows for compatibility with plugin framework and sdkv2. type kibanaConfigKeys struct { Address bool Username bool