From 5a3774c72e5b2dee8d31879c82eec733ac6c154f Mon Sep 17 00:00:00 2001 From: Nasar Khan <nasarak98@gmail.com> Date: Wed, 17 Jul 2024 11:39:58 -0400 Subject: [PATCH] add messaging hostname validation --- .../message_configuration.rb | 24 ++++++++- .../message_configuration_client.rb | 2 +- .../message_configuration_server.rb | 15 ++---- lib/manageiq/appliance_console/prompts.rb | 6 +++ spec/message_configuration_client_spec.rb | 5 +- spec/message_configuration_server_spec.rb | 54 ++----------------- 6 files changed, 41 insertions(+), 65 deletions(-) diff --git a/lib/manageiq/appliance_console/message_configuration.rb b/lib/manageiq/appliance_console/message_configuration.rb index 53110248..b77f5a4d 100644 --- a/lib/manageiq/appliance_console/message_configuration.rb +++ b/lib/manageiq/appliance_console/message_configuration.rb @@ -68,7 +68,7 @@ def ask_questions show_parameters return false unless agree("\nProceed? (Y/N): ") - return false unless host_reachable?(message_server_host, "Message Server Host:") + return false unless host_resolvable?(message_server_host) && host_reachable?(message_server_host, "Message Server Host:") true end @@ -190,6 +190,28 @@ def host_reachable?(host, what) true end + def host_resolvable?(host) + require 'resolv' + + say("Checking if #{host} is resolvable ... ") + begin + ip_address = Resolv.getaddress(host) + + if IPAddr.new("127.0.0.1/8").include?(ip_address) || IPAddr.new("::1/128").include?(ip_address) + say("Failed.\nThe hostname must not resolve to a link-local address") + + return false + end + rescue Resolv::ResolvError => e + say("Failed.\nHostname #{host} is not resolvable: #{e.message}") + + return false + end + + say("Succeeded.") + true + end + def unconfigure remove_installed_files end diff --git a/lib/manageiq/appliance_console/message_configuration_client.rb b/lib/manageiq/appliance_console/message_configuration_client.rb index caf10689..3a43d44f 100644 --- a/lib/manageiq/appliance_console/message_configuration_client.rb +++ b/lib/manageiq/appliance_console/message_configuration_client.rb @@ -46,7 +46,7 @@ def configure def ask_for_parameters say("\nMessage Client Parameters:\n\n") - @message_server_host = ask_for_string("Message Server Hostname or IP address") + @message_server_host = ask_for_messaging_hostname("Message Server Hostname") @message_server_port = ask_for_integer("Message Server Port number", (1..65_535), 9_093).to_i @message_server_username = ask_for_string("Message Server Username", message_server_username) @message_server_password = ask_for_password("Message Server Password") diff --git a/lib/manageiq/appliance_console/message_configuration_server.rb b/lib/manageiq/appliance_console/message_configuration_server.rb index 44a4c2ec..6532cbdd 100644 --- a/lib/manageiq/appliance_console/message_configuration_server.rb +++ b/lib/manageiq/appliance_console/message_configuration_server.rb @@ -68,11 +68,7 @@ def restart_services def ask_for_parameters say("\nMessage Server Parameters:\n\n") - @message_server_host = ask_for_string("Message Server Hostname or IP address", message_server_host) - - # SSL Validation for Kafka does not work for hostnames containing "localhost" - # Therefore we replace with the equivalent IP "127.0.0.1" if a /localhost*/ hostname was entered - @message_server_host = "127.0.0.1" if @message_server_host.include?("localhost") + @message_server_host = ask_for_messaging_hostname("Message Server Hostname", message_server_host) @message_keystore_username = ask_for_string("Message Keystore Username", message_keystore_username) @message_keystore_password = ask_for_new_password("Message Keystore Password") @@ -301,13 +297,8 @@ def assemble_keystore_params "-genkey" => nil, "-keyalg" => "RSA"} - if message_server_host.ipaddress? - keystore_params["-alias"] = "localhost" - keystore_params["-ext"] = "san=ip:#{message_server_host}" - else - keystore_params["-alias"] = message_server_host - keystore_params["-ext"] = "san=dns:#{message_server_host}" - end + keystore_params["-alias"] = message_server_host + keystore_params["-ext"] = "san=dns:#{message_server_host}" keystore_params["-dname"] = "cn=#{keystore_params["-alias"]}" diff --git a/lib/manageiq/appliance_console/prompts.rb b/lib/manageiq/appliance_console/prompts.rb index c01429f9..bf279962 100644 --- a/lib/manageiq/appliance_console/prompts.rb +++ b/lib/manageiq/appliance_console/prompts.rb @@ -14,6 +14,7 @@ module Prompts INT_REGEXP = /^[0-9]+$/ NONE_REGEXP = /^('?NONE'?)?$/i.freeze HOSTNAME_REGEXP = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/.freeze + MESSAGING_HOSTNAME_REGEXP = /^(?!.*localhost)(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/.freeze def ask_for_uri(prompt, expected_scheme, opts = {}) require 'uri' @@ -71,6 +72,11 @@ def ask_for_hostname(prompt, default = nil, validate = HOSTNAME_REGEXP, error_te just_ask(prompt, default, validate, error_text, &block) end + def ask_for_messaging_hostname(prompt, default = nil, error_text = "a valid Messaging Hostname (not an IP or localhost)", &block) + validation = ->(h) { h =~ MESSAGING_HOSTNAME_REGEXP && h !~ IP_REGEXP } + just_ask(prompt, default, validation, error_text, &block) + end + def ask_for_ip_or_hostname(prompt, default = nil) validation = ->(h) { (h =~ HOSTNAME_REGEXP || h =~ IP_REGEXP) && h.length > 0 } ask_for_ip(prompt, default, validation, "a valid Hostname or IP Address.") diff --git a/spec/message_configuration_client_spec.rb b/spec/message_configuration_client_spec.rb index b202a9dd..8876849d 100644 --- a/spec/message_configuration_client_spec.rb +++ b/spec/message_configuration_client_spec.rb @@ -40,12 +40,13 @@ describe "#ask_questions" do before do allow(subject).to receive(:agree).and_return(true) + allow(subject).to receive(:host_resolvable?).and_return(true) allow(subject).to receive(:host_reachable?).and_return(true) allow(subject).to receive(:message_client_configured?).and_return(false) end it "should prompt for message_keystore_username and message_keystore_password" do - expect(subject).to receive(:ask_for_string).with("Message Server Hostname or IP address").and_return("my-host-name.example.com") + expect(subject).to receive(:ask_for_messaging_hostname).with("Message Server Hostname").and_return("my-host-name.example.com") expect(subject).to receive(:ask_for_integer).with("Message Server Port number", (1..65_535), 9_093).and_return("9093") expect(subject).to receive(:ask_for_string).with("Message Keystore Username", message_keystore_username).and_return("admin") expect(subject).to receive(:ask_for_password).with("Message Keystore Password").and_return("top_secret") @@ -61,7 +62,7 @@ end it "should display Server Hostname and Key Username" do - allow(subject).to receive(:ask_for_string).with("Message Server Hostname or IP address").and_return("my-kafka-server.example.com") + allow(subject).to receive(:ask_for_messaging_hostname).with("Message Server Hostname").and_return("my-kafka-server.example.com") allow(subject).to receive(:ask_for_integer).with("Message Server Port number", (1..65_535), 9_093).and_return("9093") allow(subject).to receive(:ask_for_string).with("Message Keystore Username", message_keystore_username).and_return("admin") allow(subject).to receive(:ask_for_password).with("Message Keystore Password").and_return("top_secret") diff --git a/spec/message_configuration_server_spec.rb b/spec/message_configuration_server_spec.rb index 6952a7a5..d6a64cfa 100644 --- a/spec/message_configuration_server_spec.rb +++ b/spec/message_configuration_server_spec.rb @@ -35,6 +35,7 @@ describe "#ask_questions" do before do allow(subject).to receive(:agree).and_return(true) + allow(subject).to receive(:host_resolvable?).and_return(true) allow(subject).to receive(:host_reachable?).and_return(true) allow(subject).to receive(:message_server_configured?).and_return(false) end @@ -45,7 +46,7 @@ end it "should prompt for message_keystore_username and message_keystore_password" do - expect(subject).to receive(:ask_for_string).with("Message Server Hostname or IP address", "my-host-name.example.com").and_return("my-host-name.example.com") + expect(subject).to receive(:ask_for_messaging_hostname).with("Message Server Hostname", "my-host-name.example.com").and_return("my-host-name.example.com") expect(subject).to receive(:ask_for_string).with("Message Keystore Username", message_keystore_username).and_return("admin") expect(subject).to receive(:just_ask).with(/Message Keystore Password/i, anything).twice.and_return("top_secret") @@ -55,7 +56,7 @@ end it "should re-prompt when an empty message_keystore_password is given" do - expect(subject).to receive(:ask_for_string).with("Message Server Hostname or IP address", "my-host-name.example.com").and_return("my-host-name.example.com") + expect(subject).to receive(:ask_for_messaging_hostname).with("Message Server Hostname", "my-host-name.example.com").and_return("my-host-name.example.com") expect(subject).to receive(:ask_for_string).with("Message Keystore Username", message_keystore_username).and_return("admin") expect(subject).to receive(:just_ask).with(/Message Keystore Password/i, anything).and_return("") expect(subject).to receive(:just_ask).with(/Message Keystore Password/i, anything).twice.and_return("top_secret") @@ -67,7 +68,7 @@ end it "should display Server Hostname and Keystore Username" do - allow(subject).to receive(:ask_for_string).with("Message Server Hostname or IP address", "my-host-name.example.com").and_return("my-host-name.example.com") + allow(subject).to receive(:ask_for_messaging_hostname).with("Message Server Hostname", "my-host-name.example.com").and_return("my-host-name.example.com") allow(subject).to receive(:ask_for_string).with("Message Keystore Username", message_keystore_username).and_return("admin") expect(subject).to receive(:just_ask).with(/Message Keystore Password/i, anything).twice.and_return("top_secret") @@ -88,7 +89,7 @@ it "should prompt for message_keystore_username, message_keystore_password and persistent disk" do message_persistent_disk = LinuxAdmin::Disk.new(:path => "/tmp/disk") - expect(subject).to receive(:ask_for_string).with("Message Server Hostname or IP address", "my-host-name.example.com").and_return("my-host-name.example.com") + expect(subject).to receive(:ask_for_messaging_hostname).with("Message Server Hostname", "my-host-name.example.com").and_return("my-host-name.example.com") expect(subject).to receive(:ask_for_string).with("Message Keystore Username", message_keystore_username).and_return("admin") expect(subject).to receive(:just_ask).with(/Message Keystore Password/i, anything).twice.and_return("top_secret") expect(subject).to receive(:ask_for_disk).with("Persistent disk").and_return(message_persistent_disk) @@ -215,14 +216,6 @@ expect(subject).to receive(:say).with("Configure Keystore") end - context "with IP address" do - let(:ks_alias) { "localhost" } - let(:message_server_host) { "192.0.2.0" } - let(:ext) { "san=ip:#{message_server_host}" } - - include_examples "configure keystore" - end - context "with hostname" do let(:ks_alias) { "my-host-name.example.com" } let(:message_server_host) { ks_alias } @@ -283,13 +276,6 @@ end end - context "with IP address" do - let(:ident_algorithm) { "" } - let(:client_auth) { "none" } - let(:message_server_host) { "192.0.2.0" } - include_examples "service properties file" - end - context "with hostname" do let(:ident_algorithm) { "HTTPS" } let(:client_auth) { "required" } @@ -366,35 +352,5 @@ expect(subject.message_server_host).to eq("192.0.2.1") end end - - context "when --message-server-host is specified as localhost*" do - before do - allow(subject).to receive(:agree).and_return(true) - allow(subject).to receive(:host_reachable?).and_return(true) - allow(subject).to receive(:message_server_configured?).and_return(true) - end - - it "replaces localhost with 127.0.0.1" do - expect(subject).to receive(:say).with(/Message Server Parameters/) - expect(subject).to receive(:ask_for_string).with("Message Server Hostname or IP address", anything).and_return("localhost") - expect(subject).to receive(:ask_for_string).with("Message Keystore Username", anything).and_return("admin") - expect(subject).to receive(:just_ask).with(/Message Keystore Password/i, anything).twice.and_return("top_secret") - expect(subject).to receive(:ask_for_disk).with("Persistent disk").and_return("/tmp/disk") - - subject.ask_for_parameters - expect(subject.message_server_host).to eq("127.0.0.1") - end - - it "replaces localhost.localadmin with 127.0.0.1" do - expect(subject).to receive(:say).with(/Message Server Parameters/) - expect(subject).to receive(:ask_for_string).with("Message Server Hostname or IP address", anything).and_return("localhost.localadmin") - expect(subject).to receive(:ask_for_string).with("Message Keystore Username", anything).and_return("admin") - expect(subject).to receive(:just_ask).with(/Message Keystore Password/i, anything).twice.and_return("top_secret") - expect(subject).to receive(:ask_for_disk).with("Persistent disk").and_return("/tmp/disk") - - subject.ask_for_parameters - expect(subject.message_server_host).to eq("127.0.0.1") - end - end end end