diff --git a/manifests/config.pp b/manifests/config.pp index 9572f367a..40619ed41 100644 --- a/manifests/config.pp +++ b/manifests/config.pp @@ -158,128 +158,161 @@ $listen_socket = '/run/foreman.sock' class { 'foreman::config::apache': - app_root => $foreman::app_root, - priority => $foreman::vhost_priority, - servername => $foreman::servername, - serveraliases => $foreman::serveraliases, - server_port => $foreman::server_port, - server_ssl_port => $foreman::server_ssl_port, - proxy_backend => "unix://${listen_socket}", - ssl => $foreman::ssl, - ssl_ca => $foreman::server_ssl_ca, - ssl_chain => $foreman::server_ssl_chain, - ssl_cert => $foreman::server_ssl_cert, - ssl_key => $foreman::server_ssl_key, - ssl_crl => $foreman::server_ssl_crl, - ssl_protocol => $foreman::server_ssl_protocol, - ssl_verify_client => $foreman::server_ssl_verify_client, - user => $foreman::user, - foreman_url => $foreman::foreman_url, - ipa_authentication => $foreman::ipa_authentication, - keycloak => $foreman::keycloak, - keycloak_app_name => $foreman::keycloak_app_name, - keycloak_realm => $foreman::keycloak_realm, + app_root => $foreman::app_root, + priority => $foreman::vhost_priority, + servername => $foreman::servername, + serveraliases => $foreman::serveraliases, + server_port => $foreman::server_port, + server_ssl_port => $foreman::server_ssl_port, + proxy_backend => "unix://${listen_socket}", + ssl => $foreman::ssl, + ssl_ca => $foreman::server_ssl_ca, + ssl_chain => $foreman::server_ssl_chain, + ssl_cert => $foreman::server_ssl_cert, + ssl_key => $foreman::server_ssl_key, + ssl_crl => $foreman::server_ssl_crl, + ssl_protocol => $foreman::server_ssl_protocol, + ssl_verify_client => $foreman::server_ssl_verify_client, + user => $foreman::user, + foreman_url => $foreman::foreman_url, + external_authentication => $external_foreman::authentication, + keycloak => $foreman::keycloak, + keycloak_app_name => $foreman::keycloak_app_name, + keycloak_realm => $foreman::keycloak_realm, } contain foreman::config::apache $foreman_socket_override = template('foreman/foreman.socket-overrides.erb') - if $foreman::ipa_authentication { - if $facts['os']['selinux']['enabled'] { - selboolean { ['allow_httpd_mod_auth_pam', 'httpd_dbus_sssd']: - persistent => true, - value => 'on', + case $foreman::external_authentication { + 'ipa', 'ipa_with_api': { + if $facts['os']['selinux']['enabled'] { + selboolean { ['allow_httpd_mod_auth_pam', 'httpd_dbus_sssd']: + persistent => true, + value => 'on', + } } - } - if $foreman::ipa_manage_sssd { - service { 'sssd': - ensure => running, - enable => true, - require => Package['sssd-dbus'], + if $foreman::ipa_manage_sssd { + service { 'sssd': + ensure => running, + enable => true, + require => Package['sssd-dbus'], + } } - } - file { "/etc/pam.d/${foreman::pam_service}": - ensure => file, - owner => root, - group => root, - mode => '0644', - content => template('foreman/pam_service.erb'), - } + file { "/etc/pam.d/${foreman::pam_service}": + ensure => file, + owner => root, + group => root, + mode => '0644', + content => template('foreman/pam_service.erb'), + } - $http_keytab = pick($foreman::http_keytab, "${apache::conf_dir}/http.keytab") + $http_keytab = pick($foreman::http_keytab, "${apache::conf_dir}/http.keytab") - exec { 'ipa-getkeytab': - command => "/bin/echo Get keytab \ - && KRB5CCNAME=KEYRING:session:get-http-service-keytab kinit -k \ - && KRB5CCNAME=KEYRING:session:get-http-service-keytab /usr/sbin/ipa-getkeytab -k ${http_keytab} -p HTTP/${facts['networking']['fqdn']} \ - && kdestroy -c KEYRING:session:get-http-service-keytab", - creates => $http_keytab, - } - -> file { $http_keytab: - ensure => file, - owner => $apache::user, - mode => '0600', - } + exec { 'ipa-getkeytab': + command => "/bin/echo Get keytab \ + && KRB5CCNAME=KEYRING:session:get-http-service-keytab kinit -k \ + && KRB5CCNAME=KEYRING:session:get-http-service-keytab /usr/sbin/ipa-getkeytab -k ${http_keytab} -p HTTP/${facts['networking']['fqdn']} \ + && kdestroy -c KEYRING:session:get-http-service-keytab", + creates => $http_keytab, + } + -> file { $http_keytab: + ensure => file, + owner => $apache::user, + mode => '0600', + } - $gssapi_local_name = bool2str($foreman::gssapi_local_name, 'On', 'Off') + $gssapi_local_name = bool2str($foreman::gssapi_local_name, 'On', 'Off') - foreman::config::apache::fragment { 'intercept_form_submit': - ssl_content => template('foreman/intercept_form_submit.conf.erb'), - } + foreman::config::apache::fragment { 'intercept_form_submit': + ssl_content => template('foreman/intercept_form_submit.conf.erb'), + } - foreman::config::apache::fragment { 'lookup_identity': - ssl_content => template('foreman/lookup_identity.conf.erb'), - } + foreman::config::apache::fragment { 'lookup_identity': + ssl_content => template('foreman/lookup_identity.conf.erb'), + } - foreman::config::apache::fragment { 'auth_gssapi': - ssl_content => template('foreman/auth_gssapi.conf.erb'), - } + foreman::config::apache::fragment { 'auth_gssapi': + ssl_content => template('foreman/auth_gssapi.conf.erb'), + } - foreman::config::apache::fragment { 'external_auth_api': - ssl_content => template('foreman/external_auth_api.conf.erb'), - } + foreman::config::apache::fragment { 'external_auth_api': + ssl_content => template('foreman/external_auth_api.conf.erb'), + } - if $foreman::ipa_manage_sssd { - $sssd = pick(fact('foreman_sssd'), {}) - $sssd_services = join(unique(pick($sssd['services'], []) + ['ifp']), ', ') - $sssd_ldap_user_extra_attrs = join(unique(pick($sssd['ldap_user_extra_attrs'], []) + ['email:mail', 'lastname:sn', 'firstname:givenname']), ', ') - $sssd_allowed_uids = join(unique(pick($sssd['allowed_uids'], []) + [$apache::user, 'root']), ', ') - $sssd_user_attributes = join(unique(pick($sssd['user_attributes'], []) + ['+email', '+firstname', '+lastname']), ', ') - $sssd_ifp_extra_attributes = [ - "set target[.=~regexp('domain/.*')]/ldap_user_extra_attrs '${sssd_ldap_user_extra_attrs}'", - "set target[.='sssd']/services '${sssd_services}'", - 'set target[.=\'ifp\'] \'ifp\'', - "set target[.='ifp']/allowed_uids '${sssd_allowed_uids}'", - "set target[.='ifp']/user_attributes '${sssd_user_attributes}'", - ] - - $sssd_changes = $sssd_ifp_extra_attributes + ($foreman::ipa_sssd_default_realm ? { - undef => [], - default => ["set target[.='sssd']/default_domain_suffix '${$foreman::ipa_sssd_default_realm}'"], - }) - - augeas { 'sssd-ifp-extra-attributes': - context => '/files/etc/sssd/sssd.conf', - changes => $sssd_changes, - notify => Service['sssd'], + if $foreman::ipa_manage_sssd { + $sssd = pick(fact('foreman_sssd'), {}) + $sssd_services = join(unique(pick($sssd['services'], []) + ['ifp']), ', ') + $sssd_ldap_user_extra_attrs = join(unique(pick($sssd['ldap_user_extra_attrs'], []) + ['email:mail', 'lastname:sn', 'firstname:givenname']), ', ') + $sssd_allowed_uids = join(unique(pick($sssd['allowed_uids'], []) + [$apache::user, 'root']), ', ') + $sssd_user_attributes = join(unique(pick($sssd['user_attributes'], []) + ['+email', '+firstname', '+lastname']), ', ') + $sssd_ifp_extra_attributes = [ + "set target[.=~regexp('domain/.*')]/ldap_user_extra_attrs '${sssd_ldap_user_extra_attrs}'", + "set target[.='sssd']/services '${sssd_services}'", + 'set target[.=\'ifp\'] \'ifp\'', + "set target[.='ifp']/allowed_uids '${sssd_allowed_uids}'", + "set target[.='ifp']/user_attributes '${sssd_user_attributes}'", + ] + + $sssd_changes = $sssd_ifp_extra_attributes + ($foreman::ipa_sssd_default_realm ? { + undef => [], + default => ["set target[.='sssd']/default_domain_suffix '${$foreman::ipa_sssd_default_realm}'"], + }) + + augeas { 'sssd-ifp-extra-attributes': + context => '/files/etc/sssd/sssd.conf', + changes => $sssd_changes, + notify => Service['sssd'], + } + } + + foreman::settings_fragment { 'authorize_login_delegation.yaml': + content => template('foreman/settings-external-auth.yaml.erb'), + order => '02', } - } - foreman::settings_fragment { 'authorize_login_delegation.yaml': - content => template('foreman/settings-external-auth.yaml.erb'), - order => '02', + foreman::settings_fragment { 'authorize_login_delegation_api.yaml': + content => template('foreman/settings-external-auth-api.yaml.erb'), + order => '03', + } } + 'keycloak': { + $foreman_socket_override = undef + + unless $foreman::ssl { + fail('Keycloak requires HTTPS') + } - foreman::settings_fragment { 'authorize_login_delegation_api.yaml': - content => template('foreman/settings-external-auth-api.yaml.erb'), - order => '03', + foreman::settings_fragment { 'authorize_login_delegation.yaml': + content => template('foreman/settings-external-auth.yaml.erb'), + order => '02', + } + + # TODO: parameter + $keycloak_url = 'https://keycloak.example.com' + $oidc_issuer = "${keycloak_url}/auth/realms/${foreman::keycloak_realm}" + $keycloak_settings = { + ':login_delegation_logout_url' => "${foreman::foreman_url}/users/extlogout", + # TODO: parameters or obtain from ${oidc_issuer}/.well-known/openid-configuration + ':oidc_algorithm' => 'RS256', + ':oidc_audience' => ["${foreman::servername}-foreman-openidc"], + ':oidc_issuer' => $oidc_issuer, + ':oidc_jwks_url' => "${oidc_issuer}/protocol/openid-connect/certs", + } + + foreman::settings_fragment { 'authorize_login_delegation-keycloak.yaml': + # TODO: does this include the document marker? + content => stdlib::to_yaml($keycloak_settings), + order => '04', + } + } + default: { + $foreman_socket_override = undef } } - } else { - $foreman_socket_override = undef } systemd::dropin_file { 'foreman-socket': diff --git a/manifests/config/apache.pp b/manifests/config/apache.pp index 5651470be..5e445df1b 100644 --- a/manifests/config/apache.pp +++ b/manifests/config/apache.pp @@ -68,8 +68,8 @@ # @param access_log_format # Apache log format to use # -# @param ipa_authentication -# Whether to install support for IPA authentication +# @param external_authentication +# The authentication type to use # # @param http_vhost_options # Direct options to apache::vhost for the http vhost @@ -77,9 +77,6 @@ # @param https_vhost_options # Direct options to apache::vhost for the https vhost # -# @param keycloak -# Whether to enable keycloak support -# # @param keycloak_app_name # The app name as passed to keycloak-httpd-client-install # @@ -112,10 +109,9 @@ Optional[String] $user = undef, Optional[Stdlib::HTTPUrl] $foreman_url = undef, Optional[String] $access_log_format = undef, - Boolean $ipa_authentication = false, Hash[String, Any] $http_vhost_options = {}, Hash[String, Any] $https_vhost_options = {}, - Boolean $keycloak = false, + Optional[Enum['ipa', 'ipa_with_api', 'keycloak']] $external_authentication = undef, String[1] $keycloak_app_name = 'foreman-openidc', String[1] $keycloak_realm = 'ssl-realm', Array[String[1]] $request_headers_to_unset = [ @@ -236,31 +232,56 @@ include apache include apache::mod::headers - if $ipa_authentication { - include apache::mod::authnz_pam - include apache::mod::auth_basic - include apache::mod::intercept_form_submit - include apache::mod::lookup_identity - include apache::mod::auth_gssapi - } elsif $keycloak { - include apache::mod::auth_openidc + case $external_authentication { + 'ipa', 'ipa_with_api': { + include apache::mod::authnz_pam + include apache::mod::auth_basic + include apache::mod::intercept_form_submit + include apache::mod::lookup_identity + include apache::mod::auth_gssapi + } + 'keycloak': { + include apache::mod::auth_openidc - # This file is generated by keycloak-httpd-client-install and that manages - # the content. The command would be: - # - # keycloak-httpd-client-install --app-name ${keycloak_app_name} --keycloak-server-url $KEYCLOAK_URL --keycloak-admin-username $KEYCLOAK_USER --keycloak-realm ${keycloak_realm} --keycloak-admin-realm master --keycloak-auth-role root-admin --client-type openidc --client-hostname ${servername} --protected-locations /users/extlogin - # - # If $suburi is used, --location-root should also be passed in - # - # By defining it here we avoid purging it and also tighten the - # permissions so the world can't read its secrets. - # This is functionally equivalent to apache::custom_config without content/source - file { "${apache::confd_dir}/${keycloak_app_name}_oidc_keycloak_${keycloak_realm}.conf": - ensure => file, - owner => 'root', - group => 'root', - mode => '0640', + # TODO: parameter + $use_keycloak_httpd_client_install = true + if $use_keycloak_httpd_client_install { + # This file is generated by keycloak-httpd-client-install and that manages + # the content. The command would be: + # + # keycloak-httpd-client-install --app-name ${keycloak_app_name} --keycloak-server-url $KEYCLOAK_URL --keycloak-admin-username $KEYCLOAK_USER --keycloak-realm ${keycloak_realm} --keycloak-admin-realm master --keycloak-auth-role root-admin --client-type openidc --client-hostname ${servername} --protected-locations /users/extlogin + # + # If $suburi is used, --location-root should also be passed in + # + # By defining it here we avoid purging it and also tighten the + # permissions so the world can't read its secrets. + # This is functionally equivalent to apache::custom_config without content/source + file { "${apache::confd_dir}/${keycloak_app_name}_oidc_keycloak_${keycloak_realm}.conf": + ensure => file, + owner => 'root', + group => 'root', + mode => '0640', + } + } else { + # TODO: parameters + $oidc_parameters = { + 'OIDCClientID' => '{{ clientid }}', + 'OIDCProviderMetadataURL' => "{{ keycloak_server_url }}/realms/${keycloak_realm}/.well-known/openid-configuration", + 'OIDCCryptoPassphrase' => '{{ crypto_passphrase }}', + 'OIDCClientSecret' => '{{ oidc_client_secret }}', + 'OIDCRedirectURI' => "${foreman_url}/users/extlogin/redirect_uri", + 'OIDCRemoteUserClaim' => '{{ oidc_remote_user_claim }}', + } + # TODO: pass to Apache + $locations = { + '/users/extlogin' => [ + 'AuthType openid-connect', + 'Require valid-user', + ], + } + } } + default: {} } file { "${apache::confd_dir}/${priority}-foreman.d": diff --git a/manifests/init.pp b/manifests/init.pp index a3dde8393..bde24226c 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -44,9 +44,7 @@ # # $initial_location:: Name of an initial location # -# $ipa_authentication:: Enable configuration for external authentication via IPA -# -# $ipa_authentication_api:: Enable configuration for external authentication via IPA for API +# $external_authentication:: Type of external authentication to use # # === Advanced parameters: # @@ -129,7 +127,7 @@ # # $pam_service:: PAM service used for host-based access control in IPA # -# $ipa_manage_sssd:: If ipa_authentication is true, should the installer manage SSSD? You can disable it +# $ipa_manage_sssd:: If external_authentication is ipa or ipa_with_api, should the installer manage SSSD? You can disable it # if you use another module for SSSD configuration # # $ipa_sssd_default_realm:: If ipa_manage_sssd is true, set default_domain_suffix option in sssd configuration to this value @@ -203,10 +201,6 @@ # # === Keycloak parameters: # -# $keycloak:: Enable Keycloak support. Note this is limited -# to configuring Apache and still relies on manually -# running keycloak-httpd-client-install -# # $keycloak_app_name:: The app name as passed to keycloak-httpd-client-install # # $keycloak_realm:: The realm as passed to keycloak-httpd-client-install @@ -257,8 +251,7 @@ Optional[String] $initial_admin_timezone = undef, Optional[String] $initial_organization = undef, Optional[String] $initial_location = undef, - Boolean $ipa_authentication = false, - Boolean $ipa_authentication_api = false, + Optional[Enum['ipa', 'ipa_with_api', 'keycloak']] $external_authentication = undef, Optional[Stdlib::Absolutepath] $http_keytab = undef, Boolean $gssapi_local_name = true, String $pam_service = 'foreman', @@ -301,7 +294,6 @@ Integer[0] $foreman_service_puma_threads_max = 5, Optional[Integer[0]] $foreman_service_puma_workers = undef, Hash[String, Any] $rails_cache_store = { 'type' => 'redis' }, - Boolean $keycloak = false, String[1] $keycloak_app_name = 'foreman-openidc', String[1] $keycloak_realm = 'ssl-realm', Boolean $register_in_foreman = true, @@ -331,16 +323,8 @@ if $apache { Class['foreman::database'] -> Class['apache::service'] - if $ipa_authentication and $keycloak { - fail("${facts['networking']['hostname']}: External authentication via IPA and Keycloak are mutually exclusive.") - } - if !$ipa_authentication and $ipa_authentication_api { - fail("${facts['networking']['hostname']}: External authentication for API via IPA requires general external authentication to be enabled.") - } - } elsif $ipa_authentication { - fail("${facts['networking']['hostname']}: External authentication via IPA can only be enabled when Apache is used.") - } elsif $keycloak { - fail("${facts['networking']['hostname']}: External authentication via Keycloak can only be enabled when Apache is used.") + } elsif $external_authentication { + fail("${facts['networking']['hostname']}: External authentication can only be enabled when Apache is used.") } if $foreman::register_in_foreman { diff --git a/manifests/install.pp b/manifests/install.pp index 1fcc0ae36..e718dfbc3 100644 --- a/manifests/install.pp +++ b/manifests/install.pp @@ -20,7 +20,7 @@ } } - if $foreman::ipa_authentication and $foreman::ipa_manage_sssd { + if $foreman::external_authentication =~ /ipa/ and $foreman::ipa_manage_sssd { package { 'sssd-dbus': ensure => installed, } diff --git a/spec/acceptance/foreman_basic_spec.rb b/spec/acceptance/foreman_basic_spec.rb index 2655c0874..40e59ba0d 100644 --- a/spec/acceptance/foreman_basic_spec.rb +++ b/spec/acceptance/foreman_basic_spec.rb @@ -43,11 +43,10 @@ class { 'foreman': let(:manifest) do <<-PUPPET class { 'foreman': - ipa_authentication => true, - ipa_authentication_api => true, + external_authentication => 'ipa_with_api', # Stub out ipa enrollment - http_keytab => '/etc/httpd/conf.keytab', - ipa_manage_sssd => false, + http_keytab => '/etc/httpd/conf.keytab', + ipa_manage_sssd => false, } PUPPET end diff --git a/spec/classes/foreman_config_ipa_spec.rb b/spec/classes/foreman_config_ipa_spec.rb index 4dac145d9..9d9b41106 100644 --- a/spec/classes/foreman_config_ipa_spec.rb +++ b/spec/classes/foreman_config_ipa_spec.rb @@ -4,13 +4,13 @@ on_supported_os.each do |os, facts| context "on #{os}" do let(:facts) { facts.merge(interfaces: '') } - let(:params) { { ipa_authentication: true } } + let(:params) { { external_authentication: 'ipa' } } keytab_path = facts[:osfamily] == 'RedHat' ? '/etc/httpd/conf/http.keytab' : '/etc/apache2/http.keytab' describe 'without apache' do let(:params) { super().merge(apache: false) } - it { should raise_error(Puppet::Error, /External authentication via IPA can only be enabled when Apache is used/) } + it { should compile.and_raise_error(/External authentication can only be enabled when Apache is used/) } end context 'with apache' do @@ -61,7 +61,7 @@ end context 'with GSSAPI auth for API' do - let(:params) { super().merge(ipa_authentication_api: true) } + let(:params) { super().merge(external_authentication: 'ipa_with_api') } it 'should contain GSSAPI and Basic coniguration in API fragment' do should contain_foreman__config__apache__fragment('external_auth_api') diff --git a/spec/classes/foreman_spec.rb b/spec/classes/foreman_spec.rb index 35e4a2d65..a3c95b4a9 100644 --- a/spec/classes/foreman_spec.rb +++ b/spec/classes/foreman_spec.rb @@ -75,8 +75,7 @@ # apache it 'should contain foreman::config::apache' do - should contain_class('foreman::config::apache') - .with_keycloak(false) + should contain_class('foreman::config::apache').without_external_authentication end it do @@ -225,7 +224,6 @@ initial_admin_timezone: 'Hawaii', initial_organization: 'acme', initial_location: 'acme', - ipa_authentication: false, http_keytab: '/etc/httpd/conf.keytab', pam_service: 'foreman', ipa_manage_sssd: true, @@ -245,7 +243,7 @@ email_smtp_password: 'secret', email_reply_address: 'noreply@foreman.domain', email_subject_prefix: '[prefix]', - keycloak: true, + external_authentication: 'keycloak', keycloak_app_name: 'cloak-app', keycloak_realm: 'myrealm', provisioning_ct_location: '/usr/bin/myct', @@ -259,7 +257,7 @@ it { should contain_package('foreman-dynflow-sidekiq').with_ensure('1.12') } it do is_expected.to contain_class('foreman::config::apache') - .with_keycloak(true) + .with_external_authentication('keycloak') .with_keycloak_app_name('cloak-app') .with_keycloak_realm('myrealm') end diff --git a/templates/external_auth_api.conf.erb b/templates/external_auth_api.conf.erb index 3b73b8315..cb21598cc 100644 --- a/templates/external_auth_api.conf.erb +++ b/templates/external_auth_api.conf.erb @@ -1,7 +1,7 @@ SSLRequireSSL - <% if scope.lookupvar('foreman::ipa_authentication_api') %> + <% if scope.lookupvar('foreman::external_authentication') == 'ipa_with_api' %> AuthType Basic AuthName "PAM Authentication"