diff --git a/app/controllers/insights_cloud/api/machine_telemetries_controller.rb b/app/controllers/insights_cloud/api/machine_telemetries_controller.rb index 2384fb50..75a301a2 100644 --- a/app/controllers/insights_cloud/api/machine_telemetries_controller.rb +++ b/app/controllers/insights_cloud/api/machine_telemetries_controller.rb @@ -25,14 +25,33 @@ def forward_request }, status: :bad_gateway end - if @cloud_response.headers[:content_disposition] - return send_data @cloud_response, disposition: @cloud_response.headers[:content_disposition], type: @cloud_response.headers[:content_type] + if @cloud_response.code >= 300 + return render json: { + :message => 'Cloud request failed', + :headers => {}, + :response => @cloud_response, + }, status: @cloud_response.code end + # Append redhat-specific headers + @cloud_response.headers.each do |key, value| + assign_header(response, @cloud_response, key, false) if key.to_s.start_with?('x_rh_') + end + # Append general headers assign_header(response, @cloud_response, :x_resource_count, true) - assign_header(response, @cloud_response, :x_rh_insights_request_id, false) + headers[Rack::ETAG] = @cloud_response.headers[:etag] - render json: @cloud_response, status: @cloud_response.code + if @cloud_response.headers[:content_disposition] + # If there is a Content-Disposition header, it means we are forwarding binary data, send the raw data with proper + # content type + send_data @cloud_response, disposition: @cloud_response.headers[:content_disposition], type: @cloud_response.headers[:content_type] + elsif @cloud_response.headers[:content_type] =~ /zip/ + # if there is no Content-Disposition, but the content type is binary according the content type, + # forward the request as binry too + send_data @cloud_response, type: @cloud_response.headers[:content_type] + else + render json: @cloud_response, status: @cloud_response.code + end end def branch_info diff --git a/app/services/foreman_rh_cloud/cloud_request_forwarder.rb b/app/services/foreman_rh_cloud/cloud_request_forwarder.rb index 8bc2b9b3..50c17eb6 100644 --- a/app/services/foreman_rh_cloud/cloud_request_forwarder.rb +++ b/app/services/foreman_rh_cloud/cloud_request_forwarder.rb @@ -17,17 +17,20 @@ def forward_request(original_request, controller_name, branch_id, certs) logger.debug("Sending request to: #{request_opts[:url]}") execute_cloud_request(request_opts) + rescue RestClient::Exception => error_response + error_response.response end def prepare_request_opts(original_request, forward_payload, forward_params, certs) base_params = { method: original_request.method, payload: forward_payload, - headers: { - params: forward_params, - user_agent: http_user_agent(original_request), - content_type: original_request.media_type.presence || original_request.format.to_s, - }, + headers: original_headers(original_request).merge( + { + params: forward_params, + user_agent: http_user_agent(original_request), + content_type: original_request.media_type.presence || original_request.format.to_s, + }), } base_params.merge(path_params(original_request.path, certs)) end @@ -78,6 +81,16 @@ def path_params(request_path, certs) end end + def original_headers(original_request) + headers = { + if_none_match: original_request.if_none_match, + if_modified_since: original_request.if_modified_since, + }.compact + + logger.debug("Sending headers: #{headers}") + headers + end + def platform_request? ->(request_path) { request_path.include? '/platform' } end diff --git a/test/controllers/insights_cloud/api/machine_telemetries_controller_test.rb b/test/controllers/insights_cloud/api/machine_telemetries_controller_test.rb index 857a58ef..062dfacd 100644 --- a/test/controllers/insights_cloud/api/machine_telemetries_controller_test.rb +++ b/test/controllers/insights_cloud/api/machine_telemetries_controller_test.rb @@ -3,6 +3,8 @@ module InsightsCloud::Api class MachineTelemetriesControllerTest < ActionController::TestCase context '#forward_request' do + include MockCerts + setup do @body = 'Cloud response body' @http_req = RestClient::Request.new(:method => 'GET', :url => 'http://test.theforeman.org') @@ -12,38 +14,9 @@ class MachineTelemetriesControllerTest < ActionController::TestCase User.current = ::Katello::CpConsumerUser.new(:uuid => host.subscription_facet.uuid, :login => host.subscription_facet.uuid) InsightsCloud::Api::MachineTelemetriesController.any_instance.stubs(:upstream_owner).returns({ 'uuid' => 'abcdefg' }) - @cert1 = "-----BEGIN CERTIFICATE-----\r\n" + - "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\r\n" + - "osxMtcaZZ1E=\r\n" + - "-----END CERTIFICATE-----" + setup_certs_expectation do + InsightsCloud::Api::MachineTelemetriesController.any_instance.stubs(:candlepin_id_cert) + end end test "should respond with response from cloud" do @@ -63,13 +36,6 @@ class MachineTelemetriesControllerTest < ActionController::TestCase ::ForemanRhCloud::CloudRequestForwarder.any_instance.expects(:execute_cloud_request).with do |opts| opts[:headers][:content_type] == 'application/json' end.returns(res) - InsightsCloud::Api::MachineTelemetriesController.any_instance.expects(:candlepin_id_cert) - .returns( - { - cert: @cert1, - key: OpenSSL::PKey::RSA.new(1024).to_pem, - } - ) InsightsCloud::Api::MachineTelemetriesController.any_instance.expects(:cp_owner_id).returns('123') post :forward_request, as: :json, params: { "path" => "static/v1/test", "machine_telemetry" => {"foo" => "bar"} } @@ -90,6 +56,29 @@ class MachineTelemetriesControllerTest < ActionController::TestCase assert_equal x_rh_insights_request_id, @response.headers['x_rh_insights_request_id'] end + test "should set etag header to response from cloud" do + etag = '12345' + req = RestClient::Request.new(:method => 'GET', :url => 'http://test.theforeman.org', :headers => { "If-None-Match": etag}) + net_http_resp = Net::HTTPResponse.new(1.0, 200, "OK") + net_http_resp[Rack::ETAG] = etag + res = RestClient::Response.create(@body, net_http_resp, req) + ::ForemanRhCloud::CloudRequestForwarder.any_instance.stubs(:forward_request).returns(res) + + get :forward_request, params: { "path" => "static/v1/release/insights-core.egg" } + assert_equal etag, @response.headers[Rack::ETAG] + end + + test "should set content type header to response from cloud" do + req = RestClient::Request.new(:method => 'GET', :url => 'http://test.theforeman.org') + net_http_resp = Net::HTTPResponse.new(1.0, 200, "OK") + net_http_resp['Content-Type'] = 'application/zip' + res = RestClient::Response.create(@body, net_http_resp, req) + ::ForemanRhCloud::CloudRequestForwarder.any_instance.stubs(:forward_request).returns(res) + + get :forward_request, params: { "path" => "static/v1/release/insights-core.egg" } + assert_equal net_http_resp['Content-Type'], @response.headers['Content-Type'] + end + test "should handle failed authentication to cloud" do net_http_resp = Net::HTTPResponse.new(1.0, 401, "Unauthorized") res = RestClient::Response.create(@body, net_http_resp, @http_req) @@ -99,6 +88,17 @@ class MachineTelemetriesControllerTest < ActionController::TestCase assert_equal 502, @response.status assert_equal 'Authentication to the Insights Service failed.', JSON.parse(@response.body)['message'] end + + test "should forward errors to the client" do + net_http_resp = Net::HTTPResponse.new(1.0, 500, "TEST_RESPONSE") + res = RestClient::Response.create(@body, net_http_resp, @http_req) + ::ForemanRhCloud::CloudRequestForwarder.any_instance.stubs(:execute_cloud_request).raises(RestClient::InternalServerError.new(res)) + + get :forward_request, params: { "path" => "platform/module-update-router/v1/channel" } + assert_equal 500, @response.status + assert_equal 'Cloud request failed', JSON.parse(@response.body)['message'] + assert_match /#{@body}/, JSON.parse(@response.body)['response'] + end end context '#branch_info' do diff --git a/test/test_plugin_helper.rb b/test/test_plugin_helper.rb index acf44abf..7fadccd4 100644 --- a/test/test_plugin_helper.rb +++ b/test/test_plugin_helper.rb @@ -39,3 +39,56 @@ module KatelloLocationFix end end end + +module MockCerts + extend ActiveSupport::Concern + + def test_certificate + @test_certificate ||= "-----BEGIN CERTIFICATE-----\r\n" + + "MIIFdDCCA1ygAwIBAgIJAM5Uqykb3EAtMA0GCSqGSIb3DQEBCwUAME8xCzAJBgNV\r\n" + + "BAYTAklMMREwDwYDVQQIDAhUZWwgQXZpdjEUMBIGA1UECgwLVGhlIEZvcmVtYW4x\r\n" + + "FzAVBgNVBAMMDnRoZWZvcmVtYW4ub3JnMB4XDTE4MDMyNDEyMzYyOFoXDTI4MDMy\r\n" + + "MTEyMzYyOFowTzELMAkGA1UEBhMCSUwxETAPBgNVBAgMCFRlbCBBdml2MRQwEgYD\r\n" + + "VQQKDAtUaGUgRm9yZW1hbjEXMBUGA1UEAwwOdGhlZm9yZW1hbi5vcmcwggIiMA0G\r\n" + + "CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF04/s4h+BgHPG1HDZ/sDlYq925pkc\r\n" + + "RTVAfnE2EXDAmZ6W4Q9ueDY65MHe3ZWO5Dg72kNSP2sK9kRI7Dk5CAFOgyw1rH8t\r\n" + + "Hd1+0xp/lv6e4SvSYghxIL68vFe0ftKkm1usqejBM5ZTgKr7JCI+XSIN36F65Kde\r\n" + + "c+vxwBnayuhP04r9/aaE/709SXML4eRVYW8I3qFy9FPtUOm+bY8U2PIv5fHayqbG\r\n" + + "cL/4t3+MCtMhHJsLzdBXya+1P5t+HcKjUNlmwoUF961YAktVuEFloGd0RMRlqF3/\r\n" + + "itU3QNlXgA5QBIciE5VPr/PiqgMC3zgd5avjF4OribZ+N9AATLiQMW78il5wSfcc\r\n" + + "kQjU9ChOLrzku455vQ8KE4bc0qvpCWGfUah6MvL9JB+TQkRl/8kxl0b9ZinIvJDH\r\n" + + "ynVMb4cB/TDEjrjOfzn9mWLH0ZJqjmc2bER/G12WQxOaYLxdVwRStD3Yh6PtiFWu\r\n" + + "sXOk19UOTVkeuvGFVtvzLfEwQ1lDEo7+VBQz8FG/HBu2Hpq3IwCFrHuicikwjQJk\r\n" + + "nfturgD0rBOKEc1qWNZRCvovYOLL6ihvv5Orujsx5ZCHOAtnVNxkvIlFt2RS45LF\r\n" + + "MtPJyhAc6SjitllfUEirxprsbmeSZqrIfzcGaEhgOSnyik1WMv6bYiqPfBg8Fzjh\r\n" + + "vOCbtiDNPmvgOwIDAQABo1MwUTAdBgNVHQ4EFgQUtkAgQopsTtG9zSG3MgW2IxHD\r\n" + + "MDwwHwYDVR0jBBgwFoAUtkAgQopsTtG9zSG3MgW2IxHDMDwwDwYDVR0TAQH/BAUw\r\n" + + "AwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAJq7iN+ZroRBweNhvUobxs75bLIV6tNn1\r\n" + + "MdNHDRA+hezwf+gxHZhFyaAHfTpst2/9leK5Qe5Zd6gZLr3E5/8ppQuRod72H39B\r\n" + + "vxMlG5zxDss0WMo3vZeKZbTY6QhXi/lY2IZ6OGV4feSvCsYxn27GTjjrRUSLFeHH\r\n" + + "JVemCwCDMavaE3+OIY4v2P4FcG+MjUvfOB9ahI24TWL7YgrsNVmJjCILq+EeUj0t\r\n" + + "Gde1SXVyLkqt7PoxHRJAE0BCEMJSnjxaVB329acJgeehBUxjj4CCPqtDxtbz9HEH\r\n" + + "mOKfNdaKpFor+DUeEKUWVGnr9U9xOaC+Ws+oX7MIEUCDM7p2ob4JwcjnFs1jZgHh\r\n" + + "Hwig+i7doTlc701PvKWO96fuNHK3B3/jTb1fVvSZ49O/RvY1VWODdUdxWmXGHNh3\r\n" + + "LoR8tSPEb46lC2DXGaIQumqQt8PnBG+vL1qkQa1SGTV7dJ8TTbxbv0S+sS+igkk9\r\n" + + "zsIEK8Ea3Ep935cXximz0faAAKHSA+It+xHLAyDtqy2KaAEBgGsBuuWlUfK6TaP3\r\n" + + "Gwdjct3y4yYUO45lUsUfHqX8vk/4ttW5zYeDiW+HArJz+9VUXNbEdury4kGuHgBj\r\n" + + "xHD4Bsul65+hHZ9QywKU26F1A6TLkYpQ2rk/Dx9LGICM4m4IlHjWJPFsQdtkyOor\r\n" + + "osxMtcaZZ1E=\r\n" + + "-----END CERTIFICATE-----" + end + + def generate_certs_hash + { + cert: test_certificate, + key: OpenSSL::PKey::RSA.new(1024).to_pem, + } + end + + def setup_certs_expectation + expectation = yield + expectation.returns( + generate_certs_hash + ) + end +end