lib/manageiq/appliance_console/external_httpd_authentication/external_httpd_configuration.rb
require 'active_support/core_ext/module/delegation'
require 'pathname'
module ManageIQ
module ApplianceConsole
class ExternalHttpdAuthentication
module ExternalHttpdConfiguration
#
# External Authentication Definitions
#
IPA_COMMAND = "/usr/bin/ipa".freeze
IPA_INSTALL_COMMAND = "/usr/sbin/ipa-client-install".freeze
IPA_GETKEYTAB = "/usr/sbin/ipa-getkeytab".freeze
KERBEROS_CONFIG_FILE = "/etc/krb5.conf".freeze
SSSD_CONFIG = "/etc/sssd/sssd.conf".freeze
PAM_CONFIG = "/etc/pam.d/httpd-auth".freeze
HTTP_KEYTAB = "/etc/http.keytab".freeze
HTTP_REMOTE_USER = "/etc/httpd/conf.d/manageiq-remote-user.conf".freeze
HTTP_REMOTE_USER_OIDC = "/etc/httpd/conf.d/manageiq-remote-user-openidc.conf".freeze
HTTP_EXTERNAL_AUTH = "/etc/httpd/conf.d/manageiq-external-auth.conf".freeze
HTTP_EXTERNAL_AUTH_TEMPLATE = "#{HTTP_EXTERNAL_AUTH}.erb".freeze
GETSEBOOL_COMMAND = "/usr/sbin/getsebool".freeze
SETSEBOOL_COMMAND = "/usr/sbin/setsebool".freeze
GETENFORCE_COMMAND = "/usr/sbin/getenforce".freeze
APACHE_USER = "apache".freeze
TIMESTAMP_FORMAT = "%Y%m%d_%H%M%S".freeze
LDAP_ATTRS = {
"mail" => "REMOTE_USER_EMAIL",
"givenname" => "REMOTE_USER_FIRSTNAME",
"sn" => "REMOTE_USER_LASTNAME",
"displayname" => "REMOTE_USER_FULLNAME",
"domainname" => "REMOTE_USER_DOMAIN"
}.freeze
def template_directory
Pathname.new(ENV.fetch("APPLIANCE_TEMPLATE_DIRECTORY"))
end
#
# IPA Configuration Methods
#
def ipa_client_configure(realm, domain, server, principal, password)
say("Configuring the IPA Client ...")
AwesomeSpawn.run!(IPA_INSTALL_COMMAND,
:params => [
"-N", :force_join, :fixed_primary, :unattended, {
:realm= => realm,
:domain= => domain,
:server= => server,
:principal= => principal,
:password= => password
}
])
end
def deactivate
ipa_client_unconfigure
unconfigure_httpd
end
def ipa_client_unconfigure
say("Un-Configuring the IPA Client ...")
AwesomeSpawn.run(IPA_INSTALL_COMMAND, :params => [:uninstall, :unattended])
end
def unconfigure_httpd
say("Unconfiguring httpd ...")
unconfigure_httpd_application
say("Restarting httpd ...")
LinuxAdmin::Service.new("httpd").restart
end
def configure_httpd_application
cp_template(HTTP_EXTERNAL_AUTH_TEMPLATE, template_directory)
cp_template(HTTP_REMOTE_USER, template_directory)
end
def unconfigure_httpd_application
rm_file(HTTP_EXTERNAL_AUTH)
rm_file(HTTP_REMOTE_USER)
end
#
# Kerberos KRB5 File Methods
#
def enable_kerberos_dns_lookups
FileUtils.copy(KERBEROS_CONFIG_FILE, "#{KERBEROS_CONFIG_FILE}.miqbkp")
krb5config = File.read(KERBEROS_CONFIG_FILE)
krb5config[/(\s*)dns_lookup_kdc(\s*)=(\s*)(.*)/, 4] = 'true'
krb5config[/(\s*)dns_lookup_realm(\s*)=(\s*)(.*)/, 4] = 'true'
File.write(KERBEROS_CONFIG_FILE, krb5config)
end
#
# SSSD File Methods
#
def configure_sssd_domain(config, domain)
ldap_user_extra_attrs = LDAP_ATTRS.keys.join(", ")
if config.include?("ldap_user_extra_attrs = ")
pattern = "[domain/#{Regexp.escape(domain)}](\n.*)+ldap_user_extra_attrs = (.*)"
config[/#{pattern}/, 2] = ldap_user_extra_attrs
else
pattern = "[domain/#{Regexp.escape(domain)}].*(\n)"
config[/#{pattern}/, 1] = "\nldap_user_extra_attrs = #{ldap_user_extra_attrs}\n"
end
pattern = "[domain/#{Regexp.escape(domain)}].*(\n)"
config[/#{pattern}/, 1] = "\nentry_cache_timeout = 600\n"
end
def configure_sssd_service(config)
services = config.match(/\[sssd\](\n.*)+services = (.*)/)[2]
services = "#{services}, ifp" unless services.include?("ifp")
config[/\[sssd\](\n.*)+services = (.*)/, 2] = services
end
def configure_sssd_ifp(config)
user_attributes = LDAP_ATTRS.keys.collect { |k| "+#{k}" }.join(", ")
ifp_config = "
allowed_uids = #{APACHE_USER}, root, manageiq
user_attributes = #{user_attributes}
"
if config.include?("[ifp]")
if config[/\[ifp\](\n.*)+user_attributes = (.*)/]
config[/\[ifp\](\n.*)+user_attributes = (.*)/, 2] = user_attributes
else
config[/\[ifp\](\n)/, 1] = ifp_config
end
else
config << "\n[ifp]#{ifp_config}\n"
end
end
#
# Validation Methods
#
def installation_valid?
installed_rpm_packages = LinuxAdmin::Rpm.list_installed.keys
rpm_packages = %w(ipa-client sssd-dbus mod_intercept_form_submit mod_authnz_pam mod_lookup_identity)
missing = rpm_packages.count do |package|
installed = installed_rpm_packages.include?(package)
say("#{package} RPM is not installed") unless installed
!installed
end
if missing > 0
say("\nAppliance Installation is not valid for enabling External Authentication\n")
return false
end
true
end
def valid_environment?
return false unless installation_valid?
if ipa_client_configured?
show_current_configuration
return false unless agree("\nIPA Client already configured on this Appliance, Un-Configure first? (Y/N): ")
deactivate
return false unless agree("\nProceed with External Authentication Configuration? (Y/N): ")
end
true
end
def valid_parameters?(ipaserver)
host_reachable?(ipaserver, "IPA Server")
end
#
# Config File I/O Methods
#
def config_file_write(config, path, timestamp)
FileUtils.copy(path, "#{path}.#{timestamp}") if File.exist?(path)
File.open(path, "w") { |f| f.write(config) }
end
#
# Network validation
#
def host_reachable?(host, what = "Server")
require 'net/ping'
say("Checking connectivity to #{host} ... ")
unless Net::Ping::External.new(host).ping
say("Failed.\nCould not connect to #{host},")
say("the #{what} must be reachable by name.")
return false
end
say("Succeeded.")
true
end
def cp_template(file, src_dir, dest_dir = "/")
src_path = path_join(src_dir, file)
dest_path = path_join(dest_dir, file.gsub(".erb", ""))
if src_path.to_s.include?(".erb")
File.write(dest_path, ERB.new(File.read(src_path), nil, '-').result(binding))
else
FileUtils.cp src_path, dest_path
end
end
def rm_file(file, dir = "/")
path = path_join(dir, file)
File.delete(path) if File.exist?(path)
end
def path_join(*args)
path = Pathname.new(args.shift)
args.each { |path_seg| path = path.join("./#{path_seg}") }
path
end
end
def self.config_status
fetch_ipa_configuration("ipa_server") ||
fetch_sssd_domain ||
oidc_status ||
saml_status ||
"not configured"
end
def self.ipa_client_configured?
File.exist?(SSSD_CONFIG)
end
def self.config_file_read(path)
File.read(path)
end
def self.fetch_ipa_configuration(what, config = nil)
unless config
return nil unless ipa_client_configured?
config = config_file_read(SSSD_CONFIG)
end
pattern = "[domain/.*].*(\n.*)+#{Regexp.escape(what)} = (.*)"
config[/#{pattern}/, 2]
end
def self.fetch_sssd_domain
config_file_read(SSSD_CONFIG)[/\[domain\/(.*)\]/, 1] if File.exist?(SSSD_CONFIG)
end
def self.saml_status
"External Auth SAML" if File.exist?(HTTP_REMOTE_USER)
end
def self.oidc_status
"External Auth OpenID Connect" if File.exist?(HTTP_REMOTE_USER_OIDC)
end
delegate :ipa_client_configured?, :config_file_read, :fetch_ipa_configuration, :config_status, :to => self
end
end
end