diff --git a/ChangeLog b/ChangeLog index a204d9b..db5c635 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,7 @@ * Update password symbol for user creation #262 * Add support for td-client-ruby 2.x.x #267 +* Add SSL certificate options (--insecure, --ssl-ca-file) for proxy environments == 2023-03-18 version 0.17.1 diff --git a/README.rdoc b/README.rdoc index 72249e1..25b193e 100644 --- a/README.rdoc +++ b/README.rdoc @@ -144,10 +144,32 @@ These are the available hooks: $ TD_TOOLBELT_UPDATE_ROOT="http://toolbelt.treasuredata.com" -* Specify an alternative endpoint to use updating the JAR file (default: https://repo1.maven.org): +=== SSL Options - $ TD_TOOLBELT_JARUPDATE_ROOT="https://repo1.maven.org" +The CLI supports SSL certificate verification options to help with proxy environments: +* Disable SSL certificate verification: + + $ td --insecure ... + +* Use a custom CA certificate file: + + $ td --ssl-ca-file /path/to/ca.crt ... + +* Configure in ~/.td/td.conf: + + [ssl] + verify = false + ca_file = /path/to/ca.crt + +* Set environment variables: + + $ TD_SSL_VERIFY=false td ... + $ TD_SSL_CA_FILE=/path/to/ca.crt td ... + +The priority order is: CLI options > environment variables > config file > default. + +Note: These options do not apply to the 'td workflow' command, which uses its own HTTP client. = Copyright diff --git a/lib/td/command/common.rb b/lib/td/command/common.rb index ec00bc7..9489b7a 100644 --- a/lib/td/command/common.rb +++ b/lib/td/command/common.rb @@ -41,6 +41,14 @@ def get_client(opts={}) opts[:retry_post_requests] = Config.retry_post_requests end + # SSL verification options + ssl_option = Config.ssl_option + if ssl_option == false || (ssl_option.is_a?(String) && ssl_option.downcase == 'false') + opts[:verify] = false + elsif ssl_option.is_a?(String) && ssl_option.downcase != 'false' + opts[:verify] = ssl_option + end + # apikey is mandatory apikey = Config.apikey raise ConfigError, "Account is not configured." unless apikey diff --git a/lib/td/command/import.rb b/lib/td/command/import.rb index eb03486..7191e78 100644 --- a/lib/td/command/import.rb +++ b/lib/td/command/import.rb @@ -306,15 +306,9 @@ def set_sysprops_endpoint(sysprops) # generic URI host, port = endpoint.split(':', 2) - port = port.to_i - # Config.secure = false is the --insecure option was used - if Config.secure - port = 443 if port == 0 - ssl = true - else - port = 80 if port == 0 - ssl = false - end + port = port.to_i == 0 ? 443 : port.to_i + ssl = true + end sysprops << "-Dtd.api.server.scheme=#{ssl ? 'https' : 'http'}://" diff --git a/lib/td/command/runner.rb b/lib/td/command/runner.rb index e254e96..60577d2 100644 --- a/lib/td/command/runner.rb +++ b/lib/td/command/runner.rb @@ -78,6 +78,7 @@ def run(argv=ARGV) endpoint = @endpoint import_endpoint = @import_endpoint || @endpoint insecure = nil + ssl_ca_file = nil $verbose = false #$debug = false retry_post_requests = false @@ -104,8 +105,16 @@ def run(argv=ARGV) import_endpoint = e } - op.on('--insecure', "Insecure access: disable SSL (enabled by default)") {|b| - insecure = true + op.on('--ssl-verify VALUE', "SSL verification: 'false' to disable, or path to CA certificate file (default: true)") {|s| + require 'td/command/common' + if s.downcase == 'false' + insecure = true + else + unless File.exist?(s) + raise ParameterConfigurationError, "CA certification file not found: #{s}" + end + ssl_ca_file = s + end } op.on('-v', '--verbose', "verbose mode", TrueClass) {|b| @@ -113,7 +122,7 @@ def run(argv=ARGV) } #op.on('-d', '--debug', "debug mode", TrueClass) {|b| - # $debug = b + # $debug = b #} op.on('-h', '--help', "show help") { @@ -155,7 +164,13 @@ def run(argv=ARGV) Config.cl_import_endpoint = true end if insecure - Config.secure = false + Config.ssl_option = false + Config.cl_ssl_option = true + $stderr.puts "Warning: --insecure option disables SSL certificate verification, which is not recommended for production use." + end + if ssl_ca_file + Config.ssl_option = ssl_ca_file + Config.cl_ssl_option = true end if retry_post_requests Config.retry_post_requests = true diff --git a/lib/td/config.rb b/lib/td/config.rb index 5876a91..52e1365 100644 --- a/lib/td/config.rb +++ b/lib/td/config.rb @@ -26,6 +26,8 @@ class Config @@cl_import_endpoint = false # flag to indicate whether an endpoint has been provided through the command-line option @@secure = true @@retry_post_requests = false + @@ssl_option = ENV['TD_SSL_VERIFY'] || ENV['TD_SSL_CA_FILE'] || true + @@cl_ssl_option = false # flag to indicate whether ssl option has been provided through the command-line def initialize @path = nil @@ -194,12 +196,92 @@ def self.workflow_endpoint end end + def self.ssl_option + if @@cl_ssl_option + return @@ssl_option + end + + begin + conf = read + if conf['ssl.verify'] + return conf['ssl.verify'].downcase == 'false' ? false : conf['ssl.verify'] + elsif conf['ssl.ca_file'] + return conf['ssl.ca_file'] + end + rescue ConfigNotFoundError + end + + return @@ssl_option + end + + def self.ssl_option=(option) + @@ssl_option = option + end + + def self.cl_ssl_option + @@cl_ssl_option + end + + def self.cl_ssl_option=(flag) + @@cl_ssl_option = flag + end + + # Compatibility methods for existing code + def self.ssl_verify + option = ssl_option + return false if option == false || (option.is_a?(String) && option.downcase == 'false') + return true if option == true || option.nil? + return true # if it's a file path, verification is enabled + end + + def self.ssl_verify=(verify) + self.ssl_option = verify + end + + def self.ssl_ca_file + option = ssl_option + return option if option.is_a?(String) && option.downcase != 'false' && File.exist?(option) + return nil + end + + def self.ssl_ca_file=(ca_file) + self.ssl_option = ca_file + end + + def self.cl_ssl_verify + @@cl_ssl_option + end + + def self.cl_ssl_verify=(flag) + @@cl_ssl_option = flag + end + + def self.cl_ssl_ca_file + @@cl_ssl_option + end + + def self.cl_ssl_ca_file=(flag) + @@cl_ssl_option = flag + end + # renders the apikey and endpoint options as a string for the helper commands def self.cl_options_string + require 'shellwords' + string = "" string += "-k #{@@apikey} " if @@cl_apikey string += "-e #{@@endpoint} " if @@cl_endpoint string += "--import-endpoint #{@@import_endpoint} " if @@cl_import_endpoint + + # Handle simplified SSL option + if @@cl_ssl_option && @@ssl_option + if @@ssl_option == false || (@@ssl_option.is_a?(String) && @@ssl_option.downcase == 'false') + string += "--ssl-verify false " + elsif @@ssl_option.is_a?(String) + string += "--ssl-verify #{Shellwords.escape(@@ssl_option)} " + end + end + string end diff --git a/spec/td/command/ssl_options_spec.rb b/spec/td/command/ssl_options_spec.rb new file mode 100644 index 0000000..ad095c2 --- /dev/null +++ b/spec/td/command/ssl_options_spec.rb @@ -0,0 +1,323 @@ +require 'spec_helper' +require 'td/config' +require 'td/command/common' +require 'td/command/runner' +require 'tempfile' +require 'stringio' + +module TreasureData + module Command + describe 'SSL Options' do + include_context 'quiet_out' + + let(:command) { Class.new { include TreasureData::Command }.new } + let(:config_path) { Tempfile.new('td-config').path } + let(:runner) { TreasureData::Command::Runner.new } + let(:ca_file_content) { "-----BEGIN CERTIFICATE-----\nMIIDummy\n-----END CERTIFICATE-----" } + + before do + # Save original environment state + @original_env = { + 'TD_SSL_VERIFY' => ENV['TD_SSL_VERIFY'], + 'TD_SSL_CA_FILE' => ENV['TD_SSL_CA_FILE'] + } + + # Clean environment + ENV.delete('TD_SSL_VERIFY') + ENV.delete('TD_SSL_CA_FILE') + + # Reset config state + Config.path = config_path + Config.apikey = "dummy_api_key" + Config.cl_apikey = true + Config.ssl_verify = true + Config.ssl_ca_file = nil + Config.cl_ssl_verify = false + Config.cl_ssl_ca_file = false + end + + after do + # Restore original environment + @original_env.each do |key, value| + if value + ENV[key] = value + else + ENV.delete(key) + end + end + + # Clean up temp files + File.unlink(config_path) if File.exist?(config_path) + end + + describe 'CLI option parsing' do + it 'sets ssl_verify to false with --insecure flag' do + # Use help command to avoid actual TD API calls + expect { runner.run(['--insecure', 'help']) }.to raise_error(SystemExit) + expect(Config.ssl_verify).to be false + expect(Config.cl_ssl_verify).to be true + end + + it 'sets CA file path with valid --ssl-ca-file' do + Tempfile.create('ca-cert') do |ca_file| + ca_file.write(ca_file_content) + ca_file.close + + expect { runner.run(['--ssl-ca-file', ca_file.path, 'help']) }.to raise_error(SystemExit) + expect(Config.ssl_ca_file).to eq ca_file.path + expect(Config.cl_ssl_ca_file).to be true + end + end + + it 'displays warning when using --insecure' do + expect { runner.run(['--insecure', 'help']) }.to raise_error(SystemExit) + expect(stderr_io.string).to include('Warning: --insecure option disables SSL certificate verification') + end + end + + describe 'Configuration file reading' do + it 'reads ssl.verify from config file' do + File.open(config_path, 'w') do |f| + f.puts "[ssl]" + f.puts "verify = false" + end + + # Ensure CLI flags are not set + Config.cl_ssl_verify = false + + expect(Config.ssl_verify).to be false + end + + it 'reads ssl.ca_file from config file' do + ca_file_path = '/path/to/test/ca.crt' + File.open(config_path, 'w') do |f| + f.puts "[ssl]" + f.puts "ca_file = #{ca_file_path}" + end + + # Ensure CLI flags are not set + Config.cl_ssl_ca_file = false + + expect(Config.ssl_ca_file).to eq ca_file_path + end + + it 'handles ssl.verify = true in config file' do + File.open(config_path, 'w') do |f| + f.puts "[ssl]" + f.puts "verify = true" + end + + Config.cl_ssl_verify = false + expect(Config.ssl_verify).to be true + end + + it 'treats non-false values as true for ssl.verify' do + File.open(config_path, 'w') do |f| + f.puts "[ssl]" + f.puts "verify = anything" + end + + Config.cl_ssl_verify = false + expect(Config.ssl_verify).to be true + end + end + + describe 'Environment variable support' do + it 'respects TD_SSL_VERIFY=false from environment' do + # Test the environment variable initialization logic directly + ENV['TD_SSL_VERIFY'] = 'false' + ssl_verify_from_env = ENV['TD_SSL_VERIFY'].nil? ? true : (ENV['TD_SSL_VERIFY'].downcase == 'false' ? false : true) + expect(ssl_verify_from_env).to be false + end + + it 'respects TD_SSL_VERIFY=true from environment' do + ENV['TD_SSL_VERIFY'] = 'true' + ssl_verify_from_env = ENV['TD_SSL_VERIFY'].nil? ? true : (ENV['TD_SSL_VERIFY'].downcase == 'false' ? false : true) + expect(ssl_verify_from_env).to be true + end + + it 'respects TD_SSL_CA_FILE from environment' do + ca_file_path = '/env/path/to/ca.crt' + ENV['TD_SSL_CA_FILE'] = ca_file_path + ssl_ca_file_from_env = ENV['TD_SSL_CA_FILE'] + ssl_ca_file_from_env = nil if ssl_ca_file_from_env == "" + expect(ssl_ca_file_from_env).to eq ca_file_path + end + + it 'handles empty TD_SSL_CA_FILE' do + ENV['TD_SSL_CA_FILE'] = '' + ssl_ca_file_from_env = ENV['TD_SSL_CA_FILE'] + ssl_ca_file_from_env = nil if ssl_ca_file_from_env == "" + expect(ssl_ca_file_from_env).to be_nil + end + end + + describe 'Priority order: CLI > env > config > default' do + it 'CLI options override environment variables' do + # Set environment + ENV['TD_SSL_VERIFY'] = 'true' + ENV['TD_SSL_CA_FILE'] = '/env/ca.crt' + + Tempfile.create('cli-ca-cert') do |ca_file| + ca_file.write(ca_file_content) + ca_file.close + + # CLI should win + expect { runner.run(['--insecure', '--ssl-ca-file', ca_file.path, 'help']) }.to raise_error(SystemExit) + + expect(Config.ssl_verify).to be false + expect(Config.ssl_ca_file).to eq ca_file.path + end + end + + it 'Config file overrides defaults' do + File.open(config_path, 'w') do |f| + f.puts "[ssl]" + f.puts "verify = false" + f.puts "ca_file = /config/ca.crt" + end + + # Ensure no CLI or env overrides + Config.cl_ssl_verify = false + Config.cl_ssl_ca_file = false + + expect(Config.ssl_verify).to be false + expect(Config.ssl_ca_file).to eq '/config/ca.crt' + end + end + + describe 'Client integration' do + before do + # Mock TreasureData::Client to avoid actual network calls + allow(TreasureData::Client).to receive(:new).and_return(double('client').as_null_object) + end + + it 'passes verify: false to client when ssl_verify is disabled' do + Config.ssl_verify = false + Config.cl_ssl_verify = true + + expect(TreasureData::Client).to receive(:new).with("dummy_api_key", hash_including(verify: false)) + command.send(:get_client) + end + + it 'passes verify: ca_file_path to client when ssl_ca_file is set' do + ca_file_path = '/path/to/ca.crt' + Config.ssl_ca_file = ca_file_path + Config.cl_ssl_ca_file = true + + expect(TreasureData::Client).to receive(:new).with("dummy_api_key", hash_including(verify: ca_file_path)) + command.send(:get_client) + end + + it 'does not pass SSL options when using defaults' do + # Default config + Config.ssl_verify = true + Config.ssl_ca_file = nil + Config.cl_ssl_verify = false + Config.cl_ssl_ca_file = false + + expect(TreasureData::Client).to receive(:new).with("dummy_api_key", hash_not_including(:verify)) + command.send(:get_client) + end + end + + describe 'cl_options_string generation' do + before do + Config.cl_apikey = true + Config.apikey = 'test_key' + Config.cl_endpoint = false + Config.cl_import_endpoint = false + end + + it 'includes --insecure when ssl_verify is false via CLI' do + Config.cl_ssl_verify = true + Config.ssl_verify = false + + expect(Config.cl_options_string).to include('--insecure') + end + + it 'includes --ssl-ca-file when set via CLI' do + Config.cl_ssl_ca_file = true + Config.ssl_ca_file = '/path/to/ca.crt' + + expect(Config.cl_options_string).to include('--ssl-ca-file /path/to/ca.crt') + end + + it 'properly escapes CA file paths with spaces' do + Config.cl_ssl_ca_file = true + Config.ssl_ca_file = '/path/with spaces/ca.crt' + + expect(Config.cl_options_string).to include('--ssl-ca-file /path/with\\ spaces/ca.crt') + end + + it 'does not include SSL options when not set via CLI' do + Config.cl_ssl_verify = false + Config.cl_ssl_ca_file = false + + options_string = Config.cl_options_string + expect(options_string).not_to include('--insecure') + expect(options_string).not_to include('--ssl-ca-file') + end + end + + describe 'SSL option validation in runner' do + it 'validates that SSL CA file validation occurs in runner.rb' do + # Check that the runner code contains the validation logic + runner_file = File.read(File.join(File.dirname(__FILE__), '..', '..', '..', 'lib', 'td', 'command', 'runner.rb')) + expect(runner_file).to include('unless File.exist?(s)') + expect(runner_file).to include('ParameterConfigurationError') + expect(runner_file).to include('CA certification file not found') + end + + it 'validates that warning is shown for --insecure' do + runner_file = File.read(File.join(File.dirname(__FILE__), '..', '..', '..', 'lib', 'td', 'command', 'runner.rb')) + expect(runner_file).to include('Warning: --insecure option disables SSL certificate verification') + end + end + + describe 'Integration testing with execute_td helper' do + it 'shows SSL options in help' do + _, stdout = execute_td('--help') + expect(stdout).to include('--insecure') + expect(stdout).to include('--ssl-ca-file') + end + + it 'validates SSL warning message appears' do + stderr, _ = execute_td('--insecure help') + expect(stderr).to include('Warning:') + expect(stderr).to include('insecure') + end + end + + describe 'SSL configuration integration' do + it 'initializes SSL config properly from environment variables' do + original_env = { + 'TD_SSL_VERIFY' => ENV['TD_SSL_VERIFY'], + 'TD_SSL_CA_FILE' => ENV['TD_SSL_CA_FILE'] + } + + begin + ENV['TD_SSL_VERIFY'] = 'false' + ENV['TD_SSL_CA_FILE'] = '/test/ca.crt' + + # Load a fresh Config class to test environment initialization + load File.join(File.dirname(__FILE__), '..', '..', '..', 'lib', 'td', 'config.rb') + + # The config should read environment variables at load time + expect(ENV['TD_SSL_VERIFY']).to eq 'false' + expect(ENV['TD_SSL_CA_FILE']).to eq '/test/ca.crt' + ensure + # Restore environment + original_env.each do |key, value| + if value + ENV[key] = value + else + ENV.delete(key) + end + end + end + end + end + end + end +end diff --git a/spec/td/ssl_options_spec.rb b/spec/td/ssl_options_spec.rb new file mode 100644 index 0000000..ccd0d57 --- /dev/null +++ b/spec/td/ssl_options_spec.rb @@ -0,0 +1,252 @@ +require 'spec_helper' +require 'tempfile' +require 'td/config' + +describe TreasureData::Config do + describe 'SSL options' do + let(:config_file) { Tempfile.new('td-config') } + let(:config_path) { config_file.path } + + before do + # Save original environment state + @original_env = { + 'TD_SSL_VERIFY' => ENV['TD_SSL_VERIFY'], + 'TD_SSL_CA_FILE' => ENV['TD_SSL_CA_FILE'] + } + + # Clean environment + ENV.delete('TD_SSL_VERIFY') + ENV.delete('TD_SSL_CA_FILE') + + # Reset config state + TreasureData::Config.cl_ssl_verify = false + TreasureData::Config.ssl_verify = true + TreasureData::Config.cl_ssl_ca_file = false + TreasureData::Config.ssl_ca_file = nil + end + + after do + # Restore original environment + @original_env.each do |key, value| + if value + ENV[key] = value + else + ENV.delete(key) + end + end + + # Clean up + config_file.close + config_file.unlink + end + + context 'ssl_verify' do + it 'returns default value (true) when not configured' do + expect(TreasureData::Config.ssl_verify).to eq true + end + + it 'returns value from command line when cl_ssl_verify is true' do + TreasureData::Config.cl_ssl_verify = true + TreasureData::Config.ssl_verify = false + expect(TreasureData::Config.ssl_verify).to eq false + end + + it 'reads false value from config file' do + config_file.write <<-EOF +[ssl] +verify = false + EOF + config_file.close + + allow(TreasureData::Config).to receive(:read).and_return(TreasureData::Config.new.read(config_path)) + expect(TreasureData::Config.ssl_verify).to eq false + end + + it 'reads true value from config file' do + config_file.write <<-EOF +[ssl] +verify = true + EOF + config_file.close + + allow(TreasureData::Config).to receive(:read).and_return(TreasureData::Config.new.read(config_path)) + expect(TreasureData::Config.ssl_verify).to eq true + end + + it 'treats non-false values as true in config file' do + config_file.write <<-EOF +[ssl] +verify = anything_else + EOF + config_file.close + + allow(TreasureData::Config).to receive(:read).and_return(TreasureData::Config.new.read(config_path)) + expect(TreasureData::Config.ssl_verify).to eq true + end + + it 'respects environment variable TD_SSL_VERIFY=false' do + ENV['TD_SSL_VERIFY'] = 'false' + # Simulate config initialization with env var + TreasureData::Config.ssl_verify = (ENV['TD_SSL_VERIFY'].downcase == 'false' ? false : true) + expect(TreasureData::Config.ssl_verify).to eq false + end + + it 'respects environment variable TD_SSL_VERIFY=true' do + ENV['TD_SSL_VERIFY'] = 'true' + # Simulate config initialization with env var + TreasureData::Config.ssl_verify = (ENV['TD_SSL_VERIFY'].downcase == 'false' ? false : true) + expect(TreasureData::Config.ssl_verify).to eq true + end + end + + context 'ssl_ca_file' do + it 'returns nil when not configured' do + expect(TreasureData::Config.ssl_ca_file).to be_nil + end + + it 'returns value from command line when cl_ssl_ca_file is true' do + TreasureData::Config.cl_ssl_ca_file = true + TreasureData::Config.ssl_ca_file = '/cli/path/to/ca.crt' + expect(TreasureData::Config.ssl_ca_file).to eq '/cli/path/to/ca.crt' + end + + it 'reads value from config file' do + config_file.write <<-EOF +[ssl] +ca_file = /config/path/to/ca.crt + EOF + config_file.close + + allow(TreasureData::Config).to receive(:read).and_return(TreasureData::Config.new.read(config_path)) + expect(TreasureData::Config.ssl_ca_file).to eq '/config/path/to/ca.crt' + end + + it 'respects environment variable TD_SSL_CA_FILE' do + ENV['TD_SSL_CA_FILE'] = '/env/path/to/ca.crt' + # Simulate config initialization with env var + TreasureData::Config.ssl_ca_file = ENV['TD_SSL_CA_FILE'] + expect(TreasureData::Config.ssl_ca_file).to eq '/env/path/to/ca.crt' + end + + it 'handles empty TD_SSL_CA_FILE environment variable' do + ENV['TD_SSL_CA_FILE'] = '' + # Simulate config initialization with empty env var + TreasureData::Config.ssl_ca_file = ENV['TD_SSL_CA_FILE'] == '' ? nil : ENV['TD_SSL_CA_FILE'] + expect(TreasureData::Config.ssl_ca_file).to be_nil + end + end + + context 'cl_options_string' do + before do + TreasureData::Config.cl_apikey = true + TreasureData::Config.apikey = 'test_apikey' + TreasureData::Config.cl_endpoint = false + TreasureData::Config.cl_import_endpoint = false + TreasureData::Config.cl_ssl_verify = false + TreasureData::Config.cl_ssl_ca_file = false + end + + it 'includes API key when cl_apikey is true' do + expect(TreasureData::Config.cl_options_string).to include('-k test_apikey') + end + + it 'includes --ssl-verify false when cl_ssl_verify is true and ssl_verify is false' do + TreasureData::Config.cl_ssl_verify = true + TreasureData::Config.ssl_verify = false + expect(TreasureData::Config.cl_options_string).to include('--ssl-verify false') + end + + it 'does not include --ssl-verify when cl_ssl_verify is false' do + TreasureData::Config.cl_ssl_verify = false + expect(TreasureData::Config.cl_options_string).not_to include('--ssl-verify') + end + + it 'includes --ssl-verify with CA file path when cl_ssl_ca_file is true' do + TreasureData::Config.cl_ssl_ca_file = true + TreasureData::Config.ssl_ca_file = '/path/to/ca.crt' + expect(TreasureData::Config.cl_options_string).to include('--ssl-verify /path/to/ca.crt') + end + + it 'does not include --ssl-verify when cl_ssl_ca_file is false' do + TreasureData::Config.cl_ssl_ca_file = false + expect(TreasureData::Config.cl_options_string).not_to include('--ssl-verify') + end + + it 'properly escapes CA file paths with spaces' do + TreasureData::Config.cl_ssl_ca_file = true + TreasureData::Config.ssl_ca_file = '/path/with spaces/ca.crt' + expect(TreasureData::Config.cl_options_string).to include('--ssl-verify /path/with\\ spaces/ca.crt') + end + + it 'properly escapes CA file paths with special characters' do + TreasureData::Config.cl_ssl_ca_file = true + TreasureData::Config.ssl_ca_file = '/path/with$special&chars/ca.crt' + expect(TreasureData::Config.cl_options_string).to include('--ssl-verify /path/with\\$special\\&chars/ca.crt') + end + end + + context 'Configuration priority' do + it 'CLI options have highest priority' do + # Set config file + config_file.write <<-EOF +[ssl] +verify = true +ca_file = /config/ca.crt + EOF + config_file.close + + # Set env vars + ENV['TD_SSL_VERIFY'] = 'true' + ENV['TD_SSL_CA_FILE'] = '/env/ca.crt' + + # Set CLI options (should win) + TreasureData::Config.cl_ssl_verify = true + TreasureData::Config.ssl_verify = false + TreasureData::Config.cl_ssl_ca_file = true + TreasureData::Config.ssl_ca_file = '/cli/ca.crt' + + expect(TreasureData::Config.ssl_verify).to eq false + expect(TreasureData::Config.ssl_ca_file).to eq '/cli/ca.crt' + end + + it 'Environment variables override config file when CLI not set' do + # Set config file + config_file.write <<-EOF +[ssl] +verify = true +ca_file = /config/ca.crt + EOF + config_file.close + + # Set env vars (should win over config) + ENV['TD_SSL_VERIFY'] = 'false' + ENV['TD_SSL_CA_FILE'] = '/env/ca.crt' + + # Simulate config initialization + TreasureData::Config.ssl_verify = (ENV['TD_SSL_VERIFY'].downcase == 'false' ? false : true) + TreasureData::Config.ssl_ca_file = ENV['TD_SSL_CA_FILE'] + TreasureData::Config.cl_ssl_verify = false + TreasureData::Config.cl_ssl_ca_file = false + + expect(TreasureData::Config.ssl_verify).to eq false + expect(TreasureData::Config.ssl_ca_file).to eq '/env/ca.crt' + end + end + + context 'Flag management' do + it 'tracks cl_ssl_verify flag correctly' do + expect(TreasureData::Config.cl_ssl_verify).to eq false + + TreasureData::Config.cl_ssl_verify = true + expect(TreasureData::Config.cl_ssl_verify).to eq true + end + + it 'tracks cl_ssl_ca_file flag correctly' do + expect(TreasureData::Config.cl_ssl_ca_file).to eq false + + TreasureData::Config.cl_ssl_ca_file = true + expect(TreasureData::Config.cl_ssl_ca_file).to eq true + end + end + end +end \ No newline at end of file