manageiq-appliance_console 5.4.0 → 6.1.1

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.
@@ -0,0 +1,96 @@
1
+ require "awesome_spawn"
2
+ require "fileutils"
3
+ require "linux_admin"
4
+ require 'net/scp'
5
+ require "manageiq/appliance_console/message_configuration"
6
+
7
+ module ManageIQ
8
+ module ApplianceConsole
9
+ class MessageClientConfiguration < MessageConfiguration
10
+ attr_reader :message_server_password, :message_server_username, :installed_files,
11
+ :message_truststore_path_src, :message_ca_cert_path_src
12
+
13
+ def initialize(options = {})
14
+ super(options)
15
+
16
+ @message_server_host = options[:message_server_host]
17
+ @message_server_username = options[:message_server_usernamed] || "root"
18
+ @message_server_password = options[:message_server_password]
19
+
20
+ @message_truststore_path_src = options[:message_truststore_path_src] || truststore_path
21
+ @message_ca_cert_path_src = options[:message_ca_cert_path_src] || ca_cert_path
22
+
23
+ @installed_files = [client_properties_path, messaging_yaml_path, truststore_path]
24
+ end
25
+
26
+ def configure
27
+ begin
28
+ MessageServerConfiguration.new.unconfigure if MessageServerConfiguration.configured?
29
+ configure_messaging_yaml # Set up the local message client in case EVM is actually running on this, Message Server
30
+ create_client_properties # Create the client.properties configuration fle
31
+ fetch_truststore_from_server # Fetch the Java Keystore from the Kafka Server
32
+ configure_messaging_type("kafka") # Settings.prototype.messaging_type = 'kafka'
33
+ restart_evmserverd
34
+ rescue AwesomeSpawn::CommandResultError => e
35
+ say(e.result.output)
36
+ say(e.result.error)
37
+ say("")
38
+ say("Failed to Configure the Message Client- #{e}")
39
+ return false
40
+ rescue => e
41
+ say("Failed to Configure the Message Client- #{e}")
42
+ return false
43
+ end
44
+ true
45
+ end
46
+
47
+ def ask_for_parameters
48
+ say("\nMessage Client Parameters:\n\n")
49
+
50
+ @message_server_host = ask_for_string("Message Server Hostname or IP address")
51
+ @message_server_port = ask_for_integer("Message Server Port number", (1..65_535), 9_093).to_i
52
+ @message_server_username = ask_for_string("Message Server Username", message_server_username)
53
+ @message_server_password = ask_for_password("Message Server Password")
54
+ @message_truststore_path_src = ask_for_string("Message Server Truststore Path", truststore_path)
55
+ @message_ca_cert_path_src = ask_for_string("Message Server CA Cert Path", ca_cert_path)
56
+ @message_keystore_username = ask_for_string("Message Keystore Username", message_keystore_username) if secure?
57
+ @message_keystore_password = ask_for_password("Message Keystore Password") if secure?
58
+ end
59
+
60
+ def show_parameters
61
+ say("\nMessage Client Configuration:\n")
62
+ say("Message Client Details:\n")
63
+ say(" Message Server Hostname: #{message_server_host}\n")
64
+ say(" Message Server Username: #{message_server_username}\n")
65
+ say(" Message Keystore Username: #{message_keystore_username}\n")
66
+ end
67
+
68
+ def fetch_truststore_from_server
69
+ say(__method__.to_s.tr("_", " ").titleize)
70
+
71
+ fetch_from_server(message_truststore_path_src, truststore_path)
72
+ end
73
+
74
+ def fetch_ca_cert_from_server
75
+ say(__method__.to_s.tr("_", " ").titleize)
76
+
77
+ fetch_from_server(message_ca_cert_path_src, ca_cert_path)
78
+ end
79
+
80
+ private
81
+
82
+ def fetch_from_server(src_file, dst_file)
83
+ return if file_found?(dst_file)
84
+
85
+ Net::SCP.start(message_server_host, message_server_username, :password => message_server_password) do |scp|
86
+ scp.download!(src_file, dst_file)
87
+ end
88
+
89
+ File.exist?(dst_file)
90
+ rescue => e
91
+ say("Failed to fetch #{src_file} from server: #{e.message}")
92
+ false
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,319 @@
1
+ require "awesome_spawn"
2
+ require "fileutils"
3
+ require "linux_admin"
4
+ require "manageiq/appliance_console/message_configuration"
5
+
6
+ module ManageIQ
7
+ module ApplianceConsole
8
+ class MessageServerConfiguration < MessageConfiguration
9
+ attr_reader :jaas_config_path,
10
+ :server_properties_path, :server_properties_sample_path,
11
+ :ca_cert_srl_path, :ca_key_path, :cert_file_path, :cert_signed_path,
12
+ :keystore_files, :installed_files, :message_persistent_disk
13
+
14
+ PERSISTENT_DIRECTORY = Pathname.new("/var/lib/kafka/persistent_data").freeze
15
+ PERSISTENT_NAME = "kafka_messages".freeze
16
+
17
+ def initialize(options = {})
18
+ super(options)
19
+
20
+ @message_server_host = options[:message_server_use_ipaddr] == true ? my_ipaddr : options[:message_server_host] || my_hostname
21
+ @message_persistent_disk = LinuxAdmin::Disk.new(:path => options[:message_persistent_disk]) unless options[:message_persistent_disk].nil?
22
+
23
+ @jaas_config_path = config_dir_path.join("kafka_server_jaas.conf")
24
+ @server_properties_path = config_dir_path.join("server.properties")
25
+ @server_properties_sample_path = sample_config_dir_path.join("server.properties")
26
+
27
+ @ca_cert_srl_path = keystore_dir_path.join("ca-cert.srl")
28
+ @ca_key_path = keystore_dir_path.join("ca-key")
29
+ @cert_file_path = keystore_dir_path.join("cert-file")
30
+ @cert_signed_path = keystore_dir_path.join("cert-signed")
31
+
32
+ @keystore_files = [ca_cert_path, ca_cert_srl_path, ca_key_path, cert_file_path, cert_signed_path, truststore_path, keystore_path]
33
+ @installed_files = [jaas_config_path, client_properties_path, server_properties_path, messaging_yaml_path, LOGS_DIR] + keystore_files
34
+ end
35
+
36
+ def configure
37
+ begin
38
+ configure_persistent_disk # Configure the persistent message store on a different disk
39
+ create_jaas_config # Create the message server jaas config file
40
+ create_client_properties # Create the client.properties config
41
+ create_logs_directory # Create the logs directory:
42
+ configure_firewall # Open the firewall for message port 9093
43
+ configure_keystore # Populate the Java Keystore
44
+ create_server_properties # Update the /opt/message/config/server.properties
45
+ configure_messaging_yaml # Set up the local message client in case EVM is actually running on this, Message Server
46
+ configure_messaging_type("kafka") # Settings.prototype.messaging_type = 'kafka'
47
+ restart_services
48
+ rescue AwesomeSpawn::CommandResultError => e
49
+ say(e.result.output)
50
+ say(e.result.error)
51
+ say("")
52
+ say("Failed to Configure the Message Server- #{e}")
53
+ return false
54
+ rescue => e
55
+ say("Failed to Configure the Message Server- #{e}")
56
+ return false
57
+ end
58
+ true
59
+ end
60
+
61
+ def restart_services
62
+ say("Starting zookeeper and configure it to start on reboots ...")
63
+ LinuxAdmin::Service.new("zookeeper").start.enable
64
+
65
+ say("Starting kafka and configure it to start on reboots ...")
66
+ LinuxAdmin::Service.new("kafka").start.enable
67
+
68
+ restart_evmserverd
69
+ end
70
+
71
+ def ask_for_parameters
72
+ say("\nMessage Server Parameters:\n\n")
73
+
74
+ @message_server_host = ask_for_string("Message Server Hostname or IP address", message_server_host)
75
+ @message_keystore_username = ask_for_string("Message Keystore Username", message_keystore_username)
76
+ @message_keystore_password = ask_for_password("Message Keystore Password")
77
+ @message_persistent_disk = ask_for_persistent_disk
78
+ end
79
+
80
+ def ask_for_persistent_disk
81
+ choose_disk if use_new_disk
82
+ end
83
+
84
+ def use_new_disk
85
+ agree("Configure a new persistent disk volume? (Y/N): ")
86
+ end
87
+
88
+ def choose_disk
89
+ ask_for_disk("Persistent disk")
90
+ end
91
+
92
+ def show_parameters
93
+ say("\nMessage Server Configuration:\n")
94
+ say("Message Server Details:\n")
95
+ say(" Message Server Hostname: #{message_server_host}\n")
96
+ say(" Message Keystore Username: #{message_keystore_username}\n")
97
+ say(" Persistent message disk: #{message_persistent_disk.path}\n") if message_persistent_disk
98
+ end
99
+
100
+ def unconfigure
101
+ super
102
+
103
+ unconfigure_firewall
104
+ deactivate_services
105
+ end
106
+
107
+ def self.configured?
108
+ LinuxAdmin::Service.new("kafka").running? ||
109
+ LinuxAdmin::Service.new("zookeeper").running?
110
+ end
111
+
112
+ private
113
+
114
+ def my_ipaddr
115
+ LinuxAdmin::IpAddress.new.address
116
+ end
117
+
118
+ def my_hostname
119
+ LinuxAdmin::Hosts.new.hostname
120
+ end
121
+
122
+ def configure_persistent_disk
123
+ return true unless message_persistent_disk
124
+
125
+ say(__method__.to_s.tr("_", " ").titleize)
126
+
127
+ deactivate_services # Just in case they are running.
128
+
129
+ FileUtils.mkdir_p(PERSISTENT_DIRECTORY)
130
+ LogicalVolumeManagement.new(:disk => message_persistent_disk, :mount_point => PERSISTENT_DIRECTORY, :name => PERSISTENT_NAME).setup
131
+ FileUtils.chmod(0o755, PERSISTENT_DIRECTORY)
132
+ FileUtils.chown("kafka", "kafka", PERSISTENT_DIRECTORY)
133
+
134
+ true
135
+ end
136
+
137
+ def activate_new_persistent_disk
138
+ return true unless message_persistent_disk
139
+
140
+ say(__method__.to_s.tr("_", " ").titleize)
141
+
142
+ data = File.read(server_properties_path)
143
+ data.gsub!(/^log.dirs=.*$/, "log.dirs=#{PERSISTENT_DIRECTORY}")
144
+ File.write(server_properties_path, data)
145
+
146
+ true
147
+ end
148
+
149
+ def create_jaas_config
150
+ say(__method__.to_s.tr("_", " ").titleize)
151
+
152
+ content = <<~JAAS
153
+ KafkaServer {
154
+ org.apache.kafka.common.security.plain.PlainLoginModule required
155
+ username=#{message_keystore_username}
156
+ password=#{message_keystore_password}
157
+ user_admin=#{message_keystore_password} ;
158
+ };
159
+ JAAS
160
+
161
+ File.write(jaas_config_path, content) unless file_found?(jaas_config_path)
162
+ end
163
+
164
+ def create_logs_directory
165
+ say(__method__.to_s.tr("_", " ").titleize)
166
+
167
+ return if file_found?(LOGS_DIR)
168
+
169
+ FileUtils.mkdir_p(LOGS_DIR)
170
+ FileUtils.chmod(0o755, LOGS_DIR)
171
+ FileUtils.chown("kafka", "kafka", LOGS_DIR)
172
+ end
173
+
174
+ def configure_firewall
175
+ say(__method__.to_s.tr("_", " ").titleize)
176
+
177
+ modify_firewall(:add_port)
178
+ end
179
+
180
+ def configure_keystore
181
+ say(__method__.to_s.tr("_", " ").titleize)
182
+
183
+ return if files_found?(keystore_files)
184
+
185
+ keystore_params = assemble_keystore_params
186
+
187
+ # Generte a Java keystore and key pair, creating keystore.jks
188
+ # :stdin_data provides the -storepass twice to confirm and an extra CR to accept the same password for -keypass
189
+ AwesomeSpawn.run!("keytool", :params => keystore_params, :stdin_data => "#{message_keystore_password}\n#{message_keystore_password}\n\n")
190
+
191
+ # Use openssl to create a new CA cert, creating ca-cert and ca-key
192
+ AwesomeSpawn.run!("openssl", :env => {"PASSWORD" => message_keystore_password},
193
+ :params => ["req", "-new", "-x509", {"-keyout" => ca_key_path,
194
+ "-out" => ca_cert_path,
195
+ "-days" => 10_000,
196
+ "-passout" => "env:PASSWORD",
197
+ "-subj" => '/CN=something'}])
198
+
199
+ # Import the CA cert into the trust store, creating truststore.jks
200
+ # :stdin_data provides the -storepass argument and yes to confirm
201
+ AwesomeSpawn.run!("keytool", :params => {"-keystore" => truststore_path,
202
+ "-alias" => "CARoot",
203
+ "-import" => nil,
204
+ "-file" => ca_cert_path},
205
+ :stdin_data => "#{message_keystore_password}\n#{message_keystore_password}\nyes\n")
206
+
207
+ # Generate a certificate signing request (CSR) for an existing Java keystore, creating cert-file
208
+ # :stdin_data provides the -storepass argument
209
+ AwesomeSpawn.run!("keytool", :params => {"-keystore" => keystore_path,
210
+ "-alias" => keystore_params["-alias"],
211
+ "-certreq" => nil,
212
+ "-file" => cert_file_path},
213
+ :stdin_data => "#{message_keystore_password}\n")
214
+
215
+ # Use openssl to sign the certificate with the "CA" certificate, creating ca-cert.srl and cert-signed
216
+ AwesomeSpawn.run!("openssl", :env => {"PASSWORD" => message_keystore_password},
217
+ :params => ["x509", "-req", {"-CA" => ca_cert_path,
218
+ "-CAkey" => ca_key_path,
219
+ "-in" => cert_file_path,
220
+ "-out" => cert_signed_path,
221
+ "-days" => 10_000,
222
+ "-CAcreateserial" => nil,
223
+ "-passin" => "env:PASSWORD"}])
224
+
225
+ # Import a root or intermediate CA certificate to an existing Java keystore, updating keystore.jks
226
+ # :stdin_data provides the -storepass argument and yes to confirm
227
+ AwesomeSpawn.run!("keytool", :params => {"-keystore" => keystore_path,
228
+ "-alias" => "CARoot",
229
+ "-import" => nil,
230
+ "-file" => ca_cert_path},
231
+ :stdin_data => "#{message_keystore_password}\nyes\n")
232
+
233
+ # Import a signed primary certificate to an existing Java keystore, updating keystore.jks
234
+ # :stdin_data provides the -storepass argument
235
+ AwesomeSpawn.run!("keytool", :params => {"-keystore" => keystore_path,
236
+ "-alias" => keystore_params["-alias"],
237
+ "-import" => nil,
238
+ "-file" => cert_signed_path},
239
+ :stdin_data => "#{message_keystore_password}\n")
240
+ end
241
+
242
+ def create_server_properties
243
+ say(__method__.to_s.tr("_", " ").titleize)
244
+
245
+ if message_server_host.ipaddress?
246
+ ident_algorithm = ""
247
+ client_auth = "none"
248
+ else
249
+ ident_algorithm = "HTTPS"
250
+ client_auth = "required"
251
+ end
252
+
253
+ content = <<~SERVER_PROPERTIES
254
+
255
+ listeners=SASL_SSL://:#{message_server_port}
256
+
257
+ ssl.endpoint.identification.algorithm=#{ident_algorithm}
258
+ ssl.keystore.location=#{keystore_path}
259
+ ssl.keystore.password=#{message_keystore_password}
260
+ ssl.key.password=#{message_keystore_password}
261
+
262
+ ssl.truststore.location=#{truststore_path}
263
+ ssl.truststore.password=#{message_keystore_password}
264
+
265
+ ssl.client.auth=#{client_auth}
266
+
267
+ sasl.enabled.mechanisms=PLAIN
268
+ sasl.mechanism.inter.broker.protocol=PLAIN
269
+
270
+ security.inter.broker.protocol=SASL_SSL
271
+ SERVER_PROPERTIES
272
+
273
+ return if file_contains?(server_properties_path, content)
274
+
275
+ FileUtils.cp(server_properties_sample_path, server_properties_path)
276
+ File.write(server_properties_path, content, :mode => "a")
277
+
278
+ activate_new_persistent_disk
279
+ end
280
+
281
+ def unconfigure_firewall
282
+ say(__method__.to_s.tr("_", " ").titleize)
283
+
284
+ modify_firewall(:remove_port)
285
+ end
286
+
287
+ def deactivate_services
288
+ say(__method__.to_s.tr("_", " ").titleize)
289
+
290
+ LinuxAdmin::Service.new("zookeeper").stop
291
+ LinuxAdmin::Service.new("kafka").stop
292
+ end
293
+
294
+ def assemble_keystore_params
295
+ keystore_params = {"-keystore" => keystore_path,
296
+ "-validity" => 10_000,
297
+ "-genkey" => nil,
298
+ "-keyalg" => "RSA"}
299
+
300
+ if message_server_host.ipaddress?
301
+ keystore_params["-alias"] = "localhost"
302
+ keystore_params["-ext"] = "san=ip:#{message_server_host}"
303
+ else
304
+ keystore_params["-alias"] = message_server_host
305
+ keystore_params["-ext"] = "san=dns:#{message_server_host}"
306
+ end
307
+
308
+ keystore_params["-dname"] = "cn=#{keystore_params["-alias"]}"
309
+
310
+ keystore_params
311
+ end
312
+
313
+ def modify_firewall(action)
314
+ AwesomeSpawn.run!("firewall-cmd", :params => {action => "#{message_server_port}/tcp", :permanent => nil})
315
+ AwesomeSpawn.run!("firewall-cmd --reload")
316
+ end
317
+ end
318
+ end
319
+ end
@@ -1,3 +1,6 @@
1
+ require "net/http"
2
+ require "uri"
3
+
1
4
  module ManageIQ
2
5
  module ApplianceConsole
3
6
  class OIDCAuthentication
@@ -5,6 +8,10 @@ module ManageIQ
5
8
 
6
9
  attr_accessor :host, :options
7
10
 
11
+ URL_SUFFIX = /\/\.well-known\/openid-configuration$/.freeze
12
+ INTROSPECT_SUFFIX = "/protocol/openid-connect/token/introspect".freeze
13
+ INTROSPECT_ENDPOINT_ERROR = "Unable to derive the OpenID-Connect Client Introspection Endpoint. Use --oidc-introspection-endpoint".freeze
14
+
8
15
  def initialize(options)
9
16
  @options = options
10
17
  end
@@ -12,6 +19,7 @@ module ManageIQ
12
19
  def configure(host)
13
20
  @host = host
14
21
  validate_oidc_options
22
+ derive_introspection_endpoint
15
23
 
16
24
  say("Configuring OpenID-Connect Authentication for https://#{host} ...")
17
25
  copy_apache_oidc_configfiles
@@ -52,10 +60,18 @@ module ManageIQ
52
60
  debug_msg("Copying Apache OpenID-Connect Config files ...")
53
61
  copy_template(HTTPD_CONFIG_DIRECTORY, "manageiq-remote-user-openidc.conf")
54
62
  copy_template(HTTPD_CONFIG_DIRECTORY, "manageiq-external-auth-openidc.conf.erb",
55
- :miq_appliance => host,
56
- :oidc_provider_metadata_url => options[:oidc_url],
57
- :oidc_client_id => options[:oidc_client_id],
58
- :oidc_client_secret => options[:oidc_client_secret])
63
+ :miq_appliance => host,
64
+ :oidc_provider_metadata_url => options[:oidc_url],
65
+ :oidc_client_id => options[:oidc_client_id],
66
+ :oidc_client_secret => options[:oidc_client_secret],
67
+ :oidc_introspection_endpoint => options[:oidc_introspection_endpoint])
68
+
69
+ if options[:oidc_insecure]
70
+ File.open("#{HTTPD_CONFIG_DIRECTORY}/manageiq-external-auth-openidc.conf", "a") do |f|
71
+ f.write("\nOIDCSSLValidateServer Off\n")
72
+ f.write("OIDCOAuthSSLValidateServer Off\n")
73
+ end
74
+ end
59
75
  end
60
76
 
61
77
  def remove_apache_oidc_configfiles
@@ -76,6 +92,29 @@ module ManageIQ
76
92
  raise "Must specify the OpenID-Connect Client Secret via --oidc-client-secret" if options[:oidc_client_secret].blank?
77
93
  end
78
94
 
95
+ def derive_introspection_endpoint
96
+ return if options[:oidc_introspection_endpoint].present?
97
+
98
+ options[:oidc_introspection_endpoint] = fetch_introspection_endpoint
99
+ raise INTROSPECT_ENDPOINT_ERROR if options[:oidc_introspection_endpoint].blank?
100
+ end
101
+
102
+ def fetch_introspection_endpoint
103
+ uri = URI.parse(options[:oidc_url])
104
+ http = Net::HTTP.new(uri.host, uri.port)
105
+ http.use_ssl = (uri.scheme == "https")
106
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if options[:oidc_insecure]
107
+
108
+ request = Net::HTTP::Get.new(uri.request_uri)
109
+ request.basic_auth(options[:oidc_client_id], options[:oidc_client_secret])
110
+ response = http.request(request)
111
+
112
+ JSON.parse(response.body)["introspection_endpoint"]
113
+ rescue => err
114
+ say("Failed to fetch introspection endpoint - #{err}")
115
+ nil
116
+ end
117
+
79
118
  # Appliance Settings
80
119
 
81
120
  def configure_auth_settings_oidc