I have a environment where hard coded password are avoided. We prefer to use Kerberos. We also provided a SSO for Web UI using CAS. We use ActiveDirectory for users backend.

So I wanted a oVirt installation that will use kerberos for API authentication. For the web UI, Kerberos is not always the best solution, so I wanted to integrated it in our CAS.

The Apache part was easy to setup. It needs an external module, auth_cas_module, that can be found at Apache’s CAS module. It builds without special tweaks with

./configure
make
make install

I will show only subpart of the whole Apache setup and only authentication related part

# The CAS modules
LoadModule authz_user_module      /usr/lib64/httpd/modules/mod_authz_user.so
# Needed because auth_cas_module forget to link openssl
LoadModule ssl_module            /usr/lib64/httpd/modules/mod_ssl.so
LoadModule auth_cas_module       /usr/lib64/httpd/modules/mod_auth_cas.so

# For the kerberos authentication on the API
LoadModule auth_gssapi_module    /usr/lib64/httpd/modules/mod_auth_gssapi.so
LoadModule session_module        /usr/lib64/httpd/modules/mod_session.so
LoadModule session_cookie_module /usr/lib64/httpd/modules/mod_session_cookie.so

CASLoginURL https://sso/cas/login
CASValidateSAML On
CASValidateURL https://sso/cas/samlValidate

<VirtualHost *:443>

    RequestHeader unset X-Remote-User early

    <LocationMatch ^/api($|/)>
        RequestHeader set X-Remote-User %{REMOTE_USER}s

        RewriteEngine on
        RewriteCond %{LA-U:REMOTE_USER} ^(.*@DOMAIN)$
        RewriteRule ^(.*)$ - [L,P,E=REMOTE_USER:%1]

        AuthType GSSAPI
        AuthName "GSSAPI Single Sign On Login"
        GssapiCredStore keytab:.../httpd.keytab
        Require valid-user
        GssapiUseSessions On
        Session On
        SessionCookieName ovirt_gssapi_session path=/private;httponly;secure;
    </LocationMatch>

    <LocationMatch ^/(ovirt-engine($|/)|RHEVManagerWeb/|OvirtEngineWeb/|ca.crt$|engine.ssh.key.txt$|rhevm.ssh.key.txt$)>
        AuthType CAS
        Require valid-user
        CASAuthNHeader X-Remote-User
    </LocationMatch>
</VirtualHost>

The file httpd.keytab contains the kerberos ticket for the service HTTP. In my setup, the realm using for Linux machine is different
than the active directory’s domain, and a trust was established between them. So the keytab is created using MIT kerberos.

It was generated using the following kadmin commands:

addprinc -randkey HTTP/VHOST@REALM
addprinc -randkey HTTP/FQDN@REALM
ktadd -k .../http.keytab -e aes128-cts-hmac-sha1-96:normal -e aes256-cts-hmac-sha1-96:normal HTTP/VHOST@REALM
ktadd -k .../http.keytab -e aes128-cts-hmac-sha1-96:normal -e aes256-cts-hmac-sha1-96:normal HTTP/FQDN@REALM

Kerberos can be surprising when resolving principal and http client uses different method. Some requests an ticket using directly the Host header. Some other choose the reverse of the IP used for the connection.
So if Apache is configured using a virtual host, both principal for the virtual host and the FQDN pointed by the reverse of the IP should be created and added to the keytab.

The authn file /etc/ovirt-engine/extensions.d/apachesso-authn.properties is :

ovirt.engine.extension.name = apachesso-authn
ovirt.engine.extension.bindings.method = jbossmodule
ovirt.engine.extension.binding.jbossmodule.module = org.ovirt.engine-extensions.aaa.misc
ovirt.engine.extension.binding.jbossmodule.class = org.ovirt.engineextensions.aaa.misc.http.AuthnExtension
ovirt.engine.extension.provides = org.ovirt.engine.api.extensions.aaa.Authn
ovirt.engine.aaa.authn.profile.name = apachesso
ovirt.engine.aaa.authn.authz.plugin = DOMAIN-authz
config.artifact.name = HEADER
config.artifact.arg = X-Remote-User

And the authz file /etc/ovirt-engine/extensions.d/DOMAIN-authz.properties is:

ovirt.engine.extension.name = DOMAIN-authz
ovirt.engine.extension.bindings.method = jbossmodule
ovirt.engine.extension.binding.jbossmodule.module = org.ovirt.engine-extensions.aaa.ldap
ovirt.engine.extension.binding.jbossmodule.class = org.ovirt.engineextensions.aaa.ldap.AuthzExtension
ovirt.engine.extension.provides = org.ovirt.engine.api.extensions.aaa.Authz
config.profile.file.1 = ..../aaa/DOMAIN.properties

I had some difficulties with AD backend. A straightforward solution would have been :

include = <ad.properties>

vars.domain = DOMAIN
vars.user = BINDDN
vars.password = BINDPWD
vars.forest = domain.com

pool.default.auth.simple.bindDN = ${global:vars.user}
pool.default.auth.simple.password = ${global:vars.password}
pool.default.serverset.type = srvrecord
pool.default.serverset.srvrecord.domain = ${global:vars.domain}

pool.default.ssl.startTLS = true
pool.default.ssl.truststore.file = .../domain.jks
pool.default.ssl.truststore.password =
# Only TLSv1.2 is secure nowadays
pool.default.ssl.startTLSProtocol = TLSv1.2

# long time out should be avoided
pool.default.connection-options.connectTimeoutMillis = 500

But if fails. We have a special setup with about 100 domain controlers and only two of them can be reached from the ovirt engine. So my first try was so defined them directly in the configuration file:

pool.default.serverset.type = failover
pool.default.serverset.failover.1.server = dcX.domain.com
pool.default.serverset.failover.2.server = dcY.domain.com

But that fails. oVirt-engine was still using a lot of unreachable domain controler. After some digging I found that other part of the ldap extension use a different serverset, I don’t know why it don’t reuse the default pool. It’s called pool.default.dc-resolve (it should be called pool.dc-resolve, as it’s not the default but a custom one), so I added in my configuration:

pool.default.dc-resolve.default.serverset.type = failover
pool.default.dc-resolve.serverset.failover.1.server = dcX.domain.com
pool.default.dc-resolve.serverset.failover.2.server = dcY.domain.com

I worked well, but there is a better solution as Ondra Machacek point it to me. In Active Directory, there is something called a “site”, with a subset of all the domain controler in it. It can be found under CN=Sites,CN=Configuration,DC=DOMAIN,....

To list them:

ldapsearch -H ldap://somedc -b CN=Sites,CN=Configuration,DC=DOMAIN -s one -o ldif-wrap=no cn

The information to write down is the cn returned

You get a list of all sites, just pick the right one, remove all the serverset configuration and add :

pool.default.serverset.srvrecord.domain-conversion.type = regex
pool.default.serverset.srvrecord.domain-conversion.regex.pattern = ^(?<domain>.*)$
pool.default.serverset.srvrecord.domain-conversion.regex.replacement = GOOD_SITE._sites.${domain}

The entry _sites.${domain} don’t exist in the DNS, so to check that your regex is good, try instead:

dig +short _ldap._tcp.GOOD_SITE._sites.${domain} srv

It should return only reachable domain controlers.

So the final /etc/ovirt-engine/aaa/DOMAIN.properties was :

include = <ad.properties>

vars.domain = DOMAIN
vars.user = BINDDN
vars.password = BINDPWD
vars.forest = domain.com

pool.default.auth.simple.bindDN = ${global:vars.user}
pool.default.auth.simple.password = ${global:vars.password}
pool.default.serverset.type = srvrecord
pool.default.serverset.srvrecord.domain = ${global:vars.domain}

pool.default.ssl.startTLS = true
pool.default.ssl.truststore.file = .../domain.jks
pool.default.ssl.truststore.password =
pool.default.ssl.startTLSProtocol = TLSv1.2

pool.default.connection-options.connectTimeoutMillis = 500

pool.default.serverset.srvrecord.domain-conversion.type = regex
pool.default.serverset.srvrecord.domain-conversion.regex.pattern = ^(?<domain>.*)$
pool.default.serverset.srvrecord.domain-conversion.regex.replacement = GOOD_SITE._sites.${domain}

With this setup, my python client can connect to ovirt-engine using kerberos ticket, web users are authenticated using CAS. And there is no need to duplicate user base.