manageiq-appliance_console 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +47 -0
  3. data/.gitignore +12 -0
  4. data/.rspec +4 -0
  5. data/.rspec_ci +4 -0
  6. data/.rubocop.yml +4 -0
  7. data/.rubocop_cc.yml +5 -0
  8. data/.rubocop_local.yml +2 -0
  9. data/.travis.yml +19 -0
  10. data/Gemfile +6 -0
  11. data/LICENSE.txt +202 -0
  12. data/README.md +45 -0
  13. data/Rakefile +6 -0
  14. data/bin/appliance_console +661 -0
  15. data/bin/appliance_console_cli +7 -0
  16. data/lib/manageiq-appliance_console.rb +51 -0
  17. data/lib/manageiq/appliance_console/certificate.rb +146 -0
  18. data/lib/manageiq/appliance_console/certificate_authority.rb +140 -0
  19. data/lib/manageiq/appliance_console/cli.rb +363 -0
  20. data/lib/manageiq/appliance_console/database_configuration.rb +286 -0
  21. data/lib/manageiq/appliance_console/database_maintenance.rb +35 -0
  22. data/lib/manageiq/appliance_console/database_maintenance_hourly.rb +58 -0
  23. data/lib/manageiq/appliance_console/database_maintenance_periodic.rb +84 -0
  24. data/lib/manageiq/appliance_console/database_replication.rb +146 -0
  25. data/lib/manageiq/appliance_console/database_replication_primary.rb +59 -0
  26. data/lib/manageiq/appliance_console/database_replication_standby.rb +166 -0
  27. data/lib/manageiq/appliance_console/date_time_configuration.rb +117 -0
  28. data/lib/manageiq/appliance_console/errors.rb +5 -0
  29. data/lib/manageiq/appliance_console/external_auth_options.rb +153 -0
  30. data/lib/manageiq/appliance_console/external_database_configuration.rb +34 -0
  31. data/lib/manageiq/appliance_console/external_httpd_authentication.rb +157 -0
  32. data/lib/manageiq/appliance_console/external_httpd_authentication/external_httpd_configuration.rb +249 -0
  33. data/lib/manageiq/appliance_console/internal_database_configuration.rb +187 -0
  34. data/lib/manageiq/appliance_console/key_configuration.rb +118 -0
  35. data/lib/manageiq/appliance_console/logfile_configuration.rb +117 -0
  36. data/lib/manageiq/appliance_console/logger.rb +23 -0
  37. data/lib/manageiq/appliance_console/logging.rb +102 -0
  38. data/lib/manageiq/appliance_console/logical_volume_management.rb +94 -0
  39. data/lib/manageiq/appliance_console/principal.rb +46 -0
  40. data/lib/manageiq/appliance_console/prompts.rb +211 -0
  41. data/lib/manageiq/appliance_console/scap.rb +53 -0
  42. data/lib/manageiq/appliance_console/temp_storage_configuration.rb +79 -0
  43. data/lib/manageiq/appliance_console/timezone_configuration.rb +58 -0
  44. data/lib/manageiq/appliance_console/utilities.rb +67 -0
  45. data/lib/manageiq/appliance_console/version.rb +5 -0
  46. data/locales/appliance/en.yml +42 -0
  47. data/locales/container/en.yml +30 -0
  48. data/manageiq-appliance_console.gemspec +40 -0
  49. data/zanata.xml +7 -0
  50. metadata +317 -0
@@ -0,0 +1,94 @@
1
+ require 'linux_admin'
2
+ require 'fileutils'
3
+
4
+ module ManageIQ
5
+ module ApplianceConsole
6
+ class LogicalVolumeManagement
7
+ include ManageIQ::ApplianceConsole::Logging
8
+
9
+ # Required instantiation parameters
10
+ attr_accessor :disk, :mount_point, :name
11
+
12
+ # Derived or optionally provided instantiation parameters
13
+ attr_accessor :volume_group_name, :filesystem_type, :logical_volume_path
14
+
15
+ # Logical Disk creation objects
16
+ attr_reader :logical_volume, :partition, :physical_volume, :volume_group
17
+
18
+ def initialize(options = {})
19
+ # Required instantiation parameters
20
+ self.disk = options[:disk] || raise(ArgumentError, "disk object required")
21
+ self.mount_point = options[:mount_point] || raise(ArgumentError, "mount point required")
22
+ self.name = options[:name] || raise(ArgumentError, "unique name required")
23
+
24
+ # Derived or optionally provided instantiation parameters
25
+ self.volume_group_name ||= "vg_#{name}"
26
+ self.filesystem_type ||= "xfs"
27
+ self.logical_volume_path ||= "/dev/#{volume_group_name}/lv_#{name}"
28
+ end
29
+
30
+ # Helper method
31
+ def setup
32
+ create_partition_to_fill_disk
33
+ create_physical_volume
34
+ create_volume_group
35
+ create_logical_volume_to_fill_volume_group
36
+ format_logical_volume
37
+ mount_disk
38
+ update_fstab
39
+ end
40
+
41
+ private
42
+
43
+ def create_partition_to_fill_disk
44
+ disk.create_partition_table
45
+ AwesomeSpawn.run!("parted -s #{disk.path} mkpart primary 0% 100%")
46
+
47
+ self.disk = LinuxAdmin::Disk.local.find { |d| d.path == disk.path }
48
+ @partition = disk.partitions.first
49
+ end
50
+
51
+ def create_physical_volume
52
+ @physical_volume = LinuxAdmin::PhysicalVolume.create(partition)
53
+ end
54
+
55
+ def create_volume_group
56
+ @volume_group = LinuxAdmin::VolumeGroup.create(volume_group_name, physical_volume)
57
+ end
58
+
59
+ def create_logical_volume_to_fill_volume_group
60
+ @logical_volume = LinuxAdmin::LogicalVolume.create(logical_volume_path, volume_group, 100)
61
+ end
62
+
63
+ def format_logical_volume
64
+ AwesomeSpawn.run!("mkfs.#{filesystem_type} #{logical_volume.path}")
65
+ end
66
+
67
+ def mount_disk
68
+ if mount_point.symlink?
69
+ FileUtils.rm_rf(mount_point)
70
+ FileUtils.mkdir_p(mount_point)
71
+ end
72
+ AwesomeSpawn.run!("mount", :params => {"-t" => filesystem_type,
73
+ nil => [logical_volume.path, mount_point]})
74
+ end
75
+
76
+ def update_fstab
77
+ fstab = LinuxAdmin::FSTab.instance
78
+ return if fstab.entries.find { |e| e.mount_point == mount_point }
79
+
80
+ entry = LinuxAdmin::FSTabEntry.new(
81
+ :device => logical_volume.path,
82
+ :mount_point => mount_point,
83
+ :fs_type => filesystem_type,
84
+ :mount_options => "rw,noatime",
85
+ :dumpable => 0,
86
+ :fsck_order => 0
87
+ )
88
+
89
+ fstab.entries << entry
90
+ fstab.write! # Test this more, whitespace is removed
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,46 @@
1
+ require 'awesome_spawn'
2
+
3
+ module ManageIQ
4
+ module ApplianceConsole
5
+ # Kerberos principal
6
+ class Principal
7
+ attr_accessor :ca_name
8
+ attr_accessor :hostname
9
+ attr_accessor :realm
10
+ attr_accessor :service
11
+ # kerberos principal name
12
+ attr_accessor :name
13
+
14
+ def initialize(options = {})
15
+ options.each { |n, v| public_send("#{n}=", v) }
16
+ @ca_name ||= "ipa"
17
+ @realm = @realm.upcase if @realm
18
+ @name ||= "#{service}/#{hostname}@#{realm}"
19
+ end
20
+
21
+ def register
22
+ request if ipa? && !exist?
23
+ end
24
+
25
+ def subject_name
26
+ "CN=#{hostname},OU=#{service},O=#{realm}"
27
+ end
28
+
29
+ def ipa?
30
+ @ca_name == "ipa"
31
+ end
32
+
33
+ private
34
+
35
+ def exist?
36
+ AwesomeSpawn.run("/usr/bin/ipa", :params => ["-e", "skip_version_check=1", "service-find", "--principal", name]).success?
37
+ end
38
+
39
+ def request
40
+ # using --force because these services tend not to be in dns
41
+ # this is like VERIFY_NONE
42
+ AwesomeSpawn.run!("/usr/bin/ipa", :params => ["-e", "skip_version_check=1", "service-add", "--force", name])
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,211 @@
1
+ require 'resolv'
2
+
3
+ module ManageIQ
4
+ module ApplianceConsole
5
+ CANCEL = 'Cancel'.freeze
6
+
7
+ module Prompts
8
+ CLEAR_CODE = `clear`
9
+ IPV4_REGEXP = Resolv::IPv4::Regex
10
+ IPV6_REGEXP = Resolv::IPv6::Regex
11
+ IP_REGEXP = Regexp.union(Resolv::IPv4::Regex, Resolv::IPv6::Regex).freeze
12
+ DOMAIN_REGEXP = /^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*(\.[a-z]{2,13})?$/
13
+ INT_REGEXP = /^[0-9]+$/
14
+ NONE_REGEXP = /^('?NONE'?)?$/i.freeze
15
+ HOSTNAME_REGEXP = /^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$/
16
+
17
+ SAMPLE_URLS = {
18
+ 'nfs' => 'nfs://host.mydomain.com/exported/my_exported_folder/db.backup',
19
+ 'smb' => 'smb://host.mydomain.com/my_share/daily_backup/db.backup',
20
+ }
21
+
22
+ def sample_url(scheme)
23
+ SAMPLE_URLS[scheme]
24
+ end
25
+
26
+ def ask_for_uri(prompt, expected_scheme)
27
+ require 'uri'
28
+ just_ask(prompt, nil, nil, 'a valid URI') do |q|
29
+ q.validate = lambda do |a|
30
+ # Convert all backslashes in the URI to forward slashes and strip whitespace
31
+ a.tr!('\\', '/')
32
+ a.strip!
33
+ u = URI(a)
34
+ # validate it has a hostname/ip and a share
35
+ u.scheme == expected_scheme &&
36
+ (u.host =~ HOSTNAME_REGEXP || u.hostname =~ IP_REGEXP) &&
37
+ !u.path.empty?
38
+ end
39
+ end
40
+ end
41
+
42
+ def press_any_key
43
+ say("\nPress any key to continue.")
44
+ begin
45
+ system("stty raw -echo")
46
+ STDIN.getc
47
+ ensure
48
+ system("stty -raw echo")
49
+ end
50
+ end
51
+
52
+ def clear_screen
53
+ print CLEAR_CODE
54
+ end
55
+
56
+ def are_you_sure?(clarifier = nil)
57
+ clarifier = " you want to #{clarifier}" if clarifier && !clarifier.include?("want")
58
+ agree("Are you sure#{clarifier}? (Y/N): ")
59
+ end
60
+
61
+ def ask_yn?(prompt, default = nil)
62
+ ask("#{prompt}? (Y/N): ") do |q|
63
+ q.default = default if default
64
+ q.validate = ->(p) { (p.blank? && default) || %w(y n).include?(p.downcase[0]) }
65
+ q.responses[:not_valid] = "Please provide yes or no."
66
+ end.downcase[0] == 'y'
67
+ end
68
+
69
+ def ask_for_domain(prompt, default = nil, validate = DOMAIN_REGEXP, error_text = "a valid Domain.", &block)
70
+ just_ask(prompt, default, validate, error_text, &block)
71
+ end
72
+
73
+ def ask_for_ip(prompt, default, validate = IP_REGEXP, error_text = "a valid IP Address.", &block)
74
+ just_ask(prompt, default, validate, error_text, &block)
75
+ end
76
+
77
+ def ask_for_ipv4(prompt, default)
78
+ ask_for_ip(prompt, default, IPV4_REGEXP)
79
+ end
80
+
81
+ def ask_for_ipv4_or_none(prompt, default = nil)
82
+ ask_for_ip(prompt, default, Regexp.union(NONE_REGEXP, IPV4_REGEXP)).gsub(NONE_REGEXP, "")
83
+ end
84
+
85
+ def ask_for_ipv6(prompt, default)
86
+ ask_for_ip(prompt, default, IPV6_REGEXP)
87
+ end
88
+
89
+ def ask_for_ipv6_or_none(prompt, default = nil)
90
+ ask_for_ip(prompt, default, Regexp.union(IPV6_REGEXP, NONE_REGEXP)).gsub(NONE_REGEXP, '')
91
+ end
92
+
93
+ def ask_for_hostname(prompt, default = nil, validate = HOSTNAME_REGEXP, error_text = "a valid Hostname.", &block)
94
+ just_ask(prompt, default, validate, error_text, &block)
95
+ end
96
+
97
+ def ask_for_ip_or_hostname(prompt, default = nil)
98
+ validation = ->(h) { (h =~ HOSTNAME_REGEXP || h =~ IP_REGEXP) && h.length > 0 }
99
+ ask_for_ip(prompt, default, validation, "a valid Hostname or IP Address.")
100
+ end
101
+
102
+ def ask_for_ip_or_hostname_or_none(prompt, default = nil)
103
+ validation = Regexp.union(NONE_REGEXP, HOSTNAME_REGEXP, IP_REGEXP)
104
+ ask_for_ip(prompt, default, validation, "a valid Hostname or IP Address.").gsub(NONE_REGEXP, "")
105
+ end
106
+
107
+ def ask_for_schedule_frequency(prompt, default = nil)
108
+ validation = ->(h) { %w(hourly daily weekly monthly).include?(h) }
109
+ just_ask(prompt, default, validation, "hourly, daily, weekly or monthly")
110
+ end
111
+
112
+ def ask_for_hour_number(prompt)
113
+ ask_for_integer(prompt, (0..23))
114
+ end
115
+
116
+ def ask_for_week_day_number(prompt)
117
+ ask_for_integer(prompt, (0..6))
118
+ end
119
+
120
+ def ask_for_month_day_number(prompt)
121
+ ask_for_integer(prompt, (1..31))
122
+ end
123
+
124
+ def ask_for_many(prompt, collective = nil, default = nil, max_length = 255, max_count = 6)
125
+ collective ||= "#{prompt}s"
126
+ validate = ->(p) { (p.length < max_length) && (p.split(/[\s,;]+/).length <= max_count) }
127
+ error_message = "up to #{max_count} #{prompt}s separated by a space and up to #{max_length} characters"
128
+ just_ask(collective, default, validate, error_message).split(/[\s,;]+/).collect(&:strip)
129
+ end
130
+
131
+ def ask_for_password(prompt, default = nil)
132
+ pass = just_ask(prompt, default.present? ? "********" : nil) do |q|
133
+ q.echo = '*'
134
+ yield q if block_given?
135
+ end
136
+ pass == "********" ? (default || "") : pass
137
+ end
138
+
139
+ def ask_for_string(prompt, default = nil)
140
+ just_ask(prompt, default)
141
+ end
142
+
143
+ def ask_for_integer(prompt, range = nil, default = nil)
144
+ just_ask(prompt, default, INT_REGEXP, "an integer", Integer) { |q| q.in = range if range }
145
+ end
146
+
147
+ def ask_for_disk(disk_name, verify = true)
148
+ require "linux_admin"
149
+ disks = LinuxAdmin::Disk.local.select { |d| d.partitions.empty? }
150
+
151
+ if disks.empty?
152
+ say "No partition found for #{disk_name}. You probably want to add an unpartitioned disk and try again."
153
+ else
154
+ default_choice = disks.size == 1 ? "1" : nil
155
+ disk = ask_with_menu(
156
+ disk_name,
157
+ disks.collect { |d| [("#{d.path}: #{d.size.to_i / 1.megabyte} MB"), d] },
158
+ default_choice
159
+ ) do |q|
160
+ q.choice("Don't partition the disk") { nil }
161
+ end
162
+ end
163
+
164
+ if verify && disk.nil?
165
+ say ""
166
+ raise MiqSignalError unless are_you_sure?(" you don't want to partition the #{disk_name}")
167
+ end
168
+ disk
169
+ end
170
+
171
+ # use the integer index for menu prompts
172
+ # ensure default is a string
173
+ def default_to_index(default, options)
174
+ return unless default
175
+ default_index = if options.kind_of?(Hash)
176
+ options.values.index(default) || options.keys.index(default)
177
+ else
178
+ options.index(default)
179
+ end
180
+ default_index ? (default_index.to_i + 1).to_s : default.to_s
181
+ end
182
+
183
+ def ask_with_menu(prompt, options, default = nil, clear_screen_after = true)
184
+ say("#{prompt}\n\n")
185
+
186
+ default = default_to_index(default, options)
187
+ selection = nil
188
+ choose do |menu|
189
+ menu.default = default
190
+ menu.index = :number
191
+ menu.index_suffix = ") "
192
+ menu.prompt = "\nChoose the #{prompt.downcase}:#{" |#{default}|" if default} "
193
+ options.each { |o, v| menu.choice(o) { |c| selection = v || c } }
194
+ yield menu if block_given?
195
+ end
196
+ clear_screen if clear_screen_after
197
+ selection
198
+ end
199
+
200
+ def just_ask(prompt, default = nil, validate = nil, error_text = nil, klass = nil)
201
+ ask("Enter the #{prompt}: ", klass) do |q|
202
+ q.readline = true
203
+ q.default = default.to_s if default
204
+ q.validate = validate if validate
205
+ q.responses[:not_valid] = error_text ? "Please provide #{error_text}" : "Please provide in the specified format"
206
+ yield q if block_given?
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,53 @@
1
+ require 'linux_admin'
2
+
3
+ module ManageIQ
4
+ module ApplianceConsole
5
+ class Scap
6
+ def initialize(rules_dir)
7
+ @rules_dir = rules_dir
8
+ end
9
+
10
+ def lockdown
11
+ if packages_installed? && config_exists?
12
+ say("Locking down the appliance for SCAP...")
13
+ require 'yaml'
14
+ scap_config = YAML.load_file(yaml_filename)
15
+ begin
16
+ LinuxAdmin::Scap.new("rhel7").lockdown(*scap_config['rules'], scap_config['values'])
17
+ rescue => e
18
+ say("Configuration failed: #{e.message}")
19
+ else
20
+ say("Complete")
21
+ end
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def yaml_filename
28
+ File.expand_path("scap_rules.yml", @rules_dir)
29
+ end
30
+
31
+ def packages_installed?
32
+ if !LinuxAdmin::Scap.openscap_available?
33
+ say("OpenSCAP has not been installed")
34
+ false
35
+ elsif !LinuxAdmin::Scap.ssg_available?("rhel7")
36
+ say("SCAP Security Guide has not been installed")
37
+ false
38
+ else
39
+ true
40
+ end
41
+ end
42
+
43
+ def config_exists?
44
+ if File.exist?(yaml_filename)
45
+ true
46
+ else
47
+ say("SCAP rules configuration file missing")
48
+ false
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,79 @@
1
+ module ManageIQ
2
+ module ApplianceConsole
3
+ class TempStorageConfiguration
4
+ TEMP_DISK_FILESYSTEM_TYPE = "xfs".freeze
5
+ TEMP_DISK_MOUNT_POINT = Pathname.new("/var/www/miq_tmp").freeze
6
+ TEMP_DISK_MOUNT_OPTS = "rw,noatime,nobarrier".freeze
7
+
8
+ attr_reader :disk
9
+
10
+ include ManageIQ::ApplianceConsole::Logging
11
+
12
+ def initialize(config = {})
13
+ @disk = config[:disk]
14
+ end
15
+
16
+ def activate
17
+ say("Configuring #{disk.path} as temp storage...")
18
+ add_temp_disk(disk)
19
+ end
20
+
21
+ def ask_questions
22
+ @disk = ask_for_disk("temp storage disk", false)
23
+ disk && are_you_sure?("configure #{disk.path} as temp storage")
24
+ end
25
+
26
+ def add_temp_disk(disk)
27
+ log_and_feedback(__method__) do
28
+ partition = create_partition_to_fill_disk(disk)
29
+ format_partition(partition)
30
+ mount_temp_disk(partition)
31
+ update_fstab(partition)
32
+ end
33
+ end
34
+
35
+ def format_partition(partition)
36
+ AwesomeSpawn.run!("mkfs.#{TEMP_DISK_FILESYSTEM_TYPE} #{partition.path}")
37
+ end
38
+
39
+ def mount_temp_disk(partition)
40
+ # TODO: should this be moved into LinuxAdmin?
41
+ FileUtils.rm_rf(TEMP_DISK_MOUNT_POINT)
42
+ FileUtils.mkdir_p(TEMP_DISK_MOUNT_POINT)
43
+ AwesomeSpawn.run!("mount", :params => {
44
+ "-t" => TEMP_DISK_FILESYSTEM_TYPE,
45
+ "-o" => TEMP_DISK_MOUNT_OPTS,
46
+ nil => [partition.path, TEMP_DISK_MOUNT_POINT]
47
+ })
48
+ end
49
+
50
+ def update_fstab(partition)
51
+ fstab = LinuxAdmin::FSTab.instance
52
+ return if fstab.entries.detect { |e| e.mount_point == TEMP_DISK_MOUNT_POINT }
53
+
54
+ entry = LinuxAdmin::FSTabEntry.new(
55
+ :device => partition.path,
56
+ :mount_point => TEMP_DISK_MOUNT_POINT,
57
+ :fs_type => TEMP_DISK_FILESYSTEM_TYPE,
58
+ :mount_options => TEMP_DISK_MOUNT_OPTS,
59
+ :dumpable => 0,
60
+ :fsck_order => 0
61
+ )
62
+
63
+ fstab.entries << entry
64
+ fstab.write! # Test this more, whitespace is removed
65
+ end
66
+
67
+ # FIXME: Copied from InternalDatabaseConfiguration - remove both when LinuxAdmin updated
68
+ def create_partition_to_fill_disk(disk)
69
+ # @disk.create_partition('primary', '100%')
70
+ disk.create_partition_table # LinuxAdmin::Disk.create_partition has this already...
71
+ AwesomeSpawn.run!("parted -s #{disk.path} mkpart primary 0% 100%")
72
+
73
+ # FIXME: Refetch the disk after creating the partition
74
+ disk = LinuxAdmin::Disk.local.find { |d| d.path == disk.path }
75
+ disk.partitions.first
76
+ end
77
+ end # class TempStorageConfiguration
78
+ end # module ApplianceConsole
79
+ end