diff --git a/files/gitlab-cookbooks/consul/libraries/consul_helper.rb b/files/gitlab-cookbooks/consul/libraries/consul_helper.rb index 73bd331c1e5b068df0c5be0b223852bdab3796b5_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9jb25zdWwvbGlicmFyaWVzL2NvbnN1bF9oZWxwZXIucmI=..64b77f495472c696c041c89ab6c1ba9dbb7f77ee_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9jb25zdWwvbGlicmFyaWVzL2NvbnN1bF9oZWxwZXIucmI= 100644 --- a/files/gitlab-cookbooks/consul/libraries/consul_helper.rb +++ b/files/gitlab-cookbooks/consul/libraries/consul_helper.rb @@ -56,8 +56,8 @@ }.compact } } - tls_cfg['ports'] = { 'https': api_port('https') } if server? + tls_cfg['ports'] = { 'https': api_port('https') } tls_cfg end @@ -60,8 +60,8 @@ tls_cfg end - def configuration + def final_config config = Chef::Mixin::DeepMerge.merge( default_configuration, node['consul']['configuration'] @@ -69,5 +69,5 @@ if server? return Chef::Mixin::DeepMerge.merge( default_server_configuration, config - ).to_json + ) end @@ -73,5 +73,10 @@ end - config.to_json + + config + end + + def configuration + final_config.to_json end def use_encryption? @@ -94,5 +99,5 @@ http_port = node['consul']['http_port'] https_port = node['consul']['https_port'] - return {} if http_port.nil? && https_port.nil? + ports = {} @@ -98,8 +103,7 @@ - ports = {} ports['http'] = http_port unless http_port.nil? ports['https'] = https_port unless https_port.nil? { 'ports' => ports } end @@ -100,11 +104,11 @@ ports['http'] = http_port unless http_port.nil? ports['https'] = https_port unless https_port.nil? { 'ports' => ports } end - def api_url - scheme = use_tls? || api_port('http').negative? ? 'https' : 'http' + def api_url(scheme: nil) + scheme ||= use_tls? || api_port('http').negative? ? 'https' : 'http' "#{scheme}://#{api_address(scheme)}:#{api_port(scheme)}" end @@ -187,12 +191,11 @@ def running_version return unless OmnibusHelper.new(@node).service_up?('consul') - info = get_api('/v1/agent/self') do |response| - response.code == '200' ? JSON.parse(response.body, symbolize_names: true) : {} - end + response_code, response_body = get_api('/v1/agent/self') + info = response_code == '200' ? JSON.parse(response_body, symbolize_names: true) : {} info[:Config][:Version] unless info.empty? end private @@ -193,7 +196,37 @@ info[:Config][:Version] unless info.empty? end private + def verify_incoming? + final_config['tls']['defaults']['verify_incoming'] + end + + def can_access_api_over_https? + # If daemon isn't listening over HTTPS, no point in proceeding. Just use + # HTTP. + return false unless use_tls? + + # If incoming requests aren't verified, we can access API over HTTPS + # without client certificates. + return true unless verify_incoming? + + # If incoming connections are verified, we need a certificate/key to use as + # "client" certificate/key while accessing API. Let's use the ones + # specified as server certificate/key for this purpose. + File.exist?(final_config['tls']['defaults']['cert_file'].to_s) && File.exist?(final_config['tls']['defaults']['key_file'].to_s) + end + + def get_tls_args + args = { use_ssl: true } + + return args unless verify_incoming? + + args[:cert] = OpenSSL::X509::Certificate.new(File.read(final_config['tls']['defaults']['cert_file'])) + args[:key] = OpenSSL::PKey.read(File.read(final_config['tls']['defaults']['key_file'])) + + args + end + def get_api(endpoint, header = nil) @@ -199,4 +232,9 @@ def get_api(endpoint, header = nil) - uri = URI(api_url) - use_ssl = uri.scheme == "https" + if can_access_api_over_https? + uri = URI(api_url(scheme: 'https')) + args = get_tls_args + else + uri = URI(api_url(scheme: 'http')) + args = {} + end @@ -202,3 +240,22 @@ + begin + fetch_response(uri, endpoint, args, header) + rescue Timeout::Error => e + # If we were already using HTTP, there is nothing more we can do. + # Fail hard. + raise Timeout::Error, e.message if uri.scheme == "http" + + # We were trying HTTPS but, it wasn't available. Maybe HTTPS was enabled + # in this reconfigure run, and won't be active till user restarts Consul, + # and thus Consul is still running over HTTP only. Try accessing API over + # HTTP. + uri = URI(api_url(scheme: 'http')) + args = {} + + fetch_response(uri, endpoint, args, header) + end + end + + def fetch_response(uri, endpoint, args, header = nil) Timeout.timeout(30, Timeout::Error, "Timed out waiting for Consul to start") do loop do @@ -203,4 +260,4 @@ Timeout.timeout(30, Timeout::Error, "Timed out waiting for Consul to start") do loop do - Net::HTTP.start(uri.host, uri.port, use_ssl: use_ssl) do |http| + Net::HTTP.start(uri.host, uri.port, **args) do |http| http.request_get(endpoint, header) do |response| @@ -206,5 +263,5 @@ http.request_get(endpoint, header) do |response| - return yield response + return response.code, response.body end end rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL diff --git a/spec/chef/cookbooks/consul/libraries/consul_helper_spec.rb b/spec/chef/cookbooks/consul/libraries/consul_helper_spec.rb index 73bd331c1e5b068df0c5be0b223852bdab3796b5_c3BlYy9jaGVmL2Nvb2tib29rcy9jb25zdWwvbGlicmFyaWVzL2NvbnN1bF9oZWxwZXJfc3BlYy5yYg==..64b77f495472c696c041c89ab6c1ba9dbb7f77ee_c3BlYy9jaGVmL2Nvb2tib29rcy9jb25zdWwvbGlicmFyaWVzL2NvbnN1bF9oZWxwZXJfc3BlYy5yYg== 100644 --- a/spec/chef/cookbooks/consul/libraries/consul_helper_spec.rb +++ b/spec/chef/cookbooks/consul/libraries/consul_helper_spec.rb @@ -5,9 +5,9 @@ subject { described_class.new(chef_run.node) } describe '#running_version' do - let(:consul_api_output) { instance_double('response', code: '200', body: '{"Config": { "Version": "1.8.10" }}') } + let(:consul_api_output) { ['200', '{"Config": { "Version": "1.8.10" }}'] } before do # Un-doing the stub added in chef_helper allow_any_instance_of(described_class).to receive(:running_version).and_call_original allow(Gitlab).to receive(:[]).and_call_original @@ -9,9 +9,9 @@ before do # Un-doing the stub added in chef_helper allow_any_instance_of(described_class).to receive(:running_version).and_call_original allow(Gitlab).to receive(:[]).and_call_original - allow(subject).to receive(:get_api).and_yield(consul_api_output) + allow(subject).to receive(:get_api).and_return(consul_api_output) end context 'when consul is not running' do