foreman_rh_cloud 3.0.16 → 3.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/insights_cloud/candlepin_cache.rb +28 -0
  3. data/app/controllers/concerns/insights_cloud/client_authentication.rb +23 -0
  4. data/app/controllers/foreman_inventory_upload/accounts_controller.rb +1 -1
  5. data/app/controllers/foreman_inventory_upload/uploads_controller.rb +1 -8
  6. data/app/controllers/insights_cloud/api/machine_telemetries_controller.rb +73 -0
  7. data/app/controllers/insights_cloud/hits_controller.rb +3 -3
  8. data/app/controllers/insights_cloud/tasks_controller.rb +6 -1
  9. data/app/models/insights_hit.rb +16 -1
  10. data/app/models/insights_resolution.rb +3 -0
  11. data/app/models/insights_rule.rb +3 -0
  12. data/app/models/setting/rh_cloud.rb +12 -2
  13. data/app/services/foreman_rh_cloud/branch_info.rb +57 -0
  14. data/app/services/foreman_rh_cloud/cloud_request_forwarder.rb +102 -0
  15. data/config/routes.rb +10 -0
  16. data/db/migrate/20210214000001_create_rules_and_resolutions.foreman_rh_cloud.rb +24 -0
  17. data/db/migrate/20210214000002_add_rule_id_to_hits.foreman_rh_cloud.rb +5 -0
  18. data/lib/foreman_inventory_upload/generators/slice.rb +3 -3
  19. data/lib/foreman_rh_cloud.rb +9 -0
  20. data/lib/foreman_rh_cloud/engine.rb +1 -0
  21. data/lib/foreman_rh_cloud/version.rb +1 -1
  22. data/lib/insights_cloud.rb +4 -0
  23. data/lib/insights_cloud/async/insights_full_sync.rb +29 -0
  24. data/lib/insights_cloud/async/insights_rules_sync.rb +80 -0
  25. data/lib/insights_cloud/async/rules_result.rb +13 -0
  26. data/package.json +1 -1
  27. data/test/controllers/insights_cloud/api/machine_telemetries_controller_test.rb +107 -0
  28. data/test/factories/insights_factories.rb +2 -1
  29. data/test/factories/inventory_upload_factories.rb +12 -0
  30. data/test/jobs/insights_full_sync_test.rb +17 -0
  31. data/test/jobs/insights_rules_sync_test.rb +198 -0
  32. data/test/unit/services/foreman_rh_cloud/branch_info_test.rb +60 -0
  33. data/test/unit/services/foreman_rh_cloud/cloud_request_forwarder_test.rb +106 -0
  34. data/test/unit/slice_generator_test.rb +29 -3
  35. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountList.fixtures.js +1 -1
  36. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListActions.test.js.snap +1 -1
  37. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListReducer.test.js.snap +1 -1
  38. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListSelectors.test.js.snap +2 -2
  39. data/webpack/ForemanInventoryUpload/Components/Dashboard/DashboardActions.js +1 -1
  40. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/DashboardActions.test.js +4 -4
  41. data/webpack/ForemanInventoryUpload/Components/PageHeader/PageHeader.js +2 -0
  42. data/webpack/ForemanInventoryUpload/Components/PageHeader/PageHeader.scss +12 -0
  43. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/__snapshots__/PageHeader.test.js.snap +1 -0
  44. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/SettingsWarning.js +64 -0
  45. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/SettingsWarning.test.js +18 -0
  46. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/__snapshots__/SettingsWarning.test.js.snap +30 -0
  47. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SettingsWarning/index.js +25 -0
  48. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/SyncButton.js +2 -2
  49. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/SyncButton/__tests__/SyncButton.test.js +1 -1
  50. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTable.js +9 -5
  51. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableActions.js +55 -30
  52. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableConstants.js +10 -9
  53. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableHelpers.js +14 -6
  54. data/webpack/InsightsCloudSync/Components/InsightsTable/InsightsTableSelectors.js +4 -0
  55. data/webpack/InsightsCloudSync/Components/InsightsTable/SelectAllAlert.js +4 -10
  56. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTableActions.test.js +48 -0
  57. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/__snapshots__/InsightsTable.test.js.snap +11 -24
  58. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/__snapshots__/InsightsTableActions.test.js.snap +132 -0
  59. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/__snapshots__/InsightsTableSelectors.test.js.snap +1 -0
  60. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/fixtures.js +1 -1
  61. data/webpack/InsightsCloudSync/Components/InsightsTable/table.scss +10 -0
  62. data/webpack/InsightsCloudSync/InsightsCloudSync.js +18 -16
  63. data/webpack/InsightsCloudSync/InsightsCloudSyncActions.js +23 -20
  64. data/webpack/InsightsCloudSync/InsightsCloudSyncConstants.js +1 -1
  65. data/webpack/InsightsCloudSync/__snapshots__/InsightsCloudSync.test.js.snap +36 -36
  66. data/webpack/InsightsCloudSync/__tests__/InsightsCloudSyncActions.test.js +9 -0
  67. data/webpack/InsightsCloudSync/__tests__/__snapshots__/InsightsCloudSyncActions.test.js.snap +11 -0
  68. data/webpack/__mocks__/foremanReact/redux/API/index.js +1 -0
  69. metadata +29 -2
@@ -0,0 +1,5 @@
1
+ class AddRuleIdToHits < ActiveRecord::Migration[5.2]
2
+ def change
3
+ add_column :insights_hits, :rule_id, :string
4
+ end
5
+ end
@@ -103,10 +103,10 @@ module ForemanInventoryUpload
103
103
  @stream.simple_field('cores_per_socket', fact_value(host, 'cpu::core(s)_per_socket')) { |v| v.to_i }
104
104
  @stream.simple_field('system_memory_bytes', fact_value(host, 'memory::memtotal')) { |v| kilobytes_to_bytes(v.to_i) }
105
105
  @stream.array_field('network_interfaces') do
106
- @stream.raw(host.interfaces.map do |nic|
106
+ @stream.raw(host.interfaces.reject { |nic| nic.identifier.empty? }.map do |nic|
107
107
  {
108
- 'ipv4_addresses': [host_ips_cache[nic.ip]].compact,
109
- 'ipv6_addresses': [nic.ip6].compact,
108
+ 'ipv4_addresses': [host_ips_cache[nic.ip]].reject(&:empty?),
109
+ 'ipv6_addresses': [nic.ip6].reject(&:empty?),
110
110
  'mtu': nic.try(:mtu) && nic.mtu.to_i,
111
111
  'mac_address': nic.mac,
112
112
  'name': nic.identifier,
@@ -8,6 +8,10 @@ module ForemanRhCloud
8
8
  @base_url ||= ENV['SATELLITE_RH_CLOUD_URL'] || 'https://cloud.redhat.com'
9
9
  end
10
10
 
11
+ def self.cert_base_url
12
+ @cert_base_url ||= ENV['SATELLITE_CERT_RH_CLOUD_URL'] || 'https://cert.cloud.redhat.com'
13
+ end
14
+
11
15
  def self.authentication_url
12
16
  # https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/token
13
17
  @authentication_url ||= ENV['SATELLITE_RH_CLOUD_SSO_URL'] || 'https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/token'
@@ -17,6 +21,10 @@ module ForemanRhCloud
17
21
  @verify_ssl_method ||= ENV['SATELLITE_RH_CLOUD_URL'] ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
18
22
  end
19
23
 
24
+ def self.query_limit
25
+ @query_limit ||= ENV['SATELLITE_RH_CLOUD_QUERY_LIMIT'] ? ENV['SATELLITE_RH_CLOUD_QUERY_LIMIT'].to_i : 100
26
+ end
27
+
20
28
  def self.http_proxy_string(logger: Foreman::Logging.logger('background'))
21
29
  ForemanRhCloud.proxy_setting(logger: logger)
22
30
  end
@@ -58,6 +66,7 @@ module ForemanRhCloud
58
66
  # This method assumes uri_string contains uri-encoded username and p@$$word:
59
67
  # http://user:p%40%24%24word@localhost:8888
60
68
  def self.transform_scheme(uri_string)
69
+ return unless uri_string
61
70
  transformed_uri = URI.parse(uri_string)
62
71
 
63
72
  case transformed_uri.scheme
@@ -13,6 +13,7 @@ module ForemanRhCloud
13
13
  config.autoload_paths += Dir["#{config.root}/app/controllers/concerns"]
14
14
  config.autoload_paths += Dir["#{config.root}/app/helpers/concerns"]
15
15
  config.autoload_paths += Dir["#{config.root}/app/models/concerns"]
16
+ config.autoload_paths += Dir["#{config.root}/app/services"]
16
17
  config.autoload_paths += Dir["#{config.root}/app/overrides"]
17
18
  config.autoload_paths += Dir["#{config.root}/app/services"]
18
19
  config.autoload_paths += Dir["#{config.root}/lib"]
@@ -1,3 +1,3 @@
1
1
  module ForemanRhCloud
2
- VERSION = '3.0.16'.freeze
2
+ VERSION = '3.0.17'.freeze
3
3
  end
@@ -12,4 +12,8 @@ module InsightsCloud
12
12
  def self.hits_export_url
13
13
  ForemanRhCloud.base_url + '/api/insights/v1/export/hits/'
14
14
  end
15
+
16
+ def self.rules_url(limit: ForemanRhCloud.query_limit, offset: 0)
17
+ ForemanRhCloud.base_url + "/api/insights/v1/rule/?impacting=true&rule_status=enabled&has_playbook=true&limit=#{limit}&offset=#{offset}"
18
+ end
15
19
  end
@@ -12,6 +12,8 @@ module InsightsCloud
12
12
  setup_host_names(@hits_host_names.keys)
13
13
 
14
14
  replace_hits_data(hits)
15
+
16
+ InsightsRulesSync.perform_later
15
17
  end
16
18
 
17
19
  def logger
@@ -34,6 +36,20 @@ module InsightsCloud
34
36
  JSON.parse(hits_response)
35
37
  end
36
38
 
39
+ def query_insights_rules
40
+ rules_response = RestClient::Request.execute(
41
+ method: :get,
42
+ url: InsightsCloud.rules_url,
43
+ verify_ssl: ForemanRhCloud.verify_ssl_method,
44
+ proxy: ForemanRhCloud.transformed_http_proxy_string(logger: logger),
45
+ headers: {
46
+ Authorization: "Bearer #{rh_credentials}",
47
+ }
48
+ )
49
+
50
+ JSON.parse(rules_response)
51
+ end
52
+
37
53
  def setup_host_names(host_names)
38
54
  @host_ids = Hash[
39
55
  Host.unscoped.where(name: host_names).pluck(:name, :id)
@@ -46,6 +62,8 @@ module InsightsCloud
46
62
 
47
63
  def replace_hits_data(hits)
48
64
  InsightsHit.transaction do
65
+ # Reset hit counters to 0, they will be recreated later
66
+ InsightsFacet.unscoped.update_all(hits_count: 0)
49
67
  # create new facets for hosts that are missing one
50
68
  hosts_with_existing_facets = InsightsFacet.where(host_id: @host_ids.values).pluck(:host_id)
51
69
  InsightsFacet.create(
@@ -77,8 +95,19 @@ module InsightsCloud
77
95
  total_risk: hit_hash['total_risk'].to_i,
78
96
  likelihood: hit_hash['likelihood'].to_i,
79
97
  results_url: hit_hash['results_url'],
98
+ rule_id: to_rule_id(hit_hash['results_url']),
80
99
  }
81
100
  end
101
+
102
+ def to_rule_id(results_url)
103
+ URI.decode(safe_results_match(results_url)[:id] || '')
104
+ end
105
+
106
+ def safe_results_match(results_url)
107
+ match = results_url.match(/\/(?<id>[^\/]*)\/[^\/]*\/\z/)
108
+
109
+ match || { id: nil }
110
+ end
82
111
  end
83
112
  end
84
113
  end
@@ -0,0 +1,80 @@
1
+ require 'rest-client'
2
+
3
+ module InsightsCloud
4
+ module Async
5
+ class InsightsRulesSync < ::ApplicationJob
6
+ include ::ForemanRhCloud::CloudAuth
7
+
8
+ def perform
9
+ offset = 0
10
+ InsightsRule.transaction do
11
+ InsightsResolution.delete_all
12
+ InsightsRule.delete_all
13
+ loop do
14
+ api_response = query_insights_rules(offset)
15
+ results = RulesResult.new(api_response)
16
+ logger.debug("Downloaded #{offset + results.count} of #{results.total}")
17
+ write_rules_page(results.rules)
18
+ offset += results.count
19
+ break if offset >= results.total
20
+ end
21
+ end
22
+ end
23
+
24
+ def logger
25
+ Foreman::Logging.logger('background')
26
+ end
27
+
28
+ private
29
+
30
+ def query_insights_rules(offset)
31
+ rules_response = RestClient::Request.execute(
32
+ method: :get,
33
+ url: InsightsCloud.rules_url(offset: offset),
34
+ verify_ssl: ForemanRhCloud.verify_ssl_method,
35
+ proxy: ForemanRhCloud.transformed_http_proxy_string(logger: logger),
36
+ headers: {
37
+ Authorization: "Bearer #{rh_credentials}",
38
+ }
39
+ )
40
+
41
+ JSON.parse(rules_response)
42
+ end
43
+
44
+ def write_rules_page(rules)
45
+ rules_attributes = rules.map { |rule| to_rule_hash(rule) }
46
+ resolutions_attributes = rules.map do |rule|
47
+ rule['resolution_set'].map { |resolution| to_resolution_hash(rule['rule_id'], resolution) }
48
+ end.flatten
49
+
50
+ InsightsRule.create(rules_attributes)
51
+ InsightsResolution.create(resolutions_attributes)
52
+ end
53
+
54
+ def to_rule_hash(rule_hash)
55
+ {
56
+ rule_id: rule_hash['rule_id'],
57
+ description: rule_hash['description'],
58
+ category_name: rule_hash.dig('category', 'name'),
59
+ impact_name: rule_hash.dig('impact', 'name'),
60
+ summary: rule_hash['summary'],
61
+ generic: rule_hash['generic'],
62
+ reason: rule_hash['reason'],
63
+ total_risk: rule_hash['total_risk'],
64
+ reboot_required: rule_hash['reboot_required'],
65
+ more_info: rule_hash['more_info'],
66
+ rating: rule_hash['rating'],
67
+ }
68
+ end
69
+
70
+ def to_resolution_hash(rule_id, resolution_hash)
71
+ {
72
+ rule_id: rule_id,
73
+ system_type: resolution_hash['system_type'],
74
+ resolution: resolution_hash['resolution'],
75
+ has_playbook: resolution_hash['has_playbook'],
76
+ }
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,13 @@
1
+ module InsightsCloud
2
+ module Async
3
+ class RulesResult
4
+ attr_reader :rules, :count, :total
5
+
6
+ def initialize(result)
7
+ @total = result.dig('meta', 'count')
8
+ @count = result['data'].length
9
+ @rules = result['data']
10
+ end
11
+ end
12
+ end
13
+ end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foreman_rh_cloud",
3
- "version": "3.0.14",
3
+ "version": "3.0.17",
4
4
  "description": "Inventory Upload =============",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -0,0 +1,107 @@
1
+ require 'test_plugin_helper'
2
+
3
+ module InsightsCloud::Api
4
+ class MachineTelemetriesControllerTest < ActionController::TestCase
5
+ context '#forward_request' do
6
+ setup do
7
+ @body = 'Cloud response body'
8
+ @http_req = RestClient::Request.new(:method => 'GET', :url => 'http://test.theforeman.org')
9
+
10
+ org = FactoryBot.create(:organization)
11
+ host = FactoryBot.create(:host, :with_subscription, :organization => org)
12
+ User.current = ::Katello::CpConsumerUser.new(:uuid => host.subscription_facet.uuid, :login => host.subscription_facet.uuid)
13
+ InsightsCloud::Api::MachineTelemetriesController.any_instance.stubs(:upstream_owner).returns({ 'uuid' => 'abcdefg' })
14
+ end
15
+
16
+ test "should respond with response from cloud" do
17
+ net_http_resp = Net::HTTPResponse.new(1.0, 200, "OK")
18
+ net_http_resp.add_field 'Set-Cookie', 'Monster'
19
+ res = RestClient::Response.create(@body, net_http_resp, @http_req)
20
+ ::ForemanRhCloud::CloudRequestForwarder.any_instance.stubs(:forward_request).returns(res)
21
+
22
+ get :forward_request, params: { "path" => "platform/module-update-router/v1/channel" }
23
+ assert_equal @body, @response.body
24
+ end
25
+
26
+ test "should add headers to response from cloud" do
27
+ x_resource_count = '101'
28
+ x_rh_insights_request_id = '202'
29
+ net_http_resp = Net::HTTPResponse.new(1.0, 200, "OK")
30
+ net_http_resp['x_resource_count'] = x_resource_count
31
+ net_http_resp['x_rh_insights_request_id'] = x_rh_insights_request_id
32
+ res = RestClient::Response.create(@body, net_http_resp, @http_req)
33
+ ::ForemanRhCloud::CloudRequestForwarder.any_instance.stubs(:forward_request).returns(res)
34
+
35
+ get :forward_request, params: { "path" => "platform/module-update-router/v1/channel" }
36
+ assert_equal x_resource_count, @response.headers['x-resource-count']
37
+ assert_equal x_rh_insights_request_id, @response.headers['x_rh_insights_request_id']
38
+ end
39
+
40
+ test "should handle failed authentication to cloud" do
41
+ net_http_resp = Net::HTTPResponse.new(1.0, 401, "Unauthorized")
42
+ res = RestClient::Response.create(@body, net_http_resp, @http_req)
43
+ ::ForemanRhCloud::CloudRequestForwarder.any_instance.stubs(:forward_request).returns(res)
44
+
45
+ get :forward_request, params: { "path" => "platform/module-update-router/v1/channel" }
46
+ assert_equal 502, @response.status
47
+ assert_equal 'Authentication to the Insights Service failed.', JSON.parse(@response.body)['message']
48
+ end
49
+ end
50
+
51
+ context '#branch_info' do
52
+ setup do
53
+ User.current = User.find_by(login: 'secret_admin')
54
+
55
+ @env = FactoryBot.create(:katello_k_t_environment)
56
+ cv = @env.content_views << FactoryBot.create(:katello_content_view, organization: @env.organization)
57
+
58
+ @host = FactoryBot.create(
59
+ :host,
60
+ :with_subscription,
61
+ :with_content,
62
+ :with_hostgroup,
63
+ :with_parameter,
64
+ content_view: cv.first,
65
+ lifecycle_environment: @env,
66
+ organization: @env.organization
67
+ )
68
+
69
+ @host.subscription_facet.pools << FactoryBot.create(:katello_pool, account_number: '5678', cp_id: 1)
70
+
71
+ uuid = @host.subscription_facet.uuid
72
+
73
+ @user = ::Katello::CpConsumerUser.new({ :uuid => uuid, :login => uuid })
74
+
75
+ @controller.stub(:set_client_user, @user) do
76
+ User.current = @user
77
+ end
78
+
79
+ @controller.stubs(:cp_owner_id).returns('test-branch-id')
80
+ end
81
+
82
+ test 'should get branch info' do
83
+ get :branch_info
84
+
85
+ assert_response :success
86
+ end
87
+
88
+ test 'should handle missing organization' do
89
+ Host::Managed.any_instance.stubs(:organization).returns(nil)
90
+ get :branch_info
91
+
92
+ res = JSON.parse(@response.body)
93
+ assert_equal "Organization not found or invalid", res['message']
94
+ assert_response 400
95
+ end
96
+
97
+ test 'should handle missing branch id' do
98
+ @controller.stubs(:cp_owner_id).returns(nil)
99
+ get :branch_info
100
+
101
+ res = JSON.parse(@response.body)
102
+ assert_equal "Branch ID not found for organization #{@host.organization.title}", res['message']
103
+ assert_response 400
104
+ end
105
+ end
106
+ end
107
+ end
@@ -16,7 +16,8 @@ FactoryBot.define do
16
16
  total_risk { 1 }
17
17
  likelihood { 2 }
18
18
  publish_date { DateTime.now }
19
- sequence(:results_url) { |n| "http://example.com/results/#{n}" }
19
+ sequence(:results_url) { |n| "https://cloud.redhat.com/insights/overview/stability/test_rule%7CTEST_RULE/accdf444-5628-451d-bf3e-cf909ad7275#{n}/" }
20
+ rule_id { "test_rule|TEST_RULE" }
20
21
  end
21
22
  end
22
23
 
@@ -61,6 +61,18 @@ FactoryBot.define do
61
61
  end
62
62
  end
63
63
 
64
+ FactoryBot.define do
65
+ factory :katello_host_collection_host, :class => Katello::HostCollectionHosts do
66
+ host_id { nil }
67
+ host_collection_id { nil }
68
+ end
69
+
70
+ factory :katello_host_collection, :class => Katello::HostCollection do
71
+ sequence(:name) { |n| "Host Collection #{n}" }
72
+ organization_id { nil }
73
+ end
74
+ end
75
+
64
76
  FactoryBot.modify do
65
77
  factory :host do
66
78
  transient do
@@ -62,6 +62,23 @@ class InsightsFullSyncTest < ActiveJob::TestCase
62
62
  assert_equal 'accdf444-5628-451d-bf3e-cf909ad72757', @host2.insights.uuid
63
63
  end
64
64
 
65
+ test 'Hits counters are reset correctly' do
66
+ InsightsCloud::Async::InsightsFullSync.any_instance.expects(:query_insights_hits).returns(@hits).twice
67
+
68
+ InsightsCloud::Async::InsightsFullSync.perform_now()
69
+ # Invoke again
70
+ InsightsCloud::Async::InsightsFullSync.perform_now()
71
+
72
+ @host1.reload
73
+ @host2.reload
74
+
75
+ # Check that the counters are correct
76
+ assert_equal 2, @host1.insights.hits.count
77
+ assert_equal 1, @host2.insights.hits.count
78
+ assert_equal 'accdf444-5628-451d-bf3e-cf909ad72756', @host1.insights.uuid
79
+ assert_equal 'accdf444-5628-451d-bf3e-cf909ad72757', @host2.insights.uuid
80
+ end
81
+
65
82
  test 'Hits ignoring non-existent hosts' do
66
83
  hits_json = <<-HITS_JSON
67
84
  [
@@ -0,0 +1,198 @@
1
+ require 'test_helper'
2
+
3
+ class InsightsRulesSyncTest < ActiveJob::TestCase
4
+ setup do
5
+ rules_json = <<-'RULES_JSON'
6
+ {
7
+ "meta": {
8
+ "count": 2
9
+ },
10
+ "links": {
11
+ "first": "/api/insights/v1/rule/?has_playbook=true\u0026impacting=true\u0026limit=2\u0026offset=0\u0026rule_status=enabled",
12
+ "next": "/api/insights/v1/rule/?has_playbook=true\u0026impacting=true\u0026limit=2\u0026offset=2\u0026rule_status=enabled",
13
+ "previous": "/api/insights/v1/rule/?has_playbook=true\u0026impacting=true\u0026limit=2\u0026offset=0\u0026rule_status=enabled",
14
+ "last": "/api/insights/v1/rule/?has_playbook=true\u0026impacting=true\u0026limit=2\u0026offset=1\u0026rule_status=enabled"
15
+ },
16
+ "data": [
17
+ {
18
+ "rule_id": "test_rule|TEST_RULE",
19
+ "created_at": "2020-09-25T07:37:09.745864Z",
20
+ "updated_at": "2020-10-23T02:43:09.662260Z",
21
+ "description": "Kdump auto crashkernel reservation failed because the total memory does not meet the minimum requirement",
22
+ "active": true,
23
+ "category": {
24
+ "id": 1,
25
+ "name": "Availability"
26
+ },
27
+ "impact": {
28
+ "name": "Diagnostics Failure",
29
+ "impact": 1
30
+ },
31
+ "likelihood": 4,
32
+ "node_id": "1186753",
33
+ "tags": "incident kdump",
34
+ "playbook_count": 1,
35
+ "reboot_required": false,
36
+ "publish_date": "2020-10-01T19:04:00Z",
37
+ "summary": "Kdump fails to collect vmcore when the reserved size does not meet the minimum requirement.\n",
38
+ "generic": "Automatic reservation did not work because the total memory does not meet the minimum requirement for kdump.\n",
39
+ "reason": "{{?pydata.error_key == \"CRASHKERNEL_RECOMMENDED_VALUE_V2\"}}\nThis **{{=pydata.value[3]}}** system is running with a total memory of **{{=pydata.value[0]}}MB**. The kdump reserved size is **{{=pydata.value[1]}}MB** while the minimum amount of reserved memory required for memory **{{=pydata.value[0]}}MB** is **{{=pydata.value[2]}}MB**. \n\n{{?pydata.version == 7}}\n* [Minimum Amount of Reserved Memory Required for kdump](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/kernel_administration_guide/kernel_crash_dump_guide#sect-kdump-memory-requirements)\n{{?}}\n{{?pydata.version == 8}}\n* [Minimum Amount of Reserved Memory Required for kdump](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/managing_monitoring_and_updating_the_kernel/installing-and-configuring-kdump_managing-monitoring-and-updating-the-kernel#memory-requirements-for-kdump_supported-kdump-configurations-and-targets)\n{{?}}\n{{?}}\n\n{{?pydata.error_key == \"CRASHKERNEL_AUTO_FAILURE_V2\"}}\nThis **{{=pydata.value[2]}}** system is running with a total memory of **{{=pydata.value[0]}}MB**. The **crashkernel** is configured as **auto** and the minimum amount of memory required for automatic memory reservation is **{{=pydata.auto_minimum}}MB**. As the total memory does not meet the requirement, kdump auto crashkernel reservation failed with following log:\n ~~~\n {{=pydata.msg}}\n ~~~\n\n{{?pydata.version == 7}}\n* [Minimum Amount of Memory Required for Automatic Memory Reservation](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/kernel_administration_guide/kernel_crash_dump_guide#sect-kdump-memory-thresholds)\n\n* [Minimum Amount of Reserved Memory Required for kdump](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/kernel_administration_guide/kernel_crash_dump_guide#sect-kdump-memory-requirements)\n{{?}}\n{{?pydata.version == 8}}\n* [Minimum Amount of Memory Required for Automatic Memory Reservation](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/managing_monitoring_and_updating_the_kernel/installing-and-configuring-kdump_managing-monitoring-and-updating-the-kernel#minimum-threshold-for-automatic-memory-reservation_supported-kdump-configurations-and-targets)\n\n* [Minimum Amount of Reserved Memory Required for kdump](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/managing_monitoring_and_updating_the_kernel/installing-and-configuring-kdump_managing-monitoring-and-updating-the-kernel#memory-requirements-for-kdump_supported-kdump-configurations-and-targets)\n{{?}}\n{{?}}\n",
40
+ "more_info": "{{?pydata.version == 7}}\n* [How should the crashkernel parameter be configured for using kdump on RHEL7 ?](https://access.redhat.com/solutions/916043)\n{{?}}\n{{?pydata.version == 8}}\n* [How should the crashkernel parameter be configured for using kdump on Red Hat Enterprise Linux 8 ?](https://access.redhat.com/solutions/3698411)\n{{?}}\n",
41
+ "impacted_systems_count": 2,
42
+ "reports_shown": true,
43
+ "rule_status": "enabled",
44
+ "resolution_set": [{
45
+ "system_type": 105,
46
+ "resolution": "Red Hat recommends that you change the **crashkernel** setting in the `grub.conf` file.\n\n{{?pydata.error_key == \"CRASHKERNEL_RECOMMENDED_VALUE_V2\"}}\n1. Update the value of the **\"crashkernel\"** kernel parameter:\n ~~~\n # grubby --update-kernel=ALL --args=crashkernel={{=pydata.value[2]}}M\n ~~~\n1. Reboot the system:\n ~~~\n # reboot\n ~~~\n{{?}}\n\n\n{{?pydata.error_key == \"CRASHKERNEL_AUTO_FAILURE_V2\"}}\n1. Update the value of the **\"crashkernel\"** kernel parameter:\n ~~~\n # grubby --update-kernel=ALL --args=crashkernel={{=pydata.value[1]}}M\n ~~~\n1. Reboot the system:\n ~~~\n # reboot\n ~~~\n{{?}}\n",
47
+ "resolution_risk": {
48
+ "name": "Update Service Configuration",
49
+ "risk": 1
50
+ },
51
+ "has_playbook": true
52
+ },
53
+ {
54
+ "system_type": 105,
55
+ "resolution": "Red Hat recommends that you change the **crashkernel** setting in the `grub.conf` file.\n\n{{?pydata.error_key == \"CRASHKERNEL_RECOMMENDED_VALUE_V2\"}}\n1. Update the value of the **\"crashkernel\"** kernel parameter:\n ~~~\n # grubby --update-kernel=ALL --args=crashkernel={{=pydata.value[2]}}M\n ~~~\n1. Reboot the system:\n ~~~\n # reboot\n ~~~\n{{?}}\n\n\n{{?pydata.error_key == \"CRASHKERNEL_AUTO_FAILURE_V2\"}}\n1. Update the value of the **\"crashkernel\"** kernel parameter:\n ~~~\n # grubby --update-kernel=ALL --args=crashkernel={{=pydata.value[1]}}M\n ~~~\n1. Reboot the system:\n ~~~\n # reboot\n ~~~\n{{?}}\n",
56
+ "resolution_risk": {
57
+ "name": "test resolution name",
58
+ "risk": 2
59
+ },
60
+ "has_playbook": true
61
+ }],
62
+ "total_risk": 2,
63
+ "hosts_acked_count": 0,
64
+ "rating": 0
65
+ }, {
66
+ "rule_id": "test_rule2|TEST_RULE2",
67
+ "created_at": "2019-02-07T19:02:34.501496Z",
68
+ "updated_at": "2020-08-10T20:42:48.967063Z",
69
+ "description": "\"SMBLoris\" Samba denial of service",
70
+ "active": true,
71
+ "category": {
72
+ "id": 2,
73
+ "name": "Security"
74
+ },
75
+ "impact": {
76
+ "name": "Denial Of Service",
77
+ "impact": 3
78
+ },
79
+ "likelihood": 3,
80
+ "node_id": "",
81
+ "tags": "samba security",
82
+ "playbook_count": 1,
83
+ "reboot_required": false,
84
+ "publish_date": "2017-08-08T15:52:38Z",
85
+ "summary": "A denial of service flaw exists in Samba. The vulnerability has not been assigned a CVE.\n\nA remote attacker in a position to supply the crafted data can render system unusable.\n",
86
+ "generic": "A denial of service flaw exists in Samba that allows a remote attacker to supply crafted NetBIOS Session Service headers and cause out-of-memory state.\n\nRed Hat recommends that you apply mitigation or update Samba packages once fixed packages are available.\n",
87
+ "reason": "This machine is vulnerable because:\n\n* It has the following vulnerable Samba server package installed: **{{=pydata.INSTALLED_PACKAGES[Object.keys(pydata.INSTALLED_PACKAGES)[0]]}}**\n{{? pydata.SERVICE_RUNNING }}* An `smbd` process is running.\n{{?}}{{? pydata.SERVICE_ENABLED }}* The `smb` service is enabled.\n{{?}}{{? pydata.assuming_port_listening }}* The system {{? pydata.port_listening }}is{{??}}may be{{?}} listening on port 445.\n{{?}}{{? pydata.assuming_port_reachable }}* Port 445 appears to allow external connections.\n{{?}}\n\nBased on this information, a vulnerable version of Samba is being actively used on this machine.\n\n{{? !pydata.iptables_info_available || !pydata.netstat_info_available }}\nBecause information about this system's {{? !pydata.iptables_info_available }} firewall {{? !pydata.netstat_info_available }}and{{?}}{{?}} {{? !pydata.netstat_info_available }}listening services {{?}}is not available, it is not possible to determine this system's exact level of vulnerability.\n{{?}}\n\n{{? !pydata.samba_config_info }}\nBecause information about this system's smb.conf is not available, it is not possible to determine whether the mitigation is applied.\n{{?}}\n",
88
+ "more_info": "* For more information about the denial of service flaw see [knowledge base article](https://access.redhat.com/security/vulnerabilities/smbloris).\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934).\"\n* [Using Firewalls](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/Security_Guide/sec-Using_Firewalls.html) in RHEL 7\n* [Using Firewalls](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Security_Guide/sect-Security_Guide-Firewalls.html) in RHEL 6\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).\n",
89
+ "impacted_systems_count": 1,
90
+ "reports_shown": true,
91
+ "rule_status": "enabled",
92
+ "resolution_set": [{
93
+ "system_type": 105,
94
+ "resolution": "{{ var FIXED_PACKAGES_AVAILABLE=false; }}\n{{? false == FIXED_PACKAGES_AVAILABLE \u0026\u0026 \"Frontend until fixed packages are available.\"}}\nRed Hat recommends that you use the following mitigation:\n\nLimit the number of smbd processes by modifying `/etc/samba/smb.conf` and add the following line to the `[global]` section:\n~~~\nmax smbd processes = 1000\n~~~\n\nThen restart Samba service:\n{{? pydata.rhel_version == 6 }}\n~~~\n# service smb restart\n~~~\n{{?}}\n{{? pydata.rhel_version == 7 }}\n~~~\n# systemctl restart smb.service\n~~~\n{{?}}\n\n{{? pydata.assuming_port_reachable }}\nRed Hat recommends that you limit access to port 445 from untrusted sources. Please refer to the documentation of the firewall you use.\n\n{{? !pydata.iptables_info_available || !pydata.netstat_info_available }}\nBecause information about this system's {{? !pydata.iptables_info_available }} firewall {{? !pydata.netstat_info_available }}and{{?}}{{?}} {{? !pydata.netstat_info_available }}listening services {{?}}is not available, it is not possible to determine whether this system's port 445 is accessible.\n\n{{?}}\nRed Hat recommends that you test the new settings before applying them to production systems.\n{{?}}\n{{?}}\n{{? true == FIXED_PACKAGES_AVAILABLE \u0026\u0026 \"Frontend after fixed packages are available.\"}}\nRed Hat recommends that you update Samba packages to include the CVE-2017-TODO security release, and restart the Samba service.\n\n{{? pydata.rhel_version == 6 }}\n~~~\n# yum update {{=Object.keys(pydata.INSTALLED_PACKAGES).join(\" \")}}\n# service smb restart\n~~~\n{{?}}\n{{? pydata.rhel_version == 7 }}\n~~~\n# yum update {{=Object.keys(pydata.INSTALLED_PACKAGES).join(\" \")}}\n# systemctl restart smb.service\n~~~\n{{?}}\n\n**Alternatively**, you can use the following mitigation:\n\nLimit the number of smbd processes by modifying `/etc/samba/smb.conf` and add the following line to the `[global]` section:\n~~~\nmax smbd processes = 1000\n~~~\n\nThen restart Samba service:\n{{? pydata.rhel_version == 6 }}\n~~~\n# service smb restart\n~~~\n{{?}}\n{{? pydata.rhel_version == 7 }}\n~~~\n# systemctl restart smb.service\n~~~\n{{?}}\n\n{{? pydata.assuming_port_reachable }}\nRed Hat recommends that you limit access to port 445 from untrusted sources. Please refer to the documentation of the firewall you use.\n\n{{? !pydata.iptables_info_available || !pydata.netstat_info_available }}\nBecause information about this system's {{? !pydata.iptables_info_available }} firewall {{? !pydata.netstat_info_available }}and{{?}}{{?}} {{? !pydata.netstat_info_available }}listening services {{?}}is not available, it is not possible to determine whether this system's port 445 is accessible.\n\n{{?}}\nRed Hat recommends that you test the new settings before applying them to production systems.\n{{?}}\n\n\n{{?}}\n",
95
+ "resolution_risk": {
96
+ "name": "Update Service Configuration",
97
+ "risk": 1
98
+ },
99
+ "has_playbook": true
100
+ }],
101
+ "total_risk": 3,
102
+ "hosts_acked_count": 0,
103
+ "rating": 0
104
+ }]
105
+ }
106
+ RULES_JSON
107
+ @rules = JSON.parse(rules_json)
108
+ @host = FactoryBot.create(:host, :managed, name: 'host1')
109
+ @hit = FactoryBot.create(:insights_hit, host_id: @host.id)
110
+ end
111
+
112
+ test 'Hits data is replaced with data from cloud' do
113
+ InsightsCloud::Async::InsightsRulesSync.any_instance.expects(:query_insights_rules).returns(@rules)
114
+
115
+ InsightsCloud::Async::InsightsRulesSync.perform_now()
116
+ @hit.reload
117
+
118
+ assert_equal 2, InsightsRule.all.count
119
+ assert_not_nil @hit.rule
120
+ assert_equal true, @hit.has_playbook?
121
+ end
122
+
123
+ test 'Rules queries support pagination' do
124
+ last_rule_json = <<-'RULES_JSON'
125
+ {
126
+ "meta": {
127
+ "count": 1
128
+ },
129
+ "links": {
130
+ "first": "/api/insights/v1/rule/?has_playbook=true\u0026impacting=true\u0026limit=2\u0026offset=0\u0026rule_status=enabled",
131
+ "next": "/api/insights/v1/rule/?has_playbook=true\u0026impacting=true\u0026limit=2\u0026offset=2\u0026rule_status=enabled",
132
+ "previous": "/api/insights/v1/rule/?has_playbook=true\u0026impacting=true\u0026limit=2\u0026offset=0\u0026rule_status=enabled",
133
+ "last": "/api/insights/v1/rule/?has_playbook=true\u0026impacting=true\u0026limit=2\u0026offset=1\u0026rule_status=enabled"
134
+ },
135
+ "data": [
136
+ {
137
+ "rule_id": "crashkernel_reservation_value|CRASHKERNEL_AUTO_FAILURE_V2",
138
+ "created_at": "2020-09-25T07:37:09.745864Z",
139
+ "updated_at": "2020-10-23T02:43:09.662260Z",
140
+ "description": "Kdump auto crashkernel reservation failed because the total memory does not meet the minimum requirement",
141
+ "active": true,
142
+ "category": {
143
+ "id": 1,
144
+ "name": "Availability"
145
+ },
146
+ "impact": {
147
+ "name": "Diagnostics Failure",
148
+ "impact": 1
149
+ },
150
+ "likelihood": 4,
151
+ "node_id": "1186753",
152
+ "tags": "incident kdump",
153
+ "playbook_count": 1,
154
+ "reboot_required": false,
155
+ "publish_date": "2020-10-01T19:04:00Z",
156
+ "summary": "Kdump fails to collect vmcore when the reserved size does not meet the minimum requirement.\n",
157
+ "generic": "Automatic reservation did not work because the total memory does not meet the minimum requirement for kdump.\n",
158
+ "reason": "{{?pydata.error_key == \"CRASHKERNEL_RECOMMENDED_VALUE_V2\"}}\nThis **{{=pydata.value[3]}}** system is running with a total memory of **{{=pydata.value[0]}}MB**. The kdump reserved size is **{{=pydata.value[1]}}MB** while the minimum amount of reserved memory required for memory **{{=pydata.value[0]}}MB** is **{{=pydata.value[2]}}MB**. \n\n{{?pydata.version == 7}}\n* [Minimum Amount of Reserved Memory Required for kdump](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/kernel_administration_guide/kernel_crash_dump_guide#sect-kdump-memory-requirements)\n{{?}}\n{{?pydata.version == 8}}\n* [Minimum Amount of Reserved Memory Required for kdump](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/managing_monitoring_and_updating_the_kernel/installing-and-configuring-kdump_managing-monitoring-and-updating-the-kernel#memory-requirements-for-kdump_supported-kdump-configurations-and-targets)\n{{?}}\n{{?}}\n\n{{?pydata.error_key == \"CRASHKERNEL_AUTO_FAILURE_V2\"}}\nThis **{{=pydata.value[2]}}** system is running with a total memory of **{{=pydata.value[0]}}MB**. The **crashkernel** is configured as **auto** and the minimum amount of memory required for automatic memory reservation is **{{=pydata.auto_minimum}}MB**. As the total memory does not meet the requirement, kdump auto crashkernel reservation failed with following log:\n ~~~\n {{=pydata.msg}}\n ~~~\n\n{{?pydata.version == 7}}\n* [Minimum Amount of Memory Required for Automatic Memory Reservation](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/kernel_administration_guide/kernel_crash_dump_guide#sect-kdump-memory-thresholds)\n\n* [Minimum Amount of Reserved Memory Required for kdump](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/kernel_administration_guide/kernel_crash_dump_guide#sect-kdump-memory-requirements)\n{{?}}\n{{?pydata.version == 8}}\n* [Minimum Amount of Memory Required for Automatic Memory Reservation](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/managing_monitoring_and_updating_the_kernel/installing-and-configuring-kdump_managing-monitoring-and-updating-the-kernel#minimum-threshold-for-automatic-memory-reservation_supported-kdump-configurations-and-targets)\n\n* [Minimum Amount of Reserved Memory Required for kdump](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/managing_monitoring_and_updating_the_kernel/installing-and-configuring-kdump_managing-monitoring-and-updating-the-kernel#memory-requirements-for-kdump_supported-kdump-configurations-and-targets)\n{{?}}\n{{?}}\n",
159
+ "more_info": "{{?pydata.version == 7}}\n* [How should the crashkernel parameter be configured for using kdump on RHEL7 ?](https://access.redhat.com/solutions/916043)\n{{?}}\n{{?pydata.version == 8}}\n* [How should the crashkernel parameter be configured for using kdump on Red Hat Enterprise Linux 8 ?](https://access.redhat.com/solutions/3698411)\n{{?}}\n",
160
+ "impacted_systems_count": 2,
161
+ "reports_shown": true,
162
+ "rule_status": "enabled",
163
+ "resolution_set": [{
164
+ "system_type": 105,
165
+ "resolution": "Red Hat recommends that you change the **crashkernel** setting in the `grub.conf` file.\n\n{{?pydata.error_key == \"CRASHKERNEL_RECOMMENDED_VALUE_V2\"}}\n1. Update the value of the **\"crashkernel\"** kernel parameter:\n ~~~\n # grubby --update-kernel=ALL --args=crashkernel={{=pydata.value[2]}}M\n ~~~\n1. Reboot the system:\n ~~~\n # reboot\n ~~~\n{{?}}\n\n\n{{?pydata.error_key == \"CRASHKERNEL_AUTO_FAILURE_V2\"}}\n1. Update the value of the **\"crashkernel\"** kernel parameter:\n ~~~\n # grubby --update-kernel=ALL --args=crashkernel={{=pydata.value[1]}}M\n ~~~\n1. Reboot the system:\n ~~~\n # reboot\n ~~~\n{{?}}\n",
166
+ "resolution_risk": {
167
+ "name": "Update Service Configuration",
168
+ "risk": 1
169
+ },
170
+ "has_playbook": true
171
+ },
172
+ {
173
+ "system_type": 105,
174
+ "resolution": "Red Hat recommends that you change the **crashkernel** setting in the `grub.conf` file.\n\n{{?pydata.error_key == \"CRASHKERNEL_RECOMMENDED_VALUE_V2\"}}\n1. Update the value of the **\"crashkernel\"** kernel parameter:\n ~~~\n # grubby --update-kernel=ALL --args=crashkernel={{=pydata.value[2]}}M\n ~~~\n1. Reboot the system:\n ~~~\n # reboot\n ~~~\n{{?}}\n\n\n{{?pydata.error_key == \"CRASHKERNEL_AUTO_FAILURE_V2\"}}\n1. Update the value of the **\"crashkernel\"** kernel parameter:\n ~~~\n # grubby --update-kernel=ALL --args=crashkernel={{=pydata.value[1]}}M\n ~~~\n1. Reboot the system:\n ~~~\n # reboot\n ~~~\n{{?}}\n",
175
+ "resolution_risk": {
176
+ "name": "test resolution name",
177
+ "risk": 2
178
+ },
179
+ "has_playbook": true
180
+ }],
181
+ "total_risk": 2,
182
+ "hosts_acked_count": 0,
183
+ "rating": 0
184
+ }]
185
+ }
186
+ RULES_JSON
187
+ @last_rule = JSON.parse(last_rule_json)
188
+
189
+ @rules['meta']['count'] = 3
190
+
191
+ InsightsCloud::Async::InsightsRulesSync.any_instance.
192
+ stubs(:query_insights_rules).returns(@rules).then.returns(@last_rule)
193
+
194
+ InsightsCloud::Async::InsightsRulesSync.perform_now()
195
+
196
+ assert_equal 3, InsightsRule.all.count
197
+ end
198
+ end