diff --git a/Gemfile b/Gemfile index 94bcce25f0e0..a54ffb26707e 100644 --- a/Gemfile +++ b/Gemfile @@ -63,7 +63,7 @@ gem "pg", :require => false gem "pg-dsn_parser", "~>0.1.0", :require => false gem "query_relation", "~>0.1.0", :require => false gem "rack-attack", "~>6.5.0", :require => false -gem "rails", "~>6.0.4", ">=6.0.4.6" +gem "rails", "~>6.0.4", ">=6.0.4.7" gem "rails-i18n", "~>6.x" gem "rake", ">=12.3.3", :require => false gem "rest-client", "~>2.1.0", :require => false diff --git a/app/models/aliases/ems_automation.rb b/app/models/aliases/ems_automation.rb index 5580892ba7e2..000262634b82 100644 --- a/app/models/aliases/ems_automation.rb +++ b/app/models/aliases/ems_automation.rb @@ -1 +1 @@ -::EmsAutomation = ::ManageIQ::Providers::AnsibleTower::AutomationManager +::EmsAutomation = ::ManageIQ::Providers::ExternalAutomationManager diff --git a/app/models/cloud_database.rb b/app/models/cloud_database.rb index c0f3a74ce778..0a9d4c3e5846 100644 --- a/app/models/cloud_database.rb +++ b/app/models/cloud_database.rb @@ -1,6 +1,7 @@ class CloudDatabase < ApplicationRecord include NewWithTypeStiMixin include ProviderObjectMixin + include SupportsFeatureMixin belongs_to :ext_management_system, :foreign_key => :ems_id, :class_name => "ManageIQ::Providers::CloudManager" belongs_to :cloud_tenant @@ -8,4 +9,92 @@ class CloudDatabase < ApplicationRecord belongs_to :resource_group serialize :extra_attributes + + supports_not :create + supports_not :delete + supports_not :update + + def self.create_cloud_database_queue(userid, ext_management_system, options = {}) + task_opts = { + :action => "creating Cloud Database for user #{userid}", + :userid => userid + } + + queue_opts = { + :class_name => name, + :method_name => 'create_cloud_database', + :role => 'ems_operations', + :queue_name => ext_management_system.queue_name_for_ems_operations, + :zone => ext_management_system.my_zone, + :args => [ext_management_system.id, options] + } + + MiqTask.generic_action_with_callback(task_opts, queue_opts) + end + + def self.create_cloud_database(ems_id, options = {}) + raise ArgumentError, _("ems_id cannot be nil") if ems_id.nil? + + ext_management_system = ExtManagementSystem.find(ems_id) + klass = ext_management_system.class_by_ems(:CloudDatabase) + klass.raw_create_cloud_database(ext_management_system, options) + end + + def self.raw_create_cloud_database(_ext_management_system, _options = {}) + raise NotImplementedError, _("raw_create_cloud_database must be implemented in a subclass") + end + + def delete_cloud_database_queue(userid) + task_opts = { + :action => "deleting Cloud Database for user #{userid}", + :userid => userid + } + + queue_opts = { + :class_name => self.class.name, + :method_name => 'delete_cloud_database', + :instance_id => id, + :role => 'ems_operations', + :queue_name => ext_management_system.queue_name_for_ems_operations, + :zone => ext_management_system.my_zone, + :args => [] + } + + MiqTask.generic_action_with_callback(task_opts, queue_opts) + end + + def delete_cloud_database + raw_delete_cloud_database + end + + def raw_delete_cloud_database + raise NotImplementedError, _("raw_delete_cloud_database must be implemented in a subclass") + end + + def update_cloud_database_queue(userid, options = {}) + task_opts = { + :action => "updating Cloud Database for user #{userid}", + :userid => userid + } + + queue_opts = { + :class_name => self.class.name, + :method_name => 'update_cloud_database', + :instance_id => id, + :role => 'ems_operations', + :queue_name => ext_management_system.queue_name_for_ems_operations, + :zone => ext_management_system.my_zone, + :args => [options] + } + + MiqTask.generic_action_with_callback(task_opts, queue_opts) + end + + def update_cloud_database(options = {}) + raw_update_cloud_database(options) + end + + def raw_update_cloud_database(_options = {}) + raise NotImplementedError, _("raw_update_cloud_database must be implemented in a subclass") + end end diff --git a/app/models/cloud_volume.rb b/app/models/cloud_volume.rb index 2349618f5775..66bd201d5ca2 100644 --- a/app/models/cloud_volume.rb +++ b/app/models/cloud_volume.rb @@ -189,4 +189,8 @@ def raw_safe_delete_volume def available_vms raise NotImplementedError, _("available_vms must be implemented in a subclass") end + + def create_volume_snapshot_queue(userid, options = {}) + ext_management_system.class_by_ems(:CloudVolumeSnapshot)&.create_snapshot_queue(userid, self, options) + end end diff --git a/app/models/cloud_volume_snapshot.rb b/app/models/cloud_volume_snapshot.rb index 3a11ba436058..4614913d4335 100644 --- a/app/models/cloud_volume_snapshot.rb +++ b/app/models/cloud_volume_snapshot.rb @@ -26,6 +26,63 @@ def my_zone self.class.my_zone(ext_management_system) end + def self.create_snapshot_queue(userid, cloud_volume, options = {}) + raise ArgumentError, "Must provide a cloud volume with a provider" if cloud_volume&.ext_management_system.nil? + + ext_management_system = cloud_volume.ext_management_system + task_opts = { + :action => "creating volume snapshot in #{ext_management_system.inspect} for #{cloud_volume.inspect} with #{options.inspect}", + :userid => userid + } + + queue_opts = { + :class_name => cloud_volume.class.name, + :instance_id => cloud_volume.id, + :method_name => 'create_volume_snapshot', + :role => 'ems_operations', + :queue_name => ext_management_system.queue_name_for_ems_operations, + :zone => my_zone(ext_management_system), + :args => [options] + } + + MiqTask.generic_action_with_callback(task_opts, queue_opts) + end + + def self.create_snapshot(cloud_volume, options) + raw_create_snapshot(cloud_volume, options) + end + + def self.raw_create_snapshot(_cloud_volume, _options) + raise NotImplementedError, _("raw_create_snapshot must be implemented in a subclass") + end + + def update_snapshot_queue(userid = "system", options = {}) + task_opts = { + :action => "updating volume snapshot #{inspect} in #{ext_management_system.inspect} with #{options.inspect}", + :userid => userid + } + + queue_opts = { + :class_name => self.class.name, + :instance_id => id, + :method_name => 'update_snapshot', + :role => 'ems_operations', + :queue_name => ext_management_system.queue_name_for_ems_operations, + :zone => my_zone, + :args => [options] + } + + MiqTask.generic_action_with_callback(task_opts, queue_opts) + end + + def update_snapshot(options = {}) + raw_update_snapshot(options) + end + + def raw_update_snapshot(_options = {}) + raise NotImplementedError, _("update_snapshot must be implemented in a subclass") + end + # Delete a cloud volume snapshot as a queued task and return the task id. The # queue name and the queue zone are derived from the EMS. The userid is # optional and defaults to 'system'. @@ -42,7 +99,6 @@ def delete_snapshot_queue(userid = "system", _options = {}) :class_name => self.class.name, :instance_id => id, :method_name => 'delete_snapshot', - :priority => MiqQueue::HIGH_PRIORITY, :role => 'ems_operations', :queue_name => ext_management_system.queue_name_for_ems_operations, :zone => my_zone, diff --git a/app/models/manageiq/providers/inventory/persister/builder/physical_infra_manager.rb b/app/models/manageiq/providers/inventory/persister/builder/physical_infra_manager.rb index 94c26cf56d5c..261b6b7bb84e 100644 --- a/app/models/manageiq/providers/inventory/persister/builder/physical_infra_manager.rb +++ b/app/models/manageiq/providers/inventory/persister/builder/physical_infra_manager.rb @@ -14,6 +14,13 @@ def physical_chassis add_common_default_values end + def physical_disks + add_properties( + :manager_ref => %i[physical_storage ems_ref], + :parent_inventory_collections => %i[physical_storages] + ) + end + def physical_storages add_common_default_values end @@ -68,6 +75,30 @@ def physical_chassis_hardwares add_hardware_properties(:computer_system, :physical_chassis) end + def physical_server_network_ports + add_properties( + :model_class => ::PhysicalNetworkPort, + :manager_ref => %i[port_type uid_ems], + :parent_inventory_collections => %i[physical_servers] + ) + end + + def physical_storage_network_ports + add_properties( + :model_class => ::PhysicalNetworkPort, + :manager_ref => %i[port_type port_name guest_device], + :parent_inventory_collections => %i[physical_storages] + ) + end + + def physical_switch_network_ports + add_properties( + :model_class => ::PhysicalNetworkPort, + :manager_ref => %i[port_type port_name physical_switch], + :parent_inventory_collections => %i[physical_switches] + ) + end + def physical_storage_hardwares add_hardware_properties(:computer_system, :physical_storages) end diff --git a/app/models/miq_remote_console_worker.rb b/app/models/miq_remote_console_worker.rb index ac634dc72067..b6fc146856f8 100644 --- a/app/models/miq_remote_console_worker.rb +++ b/app/models/miq_remote_console_worker.rb @@ -16,4 +16,20 @@ def friendly_name def self.kill_priority MiqWorkerType::KILL_PRIORITY_REMOTE_CONSOLE_WORKERS end + + def container_port + 3001 + end + + def configure_service_worker_deployment(definition) + super + + definition[:spec][:template][:spec][:containers].first[:volumeMounts] << {:name => "remote-console-httpd-config", :mountPath => "/etc/httpd/conf.d"} + definition[:spec][:template][:spec][:volumes] << {:name => "remote-console-httpd-config", :configMap => {:name => "remote-console-httpd-configs", :defaultMode => 420}} + + if ENV["REMOTE_CONSOLE_SSL_SECRET_NAME"].present? + definition[:spec][:template][:spec][:containers].first[:volumeMounts] << {:name => "remote-console-httpd-ssl", :mountPath => "/etc/pki/tls"} + definition[:spec][:template][:spec][:volumes] << {:name => "remote-console-httpd-ssl", :secret => {:secretName => ENV["REMOTE_CONSOLE_SSL_SECRET_NAME"], :items => [{:key => "remote_console_crt", :path => "certs/server.crt"}, {:key => "remote_console_key", :path => "private/server.key"}], :defaultMode => 400}} + end + end end diff --git a/config/initializers/secure_headers.rb b/config/initializers/secure_headers.rb index 6725a1edad58..15ca44a72bb1 100644 --- a/config/initializers/secure_headers.rb +++ b/config/initializers/secure_headers.rb @@ -12,13 +12,15 @@ # X-Permitted-Cross-Domain-Policies config.x_xss_protection = "1; mode=block" # Content-Security-Policy + # Need google fonts in fonts_src for https://fonts.googleapis.com/css?family=IBM+Plex+Sans+Condensed%7CIBM+Plex+Sans:400,600&display=swap (For carbon-charts download) config.csp = { :report_only => false, :default_src => ["'self'"], :frame_src => ["'self'"], - :font_src => ["'self'", 'https://fonts.gstatic.com'], + :font_src => ["'self'", 'https://fonts.gstatic.com', "https://fonts.googleapis.com"], + :img_src => ["'self'", "data:"], :connect_src => ["'self'"], - :style_src => ["'unsafe-inline'", "'self'"], + :style_src => ["'unsafe-inline'", "'self'", "https://fonts.googleapis.com", "https://fonts.gstatic.com"], :script_src => ["'unsafe-eval'", "'unsafe-inline'", "'self'"], :report_uri => ["/dashboard/csp_report"] } diff --git a/lib/remote_console/rack_server.rb b/lib/remote_console/rack_server.rb index 37536709be2b..2372a9947808 100644 --- a/lib/remote_console/rack_server.rb +++ b/lib/remote_console/rack_server.rb @@ -27,8 +27,9 @@ module RemoteConsole class RackServer attr_accessor :logger - RACK_404 = [404, {'Content-Type' => 'text/plain'}, ['Not found']].freeze - RACK_YAY = [-1, {}, []].freeze + RACK_404 = [404, {'Content-Type' => 'text/plain'}, ['Not found']].freeze + RACK_PONG = [200, {'Content-Type' => 'text/plain'}, ['pong']].freeze + RACK_YAY = [-1, {}, []].freeze def initialize(options = {}) @logger = options.fetch(:logger, $remote_console_log) @@ -57,10 +58,12 @@ def initialize(options = {}) # Rack entrypoint def call(env) - exp = %r{^/ws/console/([a-zA-Z0-9]+)/?$}.match(env['REQUEST_URI']) + exp = env['REQUEST_URI'].to_s.match(%r{^/ws/console/([a-zA-Z0-9]+)/?$}) if WebSocket::Driver.websocket?(env) && same_origin_as_host?(env) && exp.present? @logger.info("RemoteConsole connection initiated") init_proxy(env, exp[1]) + elsif env['REQUEST_URI'].to_s.match?(%r{^/ping$}) + RACK_PONG else @logger.error('Invalid RemoteConsole request or URL') RACK_404 diff --git a/locale/en.yml b/locale/en.yml index 0190426f2d4d..3f9b0fbdbcd9 100644 --- a/locale/en.yml +++ b/locale/en.yml @@ -1464,6 +1464,7 @@ en: LoadBalancer: Load Balancer ManageIQ::Providers::AutomationManager: Automation Manager ManageIQ::Providers::BaseManager: Provider + ManageIQ::Providers::EmbeddedAutomationManager: Embedded Automation Manager ManageIQ::Providers::EmbeddedAutomationManager::Authentication: Credential ManageIQ::Providers::EmbeddedAutomationManager::ConfigurationScriptSource: Repository ManageIQ::Providers::EmbeddedAnsible::AutomationManager::AmazonCredential: Credential (Amazon) @@ -1480,6 +1481,7 @@ en: ManageIQ::Providers::EmbeddedAnsible::AutomationManager::VmwareCredential: Credential (VMware) ManageIQ::Providers::EmbeddedAnsible::AutomationManager::Playbook: Playbook (Embedded Ansible) ManageIQ::Providers::EmbeddedAnsible::AutomationManager::ConfigurationScriptSource: Repository (Embedded Ansible) + ManageIQ::Providers::ExternalAutomationManager: Automation Manager ManageIQ::Providers::AutomationManager::Authentication: Credential ManageIQ::Providers::AnsibleTower::AutomationManager::AmazonCredential: Credential (Amazon) ManageIQ::Providers::AnsibleTower::AutomationManager::AzureCredential: Credential (Microsoft Azure) diff --git a/spec/lib/remote_console/rack_server_spec.rb b/spec/lib/remote_console/rack_server_spec.rb index 33e98fc662e2..885a82fc1f1b 100644 --- a/spec/lib/remote_console/rack_server_spec.rb +++ b/spec/lib/remote_console/rack_server_spec.rb @@ -14,7 +14,8 @@ let(:right) { pipes.last } let(:hijack) { double } - let(:env) { {'REQUEST_URI' => "/ws/#{url}", 'rack.hijack' => hijack} } + let(:path) { "/ws/#{url}" } + let(:env) { {'REQUEST_URI' => path, 'rack.hijack' => hijack} } describe '#call' do context 'remote console' do @@ -30,6 +31,14 @@ end end + context 'any other URL' do + let(:path) { '/ping' } + + it 'returns with 200' do + expect(subject.call(env)).to eq(described_class::RACK_PONG) + end + end + context 'any other URL' do let(:url) { 'haha' } diff --git a/spec/support/supports_helper.rb b/spec/support/supports_helper.rb index 94210693fa42..dbdd895b2aaa 100644 --- a/spec/support/supports_helper.rb +++ b/spec/support/supports_helper.rb @@ -15,11 +15,10 @@ def stub_supports_not(model, feature = :update, reason = nil) stub_supports(model, feature, :supported => false) - if reason - receive_reason = receive(:unsupported_reason).with(feature).and_return(reason) - allow(model).to(receive_reason) - allow_any_instance_of(model).to(receive_reason) - end + reason ||= SupportsFeatureMixin.reason_or_default(reason) + receive_reason = receive(:unsupported_reason).with(feature).and_return(reason) + allow(model).to(receive_reason) + allow_any_instance_of(model).to(receive_reason) end end end