################################################
Federated Authentication Utilizing Apache & SSSD
################################################
:Author: John Dennis
:Email: jdennis@redhat.com
.. contents:: Table of Contents
************
Introduction
************
Applications should not need to handle the burden of authentication
and authorization. These are complex technologies further complicated
by the existence of a wide variety of authentication
mechanisms. Likewise there are numerous identity providers (IdP) which
one may wish to utilize, perhaps in a federated manner. The potential
to make critical mistakes are high while consuming significant
engineering resources. Ideally an application should "outsource" it's
authentication to an "expert" and avoid unnecessary development costs.
For web based applications (both conventional HTML and REST API) there
has been a trend to embed a simple HTTP server in the application or
application server which handles the HTTP requests eschewing the use
of a traditional web server such as Apache.
.. figure:: sssd_01.png
:align: center
_`Figure 1.`
But traditional web servers have a lot of advantages. They often come
with extensive support for technologies you might wish to utilize in
your application. It would require signification software engineering
to add support for those technologies in your application. The problem
is compounded by the fact many of these technologies demand domain
expertise which is unlikely to be available in the application
development team. Another problem is the libraries needed to utilize
the technology may not even be available in the programming language
the application is being developed in. Fundamentally an application
developer should focus on developing their application instead of
investing resources into implementing complex code for the ancillary
technologies the application may wish to utilize.
Therefore fronting your application with a web server such as Apache
makes a lot of sense. One should allow Apache to handle complex tasks
such as multiple authentication mechanisms talking to multiple
IdP's. Suppose you want your application to handle Single Sign-On
(SSO) via Kerberos or authentication based on X509 certificates
(i.e. PKI). Apache already has extensions to handle these which have
been field proven, it would be silly to try and support these in your
application. Apache also comes with other useful extensions such as
``mod_identity_lookup`` which can extract metadata about an
authenticated user from multiple sources such as LDAP,
Active Directory, NIS, etc.
By fronting your application with Apache and allowing Apache to handle
the complex task of authentication, identity lookups etc. you've
greatly increased the features of your application while at the same
time reducing application development time along with increasing
application security and robustness.
.. figure:: sssd_02.png
:align: center
_`Figure 2.`
When Apache fronts your application you will be passed the results of
authentication and identity lookups. Your application only needs a
simple mechanism to accept these values. There are a variety of ways
the values can be passed from Apache to your application which will be
discussed in later sections.
Authentication & Identity Properties
====================================
Authentication is proving that a user is who they claim to be, in
other words after authentication the user has a proven identity. In
security parlance the authenticated entity is call a
principal. Principals may be humans, machines or
services. Authorization is distinct from authentication. Authorization
declares what actions an authenticated principal may perform. For
example, does a principal have permission to read a certain file, run
a specific command, etc. Identity metadata is typically bound to the
principal to provide extra information. Examples include the users
full name, their organization, the groups they are members of, etc.
Apache can provide both authentication and identity metadata to an
application freeing the application of this task. Authorization
usually will remain the province of the application. A typical
design pattern is to assign roles to a principal based on identity
properties. As the application executes on behalf of a principal the
application will check if the principal has the necessary role needed
to perform the operation.
Apache ships with a wide variety of authentication modules. After an
Apache authentication module successfully authenticates a principal, it
sets internal variables identifying the principal and the
authentication method used to authenticate the principal. These are
exported as the CGI variables REMOTE_USER and AUTH_TYPE respectively
(see `CGI Export Issues`_ for further information).
Identity Properties
-------------------
Most Apache authentication modules do not have access to any of the
identity properties bound to the authenticated principal. Those
identity properties must be provided by some other mechanism. Typical
mechanisms include lookups in LDAP, Active Directory, NIS, POSIX
passwd/gecos and SQL. Managing these lookups can be difficult
especially in a networked environment where services may be
temporarily unavailable and/or in a enterprise deployment where
identity sources must be multiplexed across a variety of services
according to enterprise wide policy.
`SSSD`_ (System Security Services Daemon) is designed to alleviate many
of the problems surrounding authentication and identity property
lookup. SSSD can provide identity properties via D-Bus using it's
InfoPipe (IFP) feature. The `mod_identity_lookup`_ Apache module is
given the name of the authenticated principal and makes available
identity properties via Apache environment variables (see `Configure
SSSD IFP`_ for details).
Exporting & Consuming Identity Metadata
=======================================
The authenticated principal (REMOTE_USER), the mechanism used to
authenticate the principal (AUTH_TYPE) and identity properties
(supplied by SSSD IFP) are exported to the application which trusts
this metadata to be valid.
How is this identity metadata exported from Apache and then be
consumed by a Java EE Servlet?
The architectural design inside Apache tries to capitalize on the
existing CGI standard (`CGI RFC`_) as much as possible. CGI defines
these relevant environment variables:
* REMOTE_USER
* AUTH_TYPE
* REMOTE_ADDR
* REMOTE_HOST
Transporting Identity Metadata from Apache to a Java EE Servlet
===============================================================
In following figure we can see that the user connects to Apache
instead of the servlet container. Apache authenticates the user, looks
up the principal's identity information and then proxies the request
to the servlet container. The additional identity metadata must be
included in the proxy request in order for the servlet to extract it.
.. figure:: sssd_03.png
:align: center
_`Figure 3.`
The Java EE Servlet API is designed with the HTTP protocol in mind
however the servlet never directly accesses the HTTP protocol stream.
Instead it uses the servlet API to get access to HTTP request
data. The responsibility for HTTP communication rests with the
container's ``Connector`` objects. When the servlet API needs
information it works in conjunction with the ``Connector`` to supply
it. For example the ``HttpServletRequest.getRemoteHost()`` method
interrogates information the ``Connector`` placed on the internal
request object. Analogously ``HttpServletRequest.getRemoteUser()``
interrogates information placed on the internal request object by an
authentication filter.
But what happens when a HTTP request is proxied to a servlet container
by Apache and ``getRemoteHost()`` or ``getRemoteUser()`` is called? Most
``Connector`` objects do not understand the proxy scenario, to them
a request from a proxy looks just like a request sent directly to the
servlet container. Therefore ``getRemoteHost()`` or ``getRemoteUser()``
ends up returning information relative to the proxy instead of the
user who connected to the proxy because it's the proxy who connected
to the servlet container and not the end user. There are 2 fundamental
approaches which allow the servlet API to return data supplied by the
proxy:
1. Proxy uses special protocol (e.g. AJP) to embed metadata.
2. Metadata is embedded in an HTTP extension by the proxy (i.e. headers)
Proxy With AJP Protocol
-----------------------
The AJP_ protocol was designed as a protocol to exchange HTTP requests
and responses between Apache and a Java EE Servlet Container. One of
its design goals was to improve performance by translating common text
values appearing in HTTP requests to a more compact binary form. At
the same time AJP provided a mechanism to supply metadata about the
request to the servlet container. That metadata is encoded in an AJP
attribute (a name/value pair). The Apache AJP Proxy module looks up
information in the internal Apache request object (e.g. remote user,
remote address, etc.) and encodes that metadata in AJP attributes. On
the servlet container side a AJP ``Connector`` object is aware of these
metadata attributes, extracts them from the protocol and supplies
their values to the upper layers of the servlet API. Thus a call to
``HttpServletRequest.getRemoteUser()`` made by a servlet will receive
the value set by Apache prior to the proxy. This is the desired and
expected behavior. A servlet should be ignorant of the consequences of
proxies; the servlet API should behave the same regardless of the
presence of a proxy.
The AJP protocol also has a general purpose attribute mechanism whereby
any arbitrary name/value pair can be passed. This proxy metadata can
be retrieved by a servlet by calling ``ServletRequest.getAttribute()``
[1]_ When Apache mod_proxy_ajp is being used the authentication
metadata for the remote user and auth type are are automatically
inserted into the AJP protocol and the AJP ``Connector`` object on
the servlet receiving end supplies those values to
``HttpServletRequest.getRemoteHost()`` and
``HttpServletRequest.getRemoteUser()`` respectively. But the identity
metadata supplied by ``mod_identity_lookup`` needs to be explicitly
encoded into an AJP attribute (see `Configure SSSD IFP`_ for details)
that can later be retrieved by ``ServletRequest.getAttribute()``.
Proxy With HTTP Protocol
------------------------
Although the AJP protocol offers a number of nice advantages sometimes
it's not an option. Not all servlet containers support AJP or there
may be some other deployment constraint that precludes its use. In this
case option 2 from above needs to be used. Option 2 requires only the
defined HTTP protocol be used without any "out of band" metadata. The
conventional way to attach extension metadata to a HTTP request is to
add extension HTTP headers.
One problem with using extension HTTP headers to pass metadata to a
servlet is the expectation the servlet API will have the same
behavior. In other words the value returned by
``HttpServletRequest.getRemoteUser()`` should not depend on whether the
proxy request was exchanged with the AJP protocol or the HTTP
protocol. The solution to this is to wrap the ``HttpServletRequest``
object in a servlet filter. The wrapper overrides certain request
methods (e.g. ``getRemoteUser()``). The override method looks to see if
the metadata is in the extension HTTP headers, if so it returns the
value found in the extension HTTP header otherwise it defers to the
existing servlet implementation. The ``ServletRequest.getAttribute()`` is
overridden in an analogous manner in the wrapper filter. Any call to
``ServletRequest.getAttribute()`` is first checked to see if the value
exists in the extension HTTP header first.
Metadata supplied by Apache that is **not** part of the normal Java
EE Servlet API **always** appears to the servlet via the
``ServletRequest.getAttribute()`` method regardless of the proxy
transport mechanism. The consequence of this is a servlet
continues to utilize the existing Java EE Servlet API without concern
for intermediary proxies, *and* any other metadata supplied by a proxy
is *always* retrieved via ``ServletRequest.getAttribute()`` (see the
caveat about ``ServletRequest.getAttributeNames()`` [1]_).
*******************
Configuration Guide
*******************
Although Apache authentication and SSSD identity lookup can operate
with a variety of authentication mechanisms, IdP's and identity
metadata providers we will demonstrate a configuration example which
utilizes the FreeIPA_ IdP. FreeIPA excels at Kerberos SSO authentication,
Active Directory integration, LDAP based identity metadata storage and
lookup, DNS services, host based RBAC, SSH key management, certificate
management, friendly web based console, command line tools and many
other advanced IdP features.
The following configuration steps will need to be performed:
1. Install FreeIPA_ by following the installation guides in the FreeIPA_
documentation area. When you install FreeIPA_ you will need to select a
realm (a.k.a domain) in which your users and hosts will exist. In
our example we will use the ``EXAMPLE.COM`` realm.
2. Install and configure the Apache HTTP web server. The
recommendation is to install and run the Apache HTTP web server on
the same system the Java EE Container running AAA is installed on.
3. Configure the proxy connector in the Java EE Container and set the
``secureProxyPorts``.
We will also illustrate the operation of the system by adding an
example user named ``testuser`` who will be a member of the
``odl_users`` and ``odl_admin`` groups.
Add Example User and Groups to FreeIPA
======================================
After installing FreeIPA you will need to populate FreeIPA with your users,
groups and other data. Refer to the documentation in FreeIPA_ for the
variety of ways this task can be performed; it runs the gamut from web
based console to command line utilities. For simplicity we will use
the command line utilities.
Identify yourself to FreeIPA as an administrator; this will give you the
necessary privileges needed to create and modify data in FreeIPA. You do
this by obtaining a Kerberos ticket for the ``admin`` user (or any
other user in FreeIPA with administrator privileges.
::
% kinit admin@EXAMPLE.COM
Create the example ``odl_users`` and `odl_admin`` groups.
::
% ipa group-add odl_users --desc 'OpenDaylight Users'
% ipa group-add odl_admin --desc 'OpenDaylight Administrators'
Create the example user ``testuser`` with the first name "Test" and a
last name of "User" and an email address of "test.user@example.com"
::
% ipa user-add testuser --first Test --last User --email test.user@example.com
Now add ``testuser`` to the ``odl_users`` and ``odl_admin`` groups.
::
% ipa group-add-member odl_users --user testuser
% ipa group-add-member odl_admin --user testuser
Configure Apache
================
A number of Apache configuration directives will need to be specified
to implement the Apache to application binding. Although these
configuration directives can be located in any number of different
Apache configuration files the most sensible approach is to co-locate
them in a single application configuration file. This greatly
simplifies the deployment of your application and isolates your
application configuration from other applications and services sharing
the Apache installation. In the examples that follow our application
will be named ``my_app`` and the Apache application configuration file
will be named ``my_app.conf`` which should be located in Apache's
``conf.d/`` directory. The web resource we are protecting and
supplying identity metadata for will be named ``my_resource``.
Configure Apache for Kerberos
-----------------------------
When FreeIPA is deployed Kerberos is the preferred authentication mechanism
for Single Sign-On (SSO). FreeIPA also provides identity metadata via
Apache ``mod_identity_lookup``. To protect your ``my_resource`` resource
with Kerberos authentication identify your resource as requiring
Kerberos authentication in your ``my_app.conf`` Apache
configuration. For example:
::
AuthType Kerberos
AuthName "Kerberos Login"
KrbMethodNegotiate On
KrbMethodK5Passwd Off
KrbAuthRealms EXAMPLE.COM
Krb5KeyTab /etc/http.keytab
require valid-user
You will need to replace EXAMPLE.COM in the KrbAuthRealms declaration
with the Kerberos realm for your deployment.
Configure SSSD IFP
------------------
To use the Apache ``mod_identity_lookup`` module to supply identity
metadata you need to do the following in ``my_app.conf``:
1. Enable the module
::
LoadModule lookup_identity_module modules/mod_lookup_identity.so
2. Apply the identity metadata lookup to specific URL's
(e.g. ``my_resource``) via an Apache location directive. In this
example we look up the "mail" attribute and assign it to the
REMOTE_USER_EMAIL environment variable.
::
LookupUserAttr mail REMOTE_USER_EMAIL
3. Export the environment variable via the desired proxy protocol, see
`Exporting Environment Variables to the Proxy`_
Exporting Environment Variables to the Proxy
--------------------------------------------
First you need to decide which proxy protocol you're going to use, AJP
or HTTP and then determine the target address and port to proxy to. The
recommended configuration is to run both the Apache server and the
servlet container on the same host and to proxy requests over the
local loopback interface (see `Declaring the Connector Ports for
Authentication Proxies`_). In our examples we'll use port 8383. Thus
in ``my_app.conf`` add a proxy declaration.
For HTTP Proxy
::
ProxyPass / http://localhost:8383/
ProxyPassReverse / http://localhost:8383/
For AJP Proxy
::
ProxyPass / ajp://localhost:8383/
ProxyPassReverse / ajp://localhost:8383/
AJP Exports
^^^^^^^^^^^
AJP automatically forwards REMOTE_USER and AUTH_TYPE making them
available to the ``HttpServletRequest`` API, thus you do not need to
explicitly forward these in the proxy configuration. However all other
``mod_identity_lookup`` metadata must be explicitly forwarded as an AJP
attribute. These AJP attributes become visible in the
``ServletRequest.getAttribute()`` method [1]_.
The Apache ``mod_proxy_ajp`` module automatically sends any Apache
environment variable prefixed with "AJP\_" as an AJP attribute which
can be retrieved with ``ServletRequest.getAttribute()``. Therefore the
``mod_identity_lookup`` directives which specify the Apache environment
variable to set with the result of a lookup must be prefixed with
"AJP\_". Using the above example of looking up the principal's email
address we modify the environment variable to include the "AJP\_"
prefix. Thusly:
::
LookupUserAttr mail AJP_REMOTE_USER_EMAIL
The sequence of events is as follows:
1. When the URL matches "my_resource".
2. ``mod_identity_lookup`` retrieves the mail attribute for the
principal.
3. ``mod_identity_lookup`` assigns the value of the mail attribute
lookup to the AJP_REMOTE_USER_EMAIL Apache environment variable.
4. ``mod_proxy_ajp`` encodes AJP_REMOTE_USER_EMAIL environment
variable into an AJP attribute in the AJP protocol because the
environment variable is prefixed with "AJP\_". The name of the
attribute is stripped of it's "AJP\_" prefix thus the
AJP_REMOTE_USER_EMAIL environment variable is transferred as the
AJP attribute REMOTE_USER_EMAIL.
5. The request is forwarded (i.e. proxied) to servlet container
using the AJP protocol.
6. The servlet container's AJP ``Connector`` object is assigned each AJP
attribute to the set of attributes on the ``ServletRequest``
attribute list. Thus a call to
``ServletRequest.getAttribute("REMOTE_USER_EMAIL")`` yields the
value set by ``mod_identity_lookup``.
HTTP Exports
^^^^^^^^^^^^
When HTTP proxy is used there are no automatic or implicit metadata
transfers; every metadata attribute must be explicitly handled on both
ends of the proxy connection. All identity metadata attributes are
transferred as extension HTTP headers, by convention those headers are
prefixed with "X-SSSD-".
Using the original example of looking up the principal's email
address we must now perform two independent actions:
1. Lookup the value via ``mod_identity_lookup`` and assign to an
Apache environment variable.
2. Export the environment variable in the request header with the
"X-SSSD-" prefix.
::
LookupUserAttr mail REMOTE_USER_EMAIL
RequestHeader set X-SSSD-REMOTE_USER_EMAIL %{REMOTE_USER_EMAIL}e
The sequence of events is as follows:
1. When the URL matches "my_resource".
2. ``mod_identity_lookup`` retrieves the mail attribute for the
principal.
3. ``mod_identity_lookup`` assigns the value of the mail attribute
lookup to the REMOTE_USER_EMAIL Apache environment variable.
4. Apache's RequestHeader directive executes just prior to the
request being forwarded (i.e. in the Apache fixup stage). It adds
the header X-SSSD-REMOTE_USER_EMAIL and assigns the value for
REMOTE_USER_EMAIL found in the set of environment variables. It
does this because the syntax %{XXX} is a variable reference for
the name XXX and the 'e' appended after the closing brace
indicates the lookup is to be performed in the set of environment
variables.
5. The request is forwarded (i.e. proxied) to the servlet container
using the HTTP protocol.
6. When ``ServletRequest.getAttribute()`` is called the ``SssdFilter``
wrapper intercepts the ``getAttribute()`` method. It looks for an
HTTP header of the same name with "X-SSSD-" prefixed to it. In
this case ``getAttribute("REMOTE_USER_EMAIL")`` causes the lookup of
"X-SSSD-REMOTE_USER_EMAIL" in the HTTP headers, if found that
value is returned.
AJP Proxy Example Configuration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you are using AJP proxy to the Java EE Container on port 8383 your
``my_app.conf`` Apache configuration file will probably look like
this:
::
ProxyPass / ajp://localhost:8383/
ProxyPassReverse / ajp://localhost:8383/
LookupUserAttr mail AJP_REMOTE_USER_EMAIL " "
LookupUserAttr givenname AJP_REMOTE_USER_FIRSTNAME
LookupUserAttr sn AJP_REMOTE_USER_LASTNAME
LookupUserGroups AJP_REMOTE_USER_GROUPS ":"
Note the specification of the colon separator for the
``LookupUserGroups`` operation. [3]_
HTTP Proxy Example Configuration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you are using a conventional HTTP proxy to the Java EE Container on
port 8383 your ``my_app.conf`` Apache configuration file will probably
look like this:
::
ProxyPass / http://localhost:8383/
ProxyPassReverse / http://localhost:8383/
RequestHeader set X-SSSD-REMOTE_USER expr=%{REMOTE_USER}
RequestHeader set X-SSSD-AUTH_TYPE expr=%{AUTH_TYPE}
RequestHeader set X-SSSD-REMOTE_HOST expr=%{REMOTE_HOST}
RequestHeader set X-SSSD-REMOTE_ADDR expr=%{REMOTE_ADDR}
LookupUserAttr mail REMOTE_USER_EMAIL
RequestHeader set X-SSSD-REMOTE_USER_EMAIL %{REMOTE_USER_EMAIL}e
LookupUserAttr givenname REMOTE_USER_FIRSTNAME
RequestHeader set X-SSSD-REMOTE_USER_FIRSTNAME %{REMOTE_USER_FIRSTNAME}e
LookupUserAttr sn REMOTE_USER_LASTNAME
RequestHeader set X-SSSD-REMOTE_USER_LASTNAME %{REMOTE_USER_LASTNAME}e
LookupUserGroups REMOTE_USER_GROUPS ":"
RequestHeader set X-SSSD-REMOTE_USER_GROUPS %{REMOTE_USER_GROUPS}e
Note the specification of the colon separator for the
``LookupUserGroups`` operation. [3]_
Configure Java EE Container Proxy Connector
===========================================
The Java EE Container must be configured to listen for connections
from the Apache web server. A Java EE Container specifies connections
via a ``Connector`` object. A ``Connector`` **must** be dedicated
**exclusively** for handling authenticated requests from the Apache
web server. The reason for this is explained in `The Proxy
Problem`_. In addition ``ClaimAuthFilter`` needs to validate that any
request it processes originated from the trusted Apache instance. This
is accomplished by dedicating one or more ports exclusively for use by
the trusted Apache server and enumerating them in the
``secureProxyPorts`` configuration as explained in `Locking Down the
Apache to Java EE Container Channel`_ and `Declaring the Connector
Ports for Authentication Proxies`_.
Configure Tomcat Proxy Connector
--------------------------------
The Tomcat Java EE Container defines Connectors in its ``server.xml``
configuration file.
::
:address:
This should be the loopback address as explained `Locking Down the
Apache to Java EE Container Channel`_.
:port:
In our examples we've been using port 8383 as the proxy port. The
exact port is not important but it must be consistent with the
Apache proxy port, the ``Connector`` declaration, and the port value
in ``secureProxyPorts``.
:protocol:
As explained in `Transporting Identity Metadata from Apache to a
Java EE Servlet`_ you will need to decide if you are using HTTP or
AJP as the proxy protocol. In the example above the protocol is set
for HTTP, if you use AJP instead the protocol should instead be
"AJP/1.3".
:tomcatAuthentication:
This boolean flag tells Tomcat whether Tomcat should perform
authentication on the incoming requests or not. Since authentication
is performed by Apache we do not want Tomcat to perform
authentication therefore this flag must be set to false.
The AAA system needs to know which port(s) the trusted Apache proxy
will be sending requests on so it can trust the request authentication
metadata. See `Declaring the Connector Ports for Authentication
Proxies`_ for more information). Set ``secureProxyPorts`` in the
FederationConfiguration.
::
secureProxyPorts=8383
Configure Jetty Proxy Connector
-------------------------------
The Jetty Java EE Container defines Connectors in its ``jetty.xml``
configuration file.
::
127.0.0.1
8383
300000
2
false
8445
federationConn
20000
5000
:host:
This should be the loopback address as explained `Locking Down the
Apache to Java EE Container Channel`_.
:port:
In our examples we've been using port 8383 as the proxy port. The
exact port is not important but it must be consistent with the
Apache proxy port, the ``Connector`` declaration, and the port value
in ``secureProxyPorts``.
Note, values in Jetty XML can also be parameterized so that they may
be passed from property files or set on the command line. Thus
typically the port is set within Jetty XML, but uses the Property
element to be customizable. Thus the above ``host`` and ``port``
properties could be specificed this way:
::
The AAA system needs to know which port(s) the trusted Apache proxy
will be sending requests on so it can trust the request authentication
metadata. See `Declaring the Connector Ports for Authentication
Proxies`_ for more information). Set ``secureProxyPorts`` in the
FederationConfiguration.
************************************************
How Apache Identity Metadata is Processed in AAA
************************************************
`Figure 2.`_ and `Figure 3.`_ illustrates the fact the first stage in
processing a request from a user begins with Apache where the user is
authenticated and SSSD supplies additional metadata about the
user. The original request along with the metadata are subsequently
forwarded by Apache to the Java EE Container. `Figure 4.`_ illustrates
the processing inside the Java EE Container once it receives the
request on one of its secure connectors.
.. figure:: sssd_04.png
:align: center
_`Figure 4.`
:Step 1:
One or more Connectors have been configured to listen for requests
being forwarded from a trusted Apache instance. The Connector is
configured to communicate using either the HTTP or AJP protocols.
See `Exporting Environment Variables to the Proxy`_ for more
information on selecting a proxy transport protocol.
:Step 2:
The identity metadata bound to the request needs to be extracted
differently depending upon whether HTTP or AJP is the transport
protocol. To allow later stages in the pipeline to be ignorant of
the transport protocol semantics the ``SssdFilter`` servlet filter
is introduced. The ``SssdFilter`` wraps the ``HttpServletRequest``
class and intercepts calls which might return the identity
metadata. The wrapper in the filter looks in protocol specific
locations for the metadata. In this manner users of the
``HttpServletRequest`` are isolated from protocol differences.
:Step 3:
The ``ClaimAuthFilter`` is responsible for determining if identity
metadata is bound to the request. If so all identity metadata is
packaged into an assertion which is then handed off to
``SssdClaimAuth`` which will transform the identity metadata in the
assertion into a AAA Claim which is the authorizing token for the user.
:Step 4:
The ``SssdClaimAuth`` object is responsible for transforming the
external federated identity metadata provided by Apache and SSSD into
a AAA claim. The AAA claim is an authorization token which includes
information about the user plus a set of roles. These roles provide the
authorization to perform AAA tasks. Although how roles are assigned is
flexible the expectation is domain and/or group membership will be the
primary criteria for role assignment. Because deciding how to handle
external federated identity metadata is site and deployment specific
we need a loadable policy mechanism. This is accomplished by a set of
transformation rules which transforms the incoming IdP identity
metadata into a AAA claim. For greater clarity this important step is
broken down into smaller units in the shaded box in `Figure 4.`_.
:Step 4.1:
`The Mapping Rule Processor`_ is designed to accept a JSON object
(set of key/value pairs) as input and emit a different JSON object
as output effectively operating as a transformation engine on
key/value pairs.
:Step 4.2:
The input assertion is rewritten as a JSON object in the format
required by the Mapping Rule Processor. The JSON assertion is then
passed into the Mapping Rule Processor.
:Step 4.3:
`The Mapping Rule Processor`_ identified as ``IdPMapper`` evaluates
the input JSON assertion in the context of the mapping rules defined
for the site deployment. If ``IdPMapper`` is able to successfully
transform the input it will return a JSON object which we called the
*mapped* result. If the input JSON assertion is not compatible with
the site specific rules loaded into the ``IdPMapper`` then NULL is
returned by the ``IdPMapper``.
:Step 4.4:
If a mapped JSON object is returned by the ``IdPMapper`` the mapping
was successful. The values in the mapped result are re-written into
an AAA Claim token.
How Apache Identity Metadata is Mapped to AAA Values
====================================================
A federated IdP supplies metadata in a form unique to the IdP. This is
called an assertion. That assertion must be transformed into a format
and data understood by AAA. More importantly that assertion needs to
yield *authorization roles specific to AAA*. In `Figure 4.`_ Step 4.3
the ``IdPMapper`` provides the transformation from an external IdP
assertion to an AAA specific claim. It does this via a Mapping Rule
Processor which reads a site specific set of transformation
rules. These mapping rules define how to transform an external IdP
assertion into a AAA claim. The mapping rules also are responsible for
validating the external IdP claim to make sure it is consistent with
the site specific requirements. The operation of the Mapping Rule
Processor and the syntax of the mapping rules are defined in `The
Mapping Rule Processor`_.
Below is an example mapping rule which might be loaded into the
Mapping Rule Processor. It is assumed there are two AAA roles which
may be assigned [4]_:
``user``
A role granting standard permissions for normal ODL users.
``admin``
A special role granting full administrative permissions.
In this example assigning the ``user`` and ``admin`` roles
will be based on group membership in the following groups:
``odl_users``
Members of this group are normal ODL users with restricted permissions.
``odl_admin``
Members of this group are ODL administrators with permission to
perform all operations.
Granting of the ``user`` and/or ``admin`` roles based on
membership in the ``odl_users`` and ``odl_admin`` is illustrated in
the follow mapping rule example which also extracts the user principal
and domain information in the preferred format for the site
(e.g. usernames are lowercase without domain suffixes and the domain
is uppercase and supplied separately).
_`Mapping Rule Example 1.`
::
1 [
2 {"mapping": {"ClientId": "$client_id",
3 "UserId": "$user_id",
4 "User": "$username",
5 "Domain": "$domain",
6 "roles": "$roles",
7 },
8 "statement_blocks": [
9 [
10 ["set", "$groups", []],
11 ["set", "$roles", []]
12 ],
13 [
14 ["in", "REMOTE_USER", "$assertion"],
15 ["exit", "rule_fails", "if_not_success"],
16 ["regexp", "$assertion[REMOTE_USER]", "(?\\w+)@(?.+)"],
17 ["exit", "rule_fails", "if_not_success"],
18 ["lower", "$username", "$regexp_map[username]"],
19 ["upper", "$domain", "$regexp_map[domain]"],
20 ],
21 [
22 ["in", "REMOTE_USER_GROUPS", "$assertion"],
23 ["exit", "rule_fails", "if_not_success"],
24 ["split", "$groups", "$assertion[REMOTE_USER_GROUPS]", ":"],
25 ],
26 [
27 ["in", "odl_users", "$groups"],
28 ["continue", "if_not_success"],
29 ["append", "$roles", "user"],
30 ],
31 [
32 ["in", "odl_admin", "$groups"],
33 ["continue", "if_not_success"],
34 ["append", "$roles", "admin"]
35 ],
36 [
37 ["unique", "$roles", "$roles"],
38 ["length", "$n_roles", "$roles"],
39 ["compare", "$n_roles", ">", 0],
40 ["exit", "rule_fails", "if_not_success"],
41 ],
42 ]
43 }
44 ]
:Line 1:
Starts a list of rules. In this example only 1 rule is defined. Each
rule is a JSON object containing a ``mapping`` and a required list
of ``statement_blocks``. The ``mapping`` may either be specified
inside a rule as it is here or may be referenced by name in a table
of mappings (this is easier to manage if you have a large number of
rules and small number of mappings).
:Lines 2-7:
Defines the JSON mapped result. Each key maps to AAA claim. The
value is a rule variable whose value will be substituted if the rule
succeeds. Thus for example the AAA claim value ``User`` will be
assigned the value from the ``$username`` rule variable.
:Line 8:
Begins the list of statement blocks. A statement must be contained
inside a block.
:Lines 9-12:
The first block usually initializes variables that will be
referenced later. Here we initialize ``$groups`` and ``$roles`` to
empty arrays. These arrays may be appended to in later blocks and
may be referenced in the final ``mapping`` output.
:Lines 13-20:
This block sets the user and domain information based on
``REMOTE_USER`` and exits the rule if ``REMOTE_USER`` is not defined.
:Lines 14-15:
This test is critical, it assures ``REMOTE_USER`` is defined in the
assertion, if not the rule is skipped because we depend on
``REMOTE_USER``.
:Lines 16-17:
Performs a regular expression match against ``REMOTE_USER`` to split
the username from the domain. The regular expression uses named
groups, in this instance ``username`` and ``domain``. If the regular
expression does not match the rule is skipped.
:Lines 18-19:
These lines reference the previous result of the regular expression
match which are stored in the special variable ``$regexp_map``. The
username is converted to lower case and stored in ``$username`` and
the domain is converted to upper case and stored in ``$domain``. The
choice of case is purely by convention and site requirements.
:Lines 21-35:
These 3 blocks assign roles based on group membership.
:Lines 21-25:
Assures ``REMOTE_USER_GROUPS`` is defined in the assertion; if not, the
rule is skipped. ``REMOTE_USER_GROUPS`` is colon separated list of group
names. In order to operate on the individual group names appearing
in ``REMOTE_USER_GROUPS`` line 24 splits the string on the colon
separator and stores the result in the ``$groups`` array.
:Lines 27-30:
This block assigns the ``user`` role if the user is a member of the
``odl_users`` group.
:Lines 31-35:
This block assigns the ``admin`` role if the user is a
member of the ``odl_admin`` group.
:Lines 36-41:
This block performs final clean up actions for the rule. First it
assures there are no duplicates in the ``$roles`` array by calling
the ``unique`` function. Then it gets a count of how many items are
in the ``$roles`` array and tests to see if it's empty. If there are
no roles assigned the rule is skipped.
:Line 43:
This is the end of the rule. If we reach the end of the rule it
succeeds. When a rule succeeds the mapping associated with the rule
is looked up. Any rule variable appearing in the mapping is
substituted with its value.
Using the rules in `Mapping Rule Example 1.`_ and following example assertion
in JSON format:
_`Assertion Example 1.`
::
{
"REMOTE_USER": "TestUser@example.com",
"REMOTE_AUTH_TYPE": "Negotiate",
"REMOTE_USER_GROUPS": "odl_users:odl_admin",
"REMOTE_USER_EMAIL": "test.user@example.com",
"REMOTE_USER_FIRSTNAME": "Test",
"REMOTE_USER_LASTNAME": "User"
}
Then the mapper will return the following mapped JSON document. This
is the ``mapping`` defined on line 2 of `Mapping Rule Example 1.`_ with the
variables substituted after the rule successfully executed. Note any
valid JSON data type can be returned, in this example the ``null``
value is returned for ``ClientId`` and ``UserId``, normal strings for
``User`` and ``Domain`` and an array of strings for the ``roles`` value.
_`Mapped Result Example 1.`
::
{
"ClientId": null,
"UserId": null,
"User": "testuser",
"Domain": "EXAMPLE.COM",
"roles": ["user", "admin"]
}
**************************
The Mapping Rule Processor
**************************
The Mapping Rule Processor is designed to be as flexible and generic
as possible. It accepts a JSON object as input and returns a JSON
object as output. JSON was chosen because virtually all data can be
represented in JSON, JSON has extensive support and JSON is human
readable. The rules loaded into the Mapping Rule Processor are also
expressed in JSON. One advantage of this is it makes it easy for a
site administrator to define hardcoded values which are always
returned and/or static tables of white and black listed users or users
who are always mapped into certain roles.
.. include:: mapping.rst
***********************
Security Considerations
***********************
Attack Vectors
==============
A Java EE Container fronted by Apache has by definition 2 major
components:
* Apache
* Java EE Container
Each of these needs to be secure in its own right. There is extensive
documentation on securing each of these components and the reader is
encouraged to review this material. For the purpose of this discussion
we are most interested in how Apache and the Java EE
Container cooperate to form an integrated security system. Because
Apache is performing authentication on behalf of the Java EE Container,
it views Apache as a trusted partner. Our primary concern is the
communication channel between Apache and the Java EE Container. We
must assure the Java EE Container knows who it's trusted partner is
and that it only accepts security sensitive data from that partner,
this can best be described as `The Proxy Problem`_.
Forged REMOTE_USER
------------------
HTTP request handling is often implemented as a processing pipeline
where individual handlers are passed the request, they may then attach
additional metadata to the request or transform it in some manner
before handing it off to the next stage in the pipeline. A request
handler may also short circuit the request processing pipeline and
cause a response to be generated. Authentication is typically
implemented an as early stage request handler. If a request gets past
an authentication handler later stage handlers can safely assume the
request belongs to an authenticated user. Authorization metadata may
also have been attached to the request. Later stage handlers use the
authentication/authorization metadata to make decisions as to whether
the operations in the request can be satisfied.
When a request is fielded by a traditional web server with CGI (Common
Gateway Interface, RFC 3875) the request metadata is passed via CGI
meta-variables. CGI meta-variables are often implemented as environment
variables, but in practical terms CGI metadata is really just a set of
name/value pairs a later stage (i.e. CGI script, servlet, etc.) can
reference to learn information about the request.
The CGI meta-variables REMOTE_USER and AUTH_TYPE relate to
authentication. REMOTE_USER is the identity of the authenticated user
and AUTH_TYPE is the authentication mechanism that was used to
authenticate the user.
**If a later stage request handler sees REMOTE_USER and AUTH_TYPE as
non-null values it assumes the user is fully authenticated! Therefore
is it essential REMOTE_USER and AUTH_TYPE can only enter the request
pipeline via a trusted source.**
The Proxy Problem
=================
In a traditional monolithic web server the CGI meta-variables are
created and managed by the web server, which then passes them to CGI
scripts and executables in a very controlled environment where they
execute in the context of the web server. Forgery of CGI
meta-variables is generally not possible unless the web server has
been compromised in some fashion.
However in our configuration the Apache web server acts as an identity
processor, which then forwards (i.e. proxies) the request to the Java
EE container (i.e Tomcat, Jetty, etc.). One could think of the Java
EE container as just another CGI script which receives CGI
meta-variables provided by the Apache web server. Where this analogy
breaks down is how Apache invokes the CGI script. Instead of forking a
child process where the child's environment and input/output pipes are
carefully controlled by Apache the request along with its additional
metadata is forwarded over a transport (typically TCP/IP) to another
process, the proxy, which listens on socket.
The proxy (in this case the Java EE container) reads the request and
the attached metadata and acts upon it. If the request read by the
proxy contains the REMOTE_USER and AUTH_TYPE CGI meta-variables the
proxy will consider the request **fully authenticated!**. Therefore
when the Java EE container is configured as a proxy it is
**essential** it only reads requests from a **trusted** Apache web
server. If any other client aside from the trusted Apache web server
is permitted to connect to the Java EE container that client could
present forged REMOTE_USER and AUTH_TYPE meta-variables, which would be
automatically accepted as valid thus opening a huge security hole.
Possible Approaches to Lock Down a Proxy Channel
================================================
Tomcat Valves
-------------
You can use a `Tomcat Remote Address Valve`_ valve to filter by IP or
hostname to only allow a subset of machines to connect. This can be
configured at the Engine, Host, or Context level in the
conf/server.xml by adding something like the following:
::
The problem with valves is they are a Tomcat only concept, the
``RemoteAddrValve`` only checks addresses, not port numbers (although
it should be easy to add port checking) and they don't offer anything
better than what is described in `Locking Down the Apache to Java EE
Container Channel`_, which is not container specific. Servlet filters
are always available regardless of the container the servlet is
running in. A filter can check both the address and port number and
refuse to operate on the request if the address and port are not known to
be a trusted authentication proxy. Also note that if the Java EE
Container is configured to accept connections other than from the
trusted HTTP proxy server (a very likely scenario) then filtering at
the connector level is not sufficient because a servlet which trusts
``REMOTE_USER`` must be assured the request arrived only on a
trusted HTTP proxy server connection, not one of the other possible
connections.
SSL/TLS with client auth
------------------------
SSL with client authentication is the ultimate way to lock down a HTTP
Server to Java EE Container proxy connection. SSL with client
authentication provides authenticity, integrity, and
confidentiality. However those desirable attributes come at a
performance cost which may be excessive. Unless a persistent TCP
connection is established between the HTTP server and the Java EE
Container a SSL handshake will need to occur on each request being
proxied, SSL handshakes are expensive. Given that the HTTP server and
the Java EE Container will likely be deployed on the same compute node
(or at a minimum on a secure subnet) the advantage of SSL for proxy
connections may not be warranted because other options are available
for these configuration scenarios; see `Locking Down the Apache to Java EE
Container Channel`_. Also note that if the Java EE
Container is configured to accept connections other than from the
trusted HTTP proxy server (a very likely scenario), then filtering at
the connector level is not sufficient because a servlet which trusts
``REMOTE_USER`` must be assured that the request arrived only on a
trusted HTTP proxy server connection, not one of the other possible
connections.
Java Security Manager Permissions
---------------------------------
The Java Security Manager allows you define permissions which are
checked at run time before code executes.
``java.net.SocketPermission`` and ``java.net.NetPermission`` would
appear to offer solutions for restricting which host and port a
request containing ``REMOTE_USER`` will be trusted. However security
permissions are applied *after* a request is accepted by a
connector. They are also more geared towards what connections code can
subsequently utilize as opposed to what connection a request was
presented on. Therefore security manager permissions seem to offer little
value for our purpose. One can simply test to see which host sent the
proxy request and on what port it arrived on by looking at the
connection information in the request. Restricting which proxies can
submit trusted requests is better handled at the level of the
connector, which unfortunately is a container implementation
issue. Tomcat and Jetty have different ways of handling connector
specifications.
AJP requiredSecret
------------------
The AJP protocol includes an attribute called ``requiredSecret``, which
can be used to secure the connection between AJP endpoints. When an
HTTP server sends an AJP proxy request to a Java EE Container it
embeds in the protocol transmission a string (``requiredSecret``)
known only to the HTTP server and the Java EE Container. The AJP
connector on the Java EE Container is configured with the
``requiredSecret`` value and will reject as unauthorized any AJP
requests whose ``requiredSecret`` does not match.
There are two problems with `requiredSecret``. First of all it's not
particularly secure. In fact, it's fundamentally no different than
sending a cleartext password. If the AJP request is not encrypted it
means the ``requiredSecret`` will be sent in the clear which is
probably one of the most egregious security mistakes. If the AJP
request is transmitted in a manner where the traffic can be sniffed, it
would be trivial to recover the ``requiredSecret`` and forge a request
with it. On the other hand encrypting the communication channel
between the HTTP server and the Java EE Container means using SSL
which is fairly heavyweight. But more to the point, if one is using
SSL to encrypt the channel there is a *far better* mechanism to ensure
the HTTP server is who it claims to be than embedding
``requiredSecret``. If one is using SSL you might as well use SSL
client authentication where the HTTP identifies itself via a client
certificate. SSL client authentication is a very robust authentication
mechanism. But doing SSL client authentication, or for that matter
just SSL encryption, for *every* AJP protocol request is prohibitively
expensive from a performance standpoint.
The second problem with ``requiredSecret`` is that despite being documented
in a number of places it's not actually implemented in Apache
``mod_proxy_ajp``. This is detailed in `bug 53098`_. You can set
``requiredSecret`` in the ``mod_proxy_ajp`` configuration, but it won't
be included in the wire protocol. There is a patch to implement
``requiredSecret`` but, it hasn't made it into any shipping version of
Apache yet. But even if ``requiredSecret`` was implemented it's not
useful. Also one could construct the equivalent of ``requiredSecret``
from other AJP attributes and/or an HTTP extension header but those
would suffer from the same security issues ``requiredSecret`` has,
therefore it's mostly pointless.
Java EE Container Issues
========================
Jetty Issues
------------
Jetty is a Java EE Container which can be used
as alternative to Tomcat. Jetty is an Eclipse project. Recent versions
of Jetty have dropped support for AJP; this is described in the
`Jetty AJP Configuration Guide`_ which states:
Configuring AJP13 Using mod_jk or mod_proxy_ajp. Support for this
feature has been dropped with Jetty 9. If you feel this should be
brought back please file a bug.
Eclipse `Bug 387928`_ *Retire jetty-ajp* was opened to track the
removal of AJP in Jetty and is now closed.
Tomcat Issues
-------------
You should refer the `Tomcat Security How-To`_ for a full discussion
of Tomcat security issues.
The tomcatAuthentication attribute is used with the AJP connectors to
determine if Tomcat should authenticate the user or if authentication
can be delegated to the reverse proxy that will then pass the
authenticated username to Tomcat as part of the AJP protocol.
The requiredSecret attribute in AJP connectors configures a shared
secret between Tomcat and the reverse proxy in front of Tomcat. It is used
to prevent unauthorized connections over AJP protocol.
Locking Down the Apache to Java EE Container Channel
====================================================
The recommended approach to lock down the proxy channel is:
* Run both Apache and the servlet container on the same host.
* Configure Apache to forward the proxy request on the loopback
interface (e.g. 127.0.0.1 also known as ``localhost``). This
prohibits any external IP address from connecting, only processes
running on the locked down host can communicate over
``localhost``.
* Reserve one or more ports for communication **exclusively** for
proxy communication between Apache and the servlet container. The
servlet container may listen on other ports for non-critical
non-authenticated requests.
* The ``ClaimAuthFilter`` that reads the identity metadata **must**
assure that requests have arrived only on a **trusted port**. To
achieve this the ``FederationConfiguration`` defines the
``secureProxyPorts`` configuration option. ``secureProxyPorts`` is
a space delimited list of ports which during deployment the
administrator has configured such that they are **exclusively**
dedicated for use by the Apache server(s) providing authentication
and identity information. These ports are set in the servlet
container's ``Connector`` declarations. See `Declaring the
Connector Ports for Authentication Proxies`_ for more
information).
* When the ``ClaimAuthFilter`` receives a request, the first thing
it does is check the ``ServletRequest.getLocalPort()`` value and
verifies it is a member of the ``secureProxyPorts`` configuration
option. If the port is a member of ``secureProxyPorts``, it will
trust every identity assertion found in the request. If the local
port is not a member of ``secureProxyPorts``, a HTTP 401
(unauthorized) error status will be returned for the request. A
warning message will be logged the first time this occurs.
Declaring the Connector Ports for Authentication Proxies
--------------------------------------------------------
As described in `The Proxy Problem`_ the AAA authentication system
**must** confirm the request it is processing originated from a *trusted
HTTP proxy server*. This is accomplished with port isolation.
The administrator deploying a federated AAA solution with SSSD
identity lookups must declare in the AAA federation configuration
which ports the proxy requests from the trusted HTTP server will
arrive on by setting the ``secureProxyPorts`` configuration
item. These ports **must** only be used for the trusted HTTP proxy
server. The AAA federation software will not perform authentication
for any request arriving on a port other than those listed in
``secureProxyPorts``.
.. figure:: sssd_05.png
:align: center
_`Figure 5.`
``secureProxyPorts`` configuration option is set either in the
``federation.cfg`` file or in the
``org.opendaylight.aaa.federation.secureProxyPorts`` bundle
configuration. ``secureProxyPorts`` is a space-delimited list of port
numbers on which a trusted HTTP proxy performing authentication
forwards pre-authenticated requests. For example:
::
secureProxyPorts=8383
Means a request which arrived on port 8383 is from a trusted HTTP
proxy server and the value of ``REMOTE_USER`` and other authentication
metadata in request can be trusted.
########
Appendix
########
*****************
CGI Export Issues
*****************
Apache processes requests as a series of steps in a pipeline
fashion. The ordering of these steps is important. Core Apache is
fairly minimal, most of Apache's features are supplied by loadable
modules. When a module is loaded it registers a set of *hooks*
(function pointers) which are to be run at specific stages in the
Apache request processing pipeline. Thus a module can execute code at
any of a number of stages in the request pipeline.
The user metadata supplied by Apache is initialized in two distinct
parts of Apache.
1. an authentication module (e.g. mod_auth_kerb)
2. the ``mod_lookup_identity`` module.
After successful authentication the authentication module will set the
name of the user principal and the mechanism used for authentication
in the request structure.
* ``request->user``
* ``request->ap_auth_type``
Authentication hooks run early in the request pipeline for the obvious
reason a request should not be processed if not authenticated. The
specific authentication module that runs is defined by ``Location``
directive in the Apache configuration which binds specific
authentication to specific URL's. The ``mod_lookup_identity`` module
must run *after* authentication module runs because it depends on
knowing who the authenticated principal is so it can lookup the data
on that principal.
When reading ``mod_lookup_identity`` documentation one often sees
references to the ``REMOTE_USER`` CGI environment variable with the
implication ``REMOTE_USER`` is how one accesses the name of the
authenticated principal. This is a bit misleading, ``REMOTE_USER`` is
a CGI environment variable. CGI environment variables are only set by
Apache when it believes the request is going to be processed by a CGI
implementation. In this case ``REMOTE_USER`` is initialized from the
``request->user`` value.
How is the authenticated principal actually forwarded to our proxy?
===================================================================
If we are using the AJP proxy protocol the ``mod_proxy_ajp`` module
when preparing the proxy request will read the value of
``request->user`` and insert it into the ``SC_A_REMOTE_USER`` AJP
attribute. On the receiving end ``SC_A_REMOTE_USER`` will be extracted
from the AJP request and used to populate the value returned
by``HttpServletRequest.getRemoteUser()``. The exchange of the
authenticated principal when using AJP is transparent to both the
sender and receiver, nothing special needs to be done. See
`Transporting Identity Metadata from Apache to a Java EE Servlet`_
for details on how metadata can be exchanged with the proxy.
However, if AJP is not being used to proxy the request the
authenticated principal must be passed through some other mechanism,
an HTTP extension header is the obvious solution. The Apache
``mod_headers`` module can be used to add HTTP request headers to the
proxy request, for example:
::
RequestHeader set MY_HEADER MY_VALUE
Where does the value MY_VALUE come from? It can be hardcoded into the
``RequestHeader`` statement or it can reference an existing
environment variable like this:
::
RequestHeader set MY_HEADER %{FOOBAR}e
where the notation ``%{FOOBAR}e`` is the contents of the environment
variable FOOBAR. Thus we might expect we could do this:
::
RequestHeader set REMOTE_USER %{REMOTE_USER}e
The conundrum is the presumption the ``REMOTE_USER`` environment
variable has already been set at the time ``mod_headers`` executes the
``RequestHeader`` statement. Unfortunately this often is not the
case.
The Apache environment variables ``REMOTE_USER`` and ``AUTH_TYPE`` are
set by the Apache function ``ap_add_common_vars()`` defined in
server/util_script.c. ``ap_add_common_vars()`` and is called by the
following modules:
* mod_authnz_fcgi
* mod_proxy_fcgi
* mod_proxy_scgi
* mod_isapi
* mod_ext_filter
* mod_include
* mod_cgi
* mod_cgid
Apache variables
================
Apache modules provide access to variables which can be referenced by
configuration directives. Unfortunately there isn't a lot of
uniformity to what the variables are and how they're referenced; it
mostly depends on how a given Apache module was implemented. As you
might imagine a bit of inconsistent historical cruft has accumulated
over the years, it can be confusing. The Apache Foundation is trying
to clean some of this up bringing uniformity to modules by utilizing
the common ``expr`` (expression) module `ap_expr`_. The idea being modules will
forgo their home grown expression syntax with its numerous quirks and
instead expose the common ``expr`` language. However this is a work in
progress and at the time of this writing only a few modules have acquired
``expr`` expression support.
Among the existing Apache modules there currently are three different
sets of variables.
1. Server variables.
2. Environment variables.
3. SSL variables.
Server variables (item 1) are names given to internal values. The set
of names for server variables and what they map to are defined by the
module implementing the server variable lookup. For example
``mod_rewrite`` has its own variable lookup implementation.
Environment variables (item 2) are variables *exported* to a
subprocess. Internally they are stored in
``request->subprocess_env``. The most common use of environment
variables exported to a subprocess are the CGI variables.
SSL variables are connection specific values describing the SSL
connection. The lookup is implemented by ``ssl_var_lookup()``, which
given a variable name looks in a variety of internal data structures to
find the matching value.
The important thing to remember is **server variables != environment
variables**. This can be confusing because they often share the same
name. For example, there is the server variable ``REMOTE_USER`` and
there is the environment variable ``REMOTE_USER``. The environment
variable ``REMOTE_USER`` only exists if some module has called
``ap_add_common_vars()``. To complicate matters, some modules allow you
to access *server variables*, other modules allow you to access
*environment variables* and some modules provide access to both
*server variables* and *environment variables*.
Coming back to our goal of setting an HTTP extension header to the
value of ``REMOTE_USER``, we observe that ``mod_headers`` provides the
needed ``RequestHeader`` operation to set a HTTP header in the
request. Looking at the documentation for ``RequestHeader`` we see a
value can be specified with one of the following lookups:
%{VARNAME}e
The contents of the environment variable VARNAME.
%{VARNAME}s
The contents of the SSL environment variable VARNAME, if mod_ssl is enabled.
But wait! This only gives us access to *environment variables* and the
``REMOTE_USER`` environment variable is only set if
``ap_add_common_vars()`` is called by a module **after** an
authentication module runs! ``ap_add_common_vars()`` is usually only
invoked if the request is going to be passed to a CGI script. But
we're not doing CGI; instead we're proxying the request. The
likelihood the ``REMOTE_USER`` environment variable will be set is
quite low. See `Setting the REMOTE_USER environment variable`_.
``mod_headers`` is the only way to set a HTTP extension header and
``mod_headers`` only gives you access to environment variables and the
``REMOTE_USER`` environment variable is not set. Therefore if we're
not using AJP and must depend on setting a HTTP extension header for
``REMOTE_USER``, we have a **serious problem**.
But there is a solution; you can either try the machinations described
in `Setting the REMOTE_USER environment variable`_ or assure you're
running at least Apache version 2.4.10. In Apache 2.4.10 the
``mod_headers`` module added support for `ap_expr`_. `ap_expr`_
provides access to *server variables* by using the ``%{VARIABLE}``
notation. `ap_expr`_ also can lookup subprocess environment variables
and operating system environment variables using its ``reqenv()`` and
``osenv()`` functions respectively.
Thus the simple solution for exporting the ``REMOTE_USER`` HTTP
extension header if you're running Apache 2.4.10 or later is:
::
RequestHeader set X-SSSD-REMOTE_USER expr=%{REMOTE_USER}
The ``expr=%{REMOTE_USER}`` in the above statement says pass
``%{REMOTE_USER}`` as an expression to `ap_expr`_, evaluate the
expression and return the value. In this case the expression
``%{REMOTE_USER}`` is very simple, just the value of the server
variables ``REMOTE_USER``. Because ``RequestHeader`` runs after
authentication ``request->user`` will have been set.
Setting the REMOTE_USER environment variable
============================================
If you do a web search on how to export ``REMOTE_USER`` in a HTTP
extension header for a proxy you will discover this is a common
problem that has frustrated a lot of people [2]_. The usual advice seems to
be to use ``mod_rewrite`` with a look-ahead. In fact this is even
documented in the `mod_rewrite documentation for REMOTE_USER`_ which says:
%{LA-U:variable} can be used for look-aheads which perform an
internal (URL-based) sub-request to determine the final value of
variable. This can be used to access variable for rewriting which is
not available at the current stage, but will be set in a later
phase.
For instance, to rewrite according to the REMOTE_USER variable from
within the per-server context (httpd.conf file) you must use
%{LA-U:REMOTE_USER} - this variable is set by the authorization
phases, which come after the URL translation phase (during which
mod_rewrite operates).
One suggested solution is this:
::
RewriteCond %{LA-U:REMOTE_USER} (.+)
RewriteRule .* - [E=RU:%1]
RequestHeader set X_REMOTE_USER %{RU}e
1. The RewriteCond with the %{LA-U:} construct performs an internal
redirect to obtain the value of ``REMOTE_USER`` *server variable*,
if that value is non-empty because the (.+) regular expression
matched the rewrite condition succeeds and the following
RewriteRule executes.
2. The RewriteRule executes, the first parameter is a pattern, the
second parameter is the replacement which can be followed by
optional flags inside brackets. The .* pattern is a regular
expression that matches anything, the - replacement is a special
value which indicates no replacement is to be performed. In other
words the pattern and replacement are no-ops and the RewriteRule is
just being used for it's side effect defined in the flags. The
E=NAME:VALUE notation says set the NAME environment variable to
VALUE. In this case the environment variable is RU and the value is
%1. The documentation for RewriteRule tells us that %N are
back-references to the last matched RewriteCond pattern, in this
case it's the value of ``REMOTE_USER``.
3. Finally ``RequestHeader`` sets the request header
``X_REMOTE_USER`` to the value of the ``RU`` environment variable.
Another suggested solution is this:
::
RewriteRule .* - [E=REMOTE_USER:%{LA-U:REMOTE_USER}]
The Problem with mod_rewrite lookahead
--------------------------------------
I **do not recommend** using mod_rewrite's lookahead to gain access to
authentication data values. Although the above suggestions will work
to get access to ``REMOTE_USER`` it is *extremely inefficient* because
it causes Apache to reprocess the request with an internal
redirect. The documentation suggests a lookahead reference will cause
one internal redirect. However from examining Apache debug logs the
``mod_rewite`` lookahead caused ``mod_lookup_identity`` to be invoked
**11 times** while handling one request. If the ``mod_rewrite``
lookahead is removed and another technique is used to get access to
``REMOTE_USER`` then ``mod_lookup_identity`` is invoked exactly once
as expected.
But it's not just ``REMOTE_USER`` which we need access to, we also need
to reference ``AUTH_TYPE`` which has the identical issues associated
with ``REMOTE_USER``. If an equivalent ``mod_rewrite`` block is added
to the configuration for ``AUTH_TYPE`` so that both ``REMOTE_USER``
and ``auth_type`` are resolved using a lookahead Apache appears to go
into an infinite loop and the request stalls.
I tried to debug what was occurring when Apache was configured this way
and why it seemed to be executing the same code over and over but I
was not able to figure it out. My conclusion is **using mod_rewrite
lookahead's is not a viable solution!** Other web posts also make
reference to the inefficiency but they seem to be unaware of just how
bad it is.
.. [1]
Tomcat has a bug/feature, not all attributes are enumerated by
getAttributeNames() therefore getAttributeNames() cannot be used to
obtain the full set of attributes. However if you know the name of
the attribute a priori you can call getAttribute() and obtain the
value. Therefore we maintain a list of attribute names
(httpAttributes) which will be used to call getAttribute() with so we
don't miss essential attributes.
This is the Tomcat bug, note it is marked WONTFIX. Bug 25363 -
request.getAttributeNames() not working properly Status: RESOLVED
WONTFIX https://issues.apache.org/bugzilla/show_bug.cgi?id=25363
The solution adopted by Tomcat is to document the behavior in the
"The Apache Tomcat Connector - Reference Guide" under the JkEnvVar
property where is says:
You can retrieve the variables on Tomcat as request attributes via
request.getAttribute(attributeName). Note that the variables send via
JkEnvVar will not be listed in request.getAttributeNames().
.. [2]
Some examples of posts concerning the export of ``REMOTE_USER`` include:
http://www.jaddog.org/2010/03/22/how-to-proxy-pass-remote_user/ and
http://serverfault.com/questions/23273/apache-proxy-passing-on-remote-user-to-backend-server/
.. [3]
The ``mod_lookup_identity`` ``LookupUserGroups`` option accepts an
optional parameter to specify the separator used to separate group
names. By convention this is normally the colon (:) character. In
our examples we explicitly specify the colon separator because the
mapping rules split the value found in ``REMOTE_USER_GROUPS`` on
the colon character.
.. [4]
The example of using the `The Mapping Rule Processor`_ to establish
the set of roles assigned to a user based on group membership is
for illustrative purposes in order to show features of the
federated IdP and mapping mechanism. Role assignment in AAA may be
done in other ways. For example an unscoped token without roles can
be used to acquire a scoped token with roles by presenting it to
the appropriate REST API endpoint. In actual deployments this may
be preferable because it places the responsibility of deciding who
has what role/permission on what part of the controller/network
resources more in the hands of the SDN controller administrator
than the IdP administrator.
.. _FreeIPA: http://www.freeipa.org/
.. _SSSD: https://fedorahosted.org/sssd/
.. _mod_identity_lookup: http://www.adelton.com/apache/mod_lookup_identity/
.. _AJP: http://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html
.. _Tomcat Security How-To: http://tomcat.apache.org/tomcat-7.0-doc/security-howto.html
.. _The Apache Tomcat Connector - Generic HowTo: http://tomcat.apache.org/connectors-doc/generic_howto/printer/proxy.html
.. _CGI RFC: http://www.ietf.org/rfc/rfc3875
.. _ap_expr: http://httpd.apache.org/docs/current/expr.html
.. _mod_rewrite documentation for REMOTE_USER: http://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritecond
.. _bug 53098: https://issues.apache.org/bugzilla/show_bug.cgi?id=53098
.. _Jetty AJP Configuration Guide: http://wiki.eclipse.org/Jetty/Howto/Configure_AJP13
.. _Bug 387928: https://bugs.eclipse.org/bugs/show_bug.cgi?id=387928
.. _Tomcat Remote Address Valve: http://tomcat.apache.org/tomcat-7.0-doc/config/valve.html#Remote_Address_Filter