I have recently explored the topics described in this article in more detail and have prepared an extensive series Kerberos protocol with focus on SSO in AD DS. The last few parts, especially Kerberos part 11 - Apache configuration and use in PHP, are most relevant to this article.
My colleague Ondra worked on setting up Kerberos in Apache and using it in PHP, and he also came up with a solution for transitioning to standard authentication when SSO doesn't work for the client. Unfortunately, there is one crucial condition: Apache must run on Linux. We were unable to find a single Kerberos library (or any other for using SSO) for Apache on Windows. However, on Windows, we can use a different application server, such as IIS or Tomcat, where we have SSO libraries available.
Related articles are Kerberos protocol and Single sign-on and Kerberos SSO - Internet Explorer and Firefox settings.
Creating an Account for the Apache Service in AD
From the principles of Kerberos, we know that each service we want to authenticate to must have a record (account) in Active Directory. And that the application server needs a secret key to be able to decrypt the communication that will come to it. Because our application server is Linux, we cannot normally join it to the domain, but we must manually create an account and export the encryption key.
The procedure described below can be used on Windows Server 2000, 2003, 2008, and 2008 R2. The only differences are some details, such as the supported encryption algorithms. The examples given are tested on Windows Server 2008 R2. The ktpass command used is available in the Windows 2000 Resource Kit, Windows 2003 Support Tools, and is included in the tools installed on the domain controller on Server 2008. Always use the version corresponding to the domain level.
Creating a DNS Record
If we don't have one yet, we must first create a DNS record for the server/service (the web server address, e.g., mywebsite.domain.local).
Creating a User Account in AD
We need to sort of join our Linux server to the domain, so it would seem we should create a computer account. In reality, we only want a service account, which we cannot create separately, but we can link it to a user account. So we create a user account for our server/service.
- for example, using Active Directory Users and Computers, we create a user account in AD
- this account must be placed in the default Users container
- we can choose the same name (not required) as the server name (e.g., mywebsite)
- of course, such an account must not already exist, and we must not set a password change requirement
Setting SPN and Exporting the Keytab File
Service Principal Name (SPN) is the name of the service that the client (web browser) will call when we want to perform Kerberos authentication to the web. The SPN is tied to the account (user, computer, group). We could create it using the setspn command, but we'll use the ktpass command instead, which will also create the keytab file for us. For a web application, the SPN has the form HTTP/<hostname> (i.e., HTTP/mywebsite.domain.local), where the hostname must match the server's DNS address. This also applies if we use the HTTPS protocol (it's still HTTP/hostname). We will associate this SPN with our created user (mywebsite@domain.local).
Note: The hostname in the SPN must match the A record in DNS and not an Alias (CNAME). For example, if we have a server web.domain.local with the same name in DNS and an alias www.domain.local, we must put web.domain.local in the SPN, even though users use the alias to access it.
Using the ktpass command, we will map the SPN and at the same time export the Kerberos keytab file. The syntax and an example for our situation:
ktpass -out <filename> -princ HTTP/<hostname>@<AD DOMAIN NAME IN UPPERCASE> -mapUser <username>@<domain name> -mapOp set -pass * -ptype KRB5_NT_PRINCIPAL -kvno 0 ktpass -out mywebsite.keytab -princ HTTP/mywebsite.domain.local@DOMAIN.LOCAL -mapUser mywebsite@domain.local -mapOp set -pass * -ptype KRB5_NT_PRINCIPAL -kvno 0
The output keytab file (mywebsite.keytab) contains the SPN and the service's secret key. The Principal name (princ parameter) consists of the SPN and the specification of the domain against which we will authenticate the user (must be in uppercase). MapOp specifies that the SPN will be set on the account (not added). If we want to save the keytab file, we must enter a password. This password will be set on the user account (the original password will be changed). When we enter an asterisk, we are then prompted for the password.
At the moment when we create the keytab file, the user account must be in the Users container, otherwise we will get the following error message (perhaps a bug in Windows Server 2008 R2). After creating the file, we can move the account to any organizational unit.
Password set failed! 0x00000020 Aborted.
It is important to ensure the security of the keytab file, as it contains login credentials for AD on behalf of the given service. You can find some information in MS articles on Service Principal Names and the Ktpass tool.
Added on 8.4.2013 - Firefox 20.0 was released and SSO set up according to this description no longer worked. Instead of logging in, the HTTP authentication dialog was displayed. After a whole day of investigation, I found that the new Firefox has a problem with the server being named mywebsite.domain.local and using this name in the SPN, but accessing the web using www.domain.local. When I created a new Keytab file and used www.domain.local, SSO started working in Firefox, but then stopped working in Internet Explorer. So I also had to change the DNS records so that the A record was www.domain.local and the CNAME was mywebsite.domain.local. After a few minutes, everything started working.
Kerberos Module for Apache - mod_auth_kerb
We tested on Oracle Enterprise Linux 5.5 (OEL). We chose the most widespread library mod_auth_kerb (Kerberos Module for Apache, whose package is part of OEL).
- we install the
mod_auth_kerbpackage and enable it in Apache (if it doesn't enable automatically) inhttpd.conf
LoadModule auth_kerb_module modules/mod_auth_kerb.so
- we upload the keytab file (mywebsite.keytab) to the server and place it in a secure directory so that Apache has access to it
- we set the parameters for the directory in Apache where we want to use authentication
Configuring the Apache Directory
The configuration can be done using the .htaccess file or using the Directory or Location directive, either in the context of the entire web server or within a specific Virtual Host, typically in the httpd.conf file. We set the parameters for a specific directory where we will place the login script (if we use the standard login method and the data is further in the session, otherwise we can also set it for the entire web and each page will authenticate). Example configuration:
<Directory /var/www/mywebsite/sso/> AuthName "Kerberos Login" AuthType Kerberos KrbAuthRealms DC.DOMAIN.LOCAL DOMAIN.LOCAL KrbServiceName HTTP/mywebsite.domain.local@DOMAIN.LOCAL Krb5KeyTab /etc/apache2/mywebsite.keytab require valid-user </Directory>
- AuthType Kerberos - we must specify that Kerberos authentication should be used
- KrbServiceName - must exactly match the principal name we used when generating the keytab file
- Krb5KeyTab - path to the keytab file stored in the file system
For even greater security, we can require the use of HTTPS on the directory by adding the directive:
SSLRequireSSL
In the Apache configuration, we also need to have the full name (FQDN, address) of the server set, either for the entire web or for the given Virtual Host (which we probably have):
ServerName mywebsite.domain.local
Using SSO in PHP
We described the communication principle (Kerberos authentication) in the previous article, now we'll just recap the exchange of information between the client (browser) and the web server.
- The client (web browser) requests a page (GET request).
- It is in a directory where we require Kerberos authentication, so the server responds in the HTTP header that it requires authentication (HTTP/1.1 401 Authorization Required, WWW-Authenticate: Negotiate, WWW-Authenticate: Basic realm="Kerberos Login").
- If the client supports Kerberos, it sends the authentication credentials in the header (which is a service ticket, Authorization: Negotiate followed by encrypted content). If not, it doesn't respond in any way, but displays the HTTP authentication window (this is due to additional data it received from the server). On Apache, it is possible to set multiple authentication methods that will be used if one fails.
- If everything is fine, the server sends confirmation again in the header (HTTP/1.1 302 Found, WWW-Authenticate: Negotiate encrypted server data).
If successful login occurs, Apache stores the user's name (username@domain, e.g., user@DOMAIN.LOCAL) in the server variable $_SERVER['REMOTE_USER'], from which we can easily retrieve it in PHP.
Simple use is to place the login script in a special directory and apply the above-described Apache settings to it. When a user tries to open such a page, SPNEGO (Kerberos) authentication will take place. If this fails, the script will not be invoked at all, but the server will send an error. Therefore, we can assume in the script that SSO was successful and only check the username. A very simple script /sso/login.php could look like this.
<?php
session_start();
$_SESSION['username'] = $_SERVER['REMOTE_USER'];
header('Location: /');
?>
A more complicated situation, however, is when we want to ensure that in case of SSO failure (for example, the user doesn't have it enabled on the workstation), it falls back to a classic form-based authentication. Normally, when SSO fails, the browser window pops up with HTTP authentication (which doesn't work in our setup) and when we click Cancel or enter something, an error page is displayed. The fact that the HTTP authentication window normally pops up when SSO fails is because Apache sends the header
WWW-Authenticate: Basic realm='Kerberos Login'
Headers that Apache sends during Kerberos authentication, we can create manually and test if the response arrives (i.e., SSO is supported). In that case, we perform the SSO authentication, otherwise we display the classic authentication through the form. The following script /login.php schematically shows this method. It works on the principle that if the browser supports SSO for the given page, it accepts the request for negotiating authentication in the header and no longer displays the received page, but sends the authentication credentials to the same address. Otherwise, it displays the received page, so we can insert a standard login form into it.
<?php
session_start();
$headers = apache_request_headers();
if(empty($headers['Authorization'])) {
header("HTTP/1.1 401 Authorization Required");
header("WWW-Authenticate: Negotiate");
// SSO není podporováno, zobrazím standardní login či informace
echo "SSO autentizace neprobehla.";
exit;
} else {
// SSO OK, přesměruju na SSO login
header('Location: /SSO/login.php');
exit;
}
?>
Ahoj,
pěkný článek na téma SSO. Nechybí zde ale část o vlastní konfiguraci kerberos klienta na straně linuxového serveru?
V mém případě vypadá takto:
[logging]
default = FILE:///var/log/krb5libs.log
kdc = FILE:///var/log/krb5kdc.log
admin_server = FILE:///var/log/kadmind.log
[libdefaults]
default_realm = DEVELOPMENT.TIMEIMPORT.CZ
dns_lookup_realm = true
dns_lookup_kdc = true
ticket_lifetime = 3600h
forwardable = yes
default_keytab_name = FILE:///etc/krb5.keytab
[realms]
DEVELOPMENT.TIMEIMPORT.CZ = {
kdc = andromeda.development.timeimport.cz:88
admin_server = andromeda.development.timeimport.cz:749
default_domain = development.timeimport.cz
}
[domain_realm]
prometheus.development.timeimport.cz = DEVELOPMENT.TIMEIMPORT.CZ
.development.timeimport.cz = DEVELOPMENT.TIMEIMPORT.CZ
[appdefaults]
pam = {
debug = false
ticket_lifetime = 36000
renew_lifetime = 36000
forwardable = true
krb4_convert = false
}
respond to [1]Ladislav Jech: Moc jsem to nepochopil, co je to za konfiguraci a čeho se týká? Pro web server nic takového nepotřebuji.
respond to [2]Samuraj: Tohle se hodí v případě, že "něco" nefunguje tak jak má, je to prakticky zrcadlení konfiguračních informací z modulu kerberos pro apache do konfiguráku linuxového kerberos klienta, který je na CentOS v lokaci /etc/krb5.conf (verze 5), a pomocí kterého lze pak různé indispozice požadované funkcionality ladit. Klient podporuje následující příkazy: kinit, klist, kvno, kdestroy, ksu, kpasswd.
Samozřejmě mód jako takový funguje i bez tohoto, a veškeré informace z modulu jdou do apache logu access_log, případně ssl_access.
To je jen jako extra, návod je perfektní a funkční.;-)
Super článek. Na netu se na toto téma najde dost informací ale žádný zdroj nebyl takhle ucelený.
Rozchodil jsem to bez problémů, ale Internet explorer mě zlobí (ve Firefoxu no problem). Když mu pošlu
header("HTTP/1.1 401 Authorization Required");
header("WWW-Authenticate: Negotiate");
a on pro danou stránku SSO nepodporuje, tak stejně zobrazí ten ošklivý dialog, který chce jméno a heslo. až po kliknutí na "cancel" zobrazí můj formulář. Neexistuje nějaký trik, aby se IE choval taky takhle? Diky
Ve firmě to používám tak, jak je zde popsáno, a funguje to OK v IE i ve FF. Netuším, kde může být problém.
Máte ten skript v adresáři, na kterém není nastaveno SSO v Apachi?
respond to [5]Samuraj: Děkuji za odpověď, mám ten skript v adresáři, kde není nastaveno SSO. Teď jsem provedl i test na serveru, kde SSO není vůbec. Tohle je komunikace IE a serveru zachycená Wiresharkem:
GET /xsso.php HTTP/1.1
Accept: */*
Accept-Language: cs-CZ
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; MALC)
Accept-Encoding: gzip, deflate
Host: www.diktatorek.cz
Connection: Keep-Alive
HTTP/1.1 401 Authorization Required
Content-Type: text/html
Server: Microsoft-IIS/7.0
X-Powered-By: PHP/5.2.11
WWW-Authenticate: Negotiate
X-Powered-By: ASP.NET
Date: Fri, 11 Feb 2011 19:19:09 GMT
Content-Length: 66
HTML formular = (odstranil jsem)
Na základě těchto dat IE zobrazí standardní autentikační dialog, Firefox rovnou zobrazí to mojo HTML.
Nevíte prosím o nějaké veřejně dostupné stránce, která by tento přístup používala? Podíval bych se co přesně server posílá a zařídil bych se podle toho. (potřebuju pouze aby se mi zobrazil login formulář.) Děkuju moc
respond to [6]Josef Andrýsek: Tak jsem to testoval a u mě v IE vyskočí to dialogové okno pouze, pokud se v hlavičce posílá:
WWW-Authenticate: Basic realm="
To tam posílá Apache, když přistupuji do adresáře, kde je nastaveno SSO.
Na své testovací stránce to posílám bez tohoto parametru a IE 8.0 zareaguje zobrazením html, které je součástí dat.
respond to [7]Samuraj: Děkuju, možná bude záležet na verzi I.E., nebo na nějakém dalším nastavení. Není ta Vaše testovací stránka na nějaké veřejné adrese? Jen abych měl 100% jistotu, že nedělám něco špatně. Diky
respond to [8]Josef Andrysek: Tak ten skript, který je na konci článku jsem uložil na adresu http://www.samuraj-cz.com/download/test-sso.php, pouze místo redirectu zobrazuji info. U mě to chodí správně, nevyskakuje žádné okno.
respond to [9]Samuraj: Děkuju moc, bohužel ten skript nevyhodí v clientu žádný dialog proto, že posílá nevalidní hlavičku:
WWW-Authenticate: Negotiate realm="20041"
IE 8 zobrazi rovnou chybovou hlasku. IE 6 zobrazi formular tak jak jsem chtel, ale nepokusi se o SSO, ikdyz muze. :-(
Nechci Vas uz s tim otravovat. Kazdeopadne Diky
respond to [10]Josef Andrýsek: Tak nevím, proč to tam doplňuje, ve skriptu to není a na produkčním prostředí, kde to používáme úplně stejně, to neposílá.
Každopádně, mě se to v IE8 zachová správně a zobrazí ten text, že není podporováno SSO.
respond to [11]samuraj: Tak tohle dela zapnutej safe mod v PHP. Kdyz ho vypnu, zustane tam jen spravne WWW-Authenticate: Negotiate