Wednesday, December 17, 2014

Adding Config Pages Into OpenAM's Console (Legacy not XUI)

My goal in this post is to shed light on how to add configuration values and containing pages into OpenAM's admin console. In a following post I'll outline how to programmatically gain access to the values configured therein. So lets start.

The RADIUS server support added into OpenAM requires a number of configuration parameters. For example, the RADIUS RFC requires that incoming requests only be accepted if from a source IP address of a defined client. All other packets should silently be dropped. Further, the shared secret for a client must be determined by that IP address. Meaning, when looking in the set of defined clients for the shared secret with which to validate the packet only the source IP address should be used to identify the client not some field in the packet. So we need to define clients, their IP address from which their packets will arrive, and the secret shared between the server and the remote client. And there are more values as will be seen.

New configuration pages can be added to OpenAM's admin console via an XML declaration and OpenAM will provide the page rendering, submit processing, data storage, and retrieval later via code including supporting registration of change listeners to be notified when some value has been changed through the console. I refer to these registered "chunks" of configuration as an admin console configuration service and the XML files used to define them as their service descriptor file.

Before I get into the structure of the service descriptor file I need to discus some of the other parameters that are needed and where I want the configuration pages to show up in OpenAM. The RADIUS server feature will listen on UDP port 1812 for incoming requests following the spec. However, I'll support placing it on some other port if desired. The service must supports enabling and disabling the server and when disabled should close the port. Further, the implementation is designed using a thread pool and a limited wait queue ensuring that the server can't be flooded to the point of failure. It also supports any number of remote clients each with their own configuration as noted above.

The Service Descriptor File

With that in mind the service descriptor file: amRadiusServer.xml and its resource bundle file radiusServer.properties are included at the bottom of this post.  Since the documentation isn't very plentiful at present for adding configuration pages into OpenAM's admin console, I arrived at my service descriptor file by looking in the existing UI for another item located in a similar location to where I desired the Radius configuration to show up. And I looked for an item that had similar configuration constructs. The Dashboard service found in the Configuration tab and Global sub-tab was a good match. Everything I need to configure for Radius fits nicely at that global level. And just like the Dashboard I needed a group of sub items to configure, clients. So I found and started with its file as a basis, changed the resource bundle and keys and pushed it into OpenAM as outlined below, over and over until everything looked just right.

The DTD that service descriptor files conform to can be found in an installed instance of OpenAM at web-root/WEB-INF/sms.dtd. There really could be a number of blog posts just on that document alone and the various ways things can show up in the UI depending on what you declare. I'll cover just a few points here.

First is the Service element's name attribute. It has the well-known name of the service; RadiusServerService in my case. NOTE: This name is what we'll need in our code at runtime to acquire what has been configured in the admin console.

Second, in a number of places there is an i18nKey attribute. The value of this attribute is the key in the resource bundle having base name of radiusServer as declared in the Schema's  i18nFileName attribute.  What isn't clear is that the sort order of the keys for a given level, more on that in a minute, defines the order of the fields as they are rendered in the UI. Also unclear is that for each key in the resource bundle you can also have <key>.help and <key>.help.txt properties that will be used by the UI to respectively render descriptive text next to the input control and place an information icon next to the control that can be clicked to see further detail about that input value displayed in a modal box.

Structure wise, note that there is a Global element with a bunch of nested AttributeSchema items and one SubSchema element with its own nested AttributeSchema items. The AttributeSchema items each define one input item be it a list or single field as determined by its type. And there is even some rudimentary validation for different types as defined by the syntax attributes. See the DTD for more detail albeit I had to try a few times before I got everything working the way that I wanted it.

The SubSchema element renders in the admin console as a table amidst the other fields as shown below and gives us the ability to create groupings of additional related fields and edit them in a separate page as a group. Also note the empty Organization element that is a sibling to the Global element for the reason noted in the xml comment. That is important to remember. Without having that element you won't be able to save your configuration values.

Registering the Service Descriptor File

To tell OpenAM about this configuration service we use the ssoadm tool or the ssoadm.jsp page included with OpenAM which is what I've always used. However, by default that JSP is blocked from access. To enable it, log in as an admin, go to the Configuration tab, Servers and Sites sub-tab, select the server for the server on which you want to enable ssoadm.jsp, then select the Advanced sub-tab. In the Advanced Properties pane press the Add button and add a property of ssoadm.disabled with a value of false and press the save button.

Now you can go to http(s)://servername(:port)/your-root/ssoadm.jsp. In the list of links on that page search for create-svc and select it. This presents the screen below in version 11 of OpenAM. Into the text area I pasted the service descriptor file contents as shown and pressed the Submit button resulting in a message that the service was created.


Accessing Your New Pages

The service is available in the UI immediately but will be stuck presenting only keys for labels if the resource bundle is not found. That can be in WEB-INF/classes or included in the root of a jar which is where mine is located; a new openam-auth-radius.jar. You can use my files to give this a try on your own to learn more about this feature. It won't hurt anything. And if you use the delete-svc tool in ssoadm.jsp specifying RadiusServerService when you are done and that will remove the constructs from the console and remove all persisted values.

Once registered the results are seen by signing-in as an admin, selecting the Configuration tab followed by the Global sub-tab. As shown in the image below there is now a RADIUS Server included in the Global Properties table.




Upon selecting the RADIUS Server link OpenAM generates the global attributes declared and generates the Secondary Configuration Instances table for the sub-schema element.  You see that we can enable or disable the RADIUS service which essentially opens or closes the UDP port. And we can customize our thread pool parameters that we declared. To see the fields defined within the sub-schema we press the Add button in the table.



Creating Radius Clients

Upon pressing the Add button in the table I am presented with the fields declared in the sub-schema as shown below.


I'll post specifics about configuring these clients as I explain the Radius support that is now working in my local OpenAM instance. Until then...Enjoy.

Admin Console Configuration Service Descriptor File for Radius Server


<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE ServicesConfiguration
PUBLIC "=//iPlanet//Service Management Services (SMS) 1.0 DTD//EN"
"jar://com/sun/identity/sm/sms.dtd">

<ServicesConfiguration>
 <Service name="RadiusServerService" version="1.0">
  <Schema
    serviceHierarchy="/DSAMEConfig/RadiusServerService"
    i18nFileName="radiusServer"
    revisionNumber="1"
    i18nKey="radius-server-service-description">

   <Global validate="yes" >
    <AttributeSchema name="radiusListenerEnabled"
         type="single_choice"
         syntax="string"
         i18nKey="a-radius-listener-enabled-label">
     <ChoiceValues>
      <ChoiceValue i18nKey="choiceYES">YES</ChoiceValue>
      <ChoiceValue i18nKey="choiceNO">NO</ChoiceValue>
     </ChoiceValues>
     <DefaultValues>
      <Value>NO</Value>
     </DefaultValues>
    </AttributeSchema>

    <AttributeSchema name="radiusServerPort"
         cosQualifier="default"
         i18nKey="b-radius-port"
         isSearchable="no"
         syntax="number_range"
         rangeStart="1025"
         rangeEnd="65535"
         type="single" >
     <DefaultValues>
      <Value>1812</Value>
     </DefaultValues>
    </AttributeSchema>

    <AttributeSchema name="radiusThreadPoolCoreSize"
         cosQualifier="default"
         i18nKey="c-radius-thread-pool-core-size"
         isSearchable="no"
         syntax="number_range"
         rangeStart="1"
         rangeEnd="100"
         type="single" >
     <DefaultValues>
      <Value>1</Value>
     </DefaultValues>
    </AttributeSchema>

    <AttributeSchema name="radiusThreadPoolMaxSize"
         cosQualifier="default"
         i18nKey="d-radius-thread-pool-max-size"
         isSearchable="no"
         syntax="number_range"
         rangeStart="1"
         rangeEnd="100"
         type="single" >
     <DefaultValues>
      <Value>10</Value>
     </DefaultValues>
    </AttributeSchema>

    <AttributeSchema name="radiusThreadPoolKeepaliveSeconds"
         cosQualifier="default"
         i18nKey="e-radius-thread-pool-keepalive-seconds"
         isSearchable="no"
         syntax="number_range"
         rangeStart="1"
         rangeEnd="3600"
         type="single" >
     <DefaultValues>
      <Value>10</Value>
     </DefaultValues>
    </AttributeSchema>

    <AttributeSchema name="radiusThreadPoolQueueSize"
         cosQualifier="default"
         i18nKey="f-radius-thread-pool-queue-size"
         isSearchable="no"
         syntax="number_range"
         rangeStart="1"
         rangeEnd="1000"
         type="single" >
     <DefaultValues>
      <Value>10</Value>
     </DefaultValues>
    </AttributeSchema>

    <SubSchema name="radiusClient"
         inheritance="multiple"
         maintainPriority="no"
         supportsApplicableOrganization="no"
         i18nFileName="radiusServer"
         i18nKey="client-config-instance">
     <AttributeSchema name="clientIpAddress"
          i18nKey="a-client-ip-address-label"
          isSearchable="no"
          syntax="string"
          type="single" >
     </AttributeSchema>
     <AttributeSchema name="clientSecret"
          i18nKey="b-client-secret-label"
          isSearchable="no"
          syntax="string"
          type="single" >
      <DefaultValues>
       <DefaultValuesClassName className="com.sun.identity.authentication.modules.radius.server.config.DefaultClientSecretGenerator"></DefaultValuesClassName>
      </DefaultValues>
     </AttributeSchema>
     <AttributeSchema name="clientPacketsLogged"
          type="single_choice"
          syntax="string"
          i18nKey="c-client-log-packets">
      <ChoiceValues>
       <ChoiceValue i18nKey="choiceYES">YES</ChoiceValue>
       <ChoiceValue i18nKey="choiceNO">NO</ChoiceValue>
      </ChoiceValues>
      <DefaultValues>
       <Value>NO</Value>
      </DefaultValues>
     </AttributeSchema>

     <AttributeSchema name="handlerClass"
          i18nKey="d-handler-class"
          isSearchable="no"
          syntax="string"
          type="single" >
      <DefaultValues>
       <Value>com.sun.identity.authentication.modules.radius.server.spi.handlers.OpenAMAuthHandler</Value>
      </DefaultValues>

     </AttributeSchema>
     <AttributeSchema name="handlerConfig"
          i18nKey="e-handler-config-params"
          isSearchable="no"
          syntax="string"
          type="list" >
     </AttributeSchema>
    </SubSchema>
   </Global>
   <!--
   Having an Organization declaration is required before
   openAM will allows us to save an instance of the global
   SubSchema. Otherwise, it gives an error upon saving saying,
   "The service RadiusClientService does not have organization
   schema." By adding an empty Organization no configuration
   is added to the Services tab for this service in realms but
   we can persist our configuration.
   -->
   <Organization>
   </Organization>

  </Schema>
 </Service>
</ServicesConfiguration>

radiusServer.properties resource bundle file


radius-server-service-description=RADIUS Server

a-radius-listener-enabled-label=Enabled
a-radius-listener-enabled-label.help=The RADIUS Server will only open a port and listen for requests when enabled.
choiceYES=YES
choiceNO=NO

b-radius-port=Listener Port
b-radius-port.help=The UDP port on which each OpenAM server will listen for RADIUS Access-Request packets
b-radius-port.help.txt=According to the RADIUS Authentication Specification, RFC 2865, the officially assigned port number for RADIUS is 1812. We allow values from 1025 up to 65535. Requests for all Clients are handled through the same port.

c-radius-thread-pool-core-size=Thread Pool Core Size
c-radius-thread-pool-core-size.help=Click the Info icon for details from ThreadPoolExecutor javadoc.
c-radius-thread-pool-core-size.help.txt=When a RADIUS request is received and fewer \
than corePoolSize threads are running, a new thread is created to handle the request, even if other worker threads \
are idle. If there are more than Pool Core Size but less than Pool Max Size threads running, a new thread will be \
created only if the queue is full. By setting Pool Core Size and Pool Max Size the same, you create a fixed-size \
thread pool. Limited from 1 to 100.<br/><br/>

d-radius-thread-pool-max-size=Thread Pool Max Size
d-radius-thread-pool-max-size.help=See notes and range restrictions for Thread Pool Core Size.

e-radius-thread-pool-keepalive-seconds=Thread Pool Keep-Alive Seconds
e-radius-thread-pool-keepalive-seconds.help=Click the Info icon for details from ThreadPoolExecutor javadoc.
e-radius-thread-pool-keepalive-seconds.help.txt=If the pool currently has more than Thread Pool Core Size threads, \
excess threads will be terminated if they have been idle for more than the Kee-Alive Seconds. Limited from 1 to 3600.

f-radius-thread-pool-queue-size=Thread Pool Queue Size
f-radius-thread-pool-queue-size.help=Number of request that can be queued for the pool. Click the Info icon for details.
f-radius-thread-pool-queue-size.help.txt=The number of requests that can be queued for the pool before further requests \
will be silently dropped. See notes for Thread Pool Core Size on the interplay with Pool Max Size. Limited from 1 to 1000.

client-config-instance=Radius Client

a-client-ip-address-label=Client IP Address
a-client-ip-address-label.help=The IP Address of the client.
a-client-ip-address-label.help.txt=Section 5.4 of the RADIUS Authentication Specification, RFC 2865, indicates that \
  the source IP address of the Access-Request packet MUST be used to identify a configured client and thence determine \
  the shared secret to use for decrypting the User-Password field. The Client IP Address field should hold the source IP address of the \
  client. This should match the value obtained from Java's InetSocketAddress.getAddress().toString(). If there is any \
  question, send an Access-Request packet to OpenAM's RADIUS port and watch for a message stating, "No Defined RADIUS Client \
  matches IP address '/127.0.0.1'. Dropping request." Then copy the value in single quotes into this field.


b-client-secret-label=Client Secret
b-client-secret-label.help=This secret shared between server and client for encryption of the user password.
b-client-secret-label.help.txt=This secret must be conveyed to the RADIUS client and entered into its configuration \
before the User-Password field of incoming Access-Request packets can be decrypted to validate the password for the \
represented by that packet. A default value is generated for you but you can enter a custom value if desired.

c-client-log-packets=Log Packet Contents for this Client
c-client-log-packets.help=Indicates if full packet contents should be dumped to the log.
c-client-log-packets.help.txt=When troubleshooting issues with RADIUS it is helpful to know what was received in \
  a given packet. Enabling this feature will cause packet contents to be logged in a human consumable format. The \
  only caveat is that the USER_PASSWORD field will be obfiscated by replacing with asterisks. This should only be \
  enabled for troubleshooting as it adds significant content to logs and slows processing.


d-handler-class=Handler Class
d-handler-class.help=The fully qualified name of a class to handle incoming RADIUS Access-Requests for this client.
d-handler-class.help.txt=This class must implement the <code>com.sun.identity.authentication.modules.radius.server.spi.AccessRequestHandler</code> \
  interface to handle incoming Access-Request packets and provide a suitable response. An instance of this class is \
  created when configuration is first loaded to validate the class and then once for each new request. The configuration \
  properties will only be passed for the request handling instances and not when validating the class.<br/><br/><br/>

e-handler-config-params=Handler Class Configuration Properties
e-handler-config-params.help=Properties needed by the handler class for its configuration.
e-handler-config-params.help.txt=These properties are provided to the handler via its \
  <code>init</code> method prior to the call to handle the request packet. If these values are changed the next \
  handler instance created for an incoming request will receive the updated values. Each entry assumes that the first '=' \
  character incurred separates a key from its value. All entries are placed in a properties file handed to each handler \
  instance<br/><br/><br/>




Tuesday, December 9, 2014

OpenAM Answers Its 1st RADIUS Request

For those who are waiting to hear back on the progress adding RADIUS Server support to OpenAM, your wait is over. Pasted below is the output of the first ever successful authentication response from OpenAM to a RADIUS client. It just so happens that this client is also included in the codebase enhancements. It is a very simple command line client that I've included for testing purposes. It uses a radius.properties config file in the current directory containing the following items telling it where to connect, the secret shared with the RADIUS server for encryption and integrity checks, and if packets should be dumped in a readable fashion to the command line.

$ cat radius.properties
secret=MY-SECRET
host=127.0.0.1
port=1812
show-traffic=true

Upon startup it prompts for username and password (please don't tell anyone my demo user's password ;-) and then sends a RADIUS AccessRequest to the targeted server. Upon receiving an AccessAccept response it dumped that response to the console as shown below and then exited. Those NAS fields are hard coded and have no meaning in this test but are required by the RFC to make the incoming request be valid on the server side.

$ java -cp openam-auth-radius-11.0.2-2014.12.09_04.15.jar com.sun.identity.authentication.modules.radius.ConsoleClient
? Username: demo
? Password: password

Packet To 127.0.0.1:1812
  ACCESS_REQUEST [1]
    - USER_NAME : demo
    - USER_PASSWORD : *******
    - NAS_IP_ADDRESS : localhost/127.0.0.1
    - NAS_PORT : 1200

Packet From 127.0.0.1:1812
  ACCESS_ACCEPT [1]

---> SUCCESS! You've Authenticated!


How about entering the wrong password? We are denied access as expected.

$ java -cp target/openam-auth-radius-11.0.2-2014.12.09_04.15.jar com.sun.identity.authentication.modules.radius.ConsoleClient
? Username: demo
? Password: bad-password

Packet To 127.0.0.1:1812
  ACCESS_REQUEST [1]
    - USER_NAME : demo
    - USER_PASSWORD : *******
    - NAS_IP_ADDRESS : localhost/127.0.0.1
    - NAS_PORT : 1200

Packet From 127.0.0.1:1812
  ACCESS_REJECT [1]

---> Sorry. Not Authenticated.

I'll be posting more entries on the RADIUS solution and how it is configured and hopefully some of what I've learned about OpenAM's internal workings. But for now, to be clear, I've only defined this one client in the OpenAM console instructing OpenAM to authenticate requests for that particular client against the root realm, "/", and authentication chain, "ldapService", which only contains the DataStore authentication module within it. So what happens when there are more modules in the chain?

Stay tuned.

Thursday, December 4, 2014

Searching for an Open AM Session In The Console

This is a short post to capture something I've been wondering about but never taken the time to figure out. The Sessions tab in OpenAM's admin console shows a table of current sessions and includes a Search box. I've tried regular expressions in there but nothing seems to work except an exact match and a single character asterisk.

Well, as part of getting the RADIUS server running in OpenAM I just ran into the SessionService java class today. And I happened to see that its singleton instance had a method to list valid sessions. That method takes a pattern string. That delegated to a matchFilter method as shown below:

    public static boolean matchFilter(String string, String pattern) {
        if (pattern.equals("*") || pattern.equals(string)) {
            return true;
        }

        int length = pattern.length();
        int wildCardIndex = pattern.indexOf("*");

        if (wildCardIndex >= 0) {
            String patternSubStr = pattern.substring(0, wildCardIndex);

            if (!string.startsWith(patternSubStr, 0)) {
                return false;
            }

            int beginIndex = patternSubStr.length() + 1;
            int stringIndex = 0;

            if (wildCardIndex > 0) {
                stringIndex = beginIndex;
            }

            String sub = pattern.substring(beginIndex, length);

            while ((wildCardIndex = pattern.indexOf("*", beginIndex)) != -1) {
                patternSubStr = pattern.substring(beginIndex, wildCardIndex);

                if (string.indexOf(patternSubStr, stringIndex) == -1) {
                    return false;
                }

                beginIndex = wildCardIndex + 1;
                stringIndex = stringIndex + patternSubStr.length() + 1;
                sub = pattern.substring(beginIndex, length);
            }

            if (string.endsWith(sub)) {
                return true;
            }
        }
        return false;
    }

Simply speaking, it allows for an exact match, a single '*' to match everything, or inclusion of from one to many '*' characters. So I threw together a quick test that had the following results. The string on the left is the first String to the method and the second is the pattern.

("12345?", "*12345?") = true
("12345?", "12345?*") = true
("12345?", "*2345?") = true
("12345?", "12345*") = true
("12345?", "*?") = true
("12345?", "1*") = true
("12345?", "*23*5?") = true
("12345?", "*2*5?") = true
("12345?", "**5*") = true
("12345?", "*23*5*") = true

Once I discovered that I wondered if this was what backed the search field in the Sessions console. And sure enough the behavior there appears to match the behavior here exactly. So the Search field supports the following rules:
  • You can include from one to many '*' characters. 
  • They can also be at the beginning and the end. 
  • They match on zero to many characters. 
This may be documented somewhere but I wanted to capture it in case it wasn't well know. Enjoy.





Monday, November 10, 2014

How to Tell Programmatically if OpenAM has Finished Starting Up

Update: this post and its thread eventually led to the correct and OpenAM specific mechanism. The short answer is: com.sun.identity.setup.AMSetupServlet (located in openam's core jar) has a public static method isCurrentConfigurationValid() that returns a boolean value. During startup it repeatedly returns false until OpenAM is ready for business and then returns true. Calling core classes after that point solves the problems that I originally outlined in this post. (2014.12.16)


I'm breaking a promise to flesh our more detail from my last post but do plan on coming back to provide those details later. I just couldn't wait to capture and share what I just found. As noted in my previous post I'm adding code to make OpenAM be a RADIUS Authentication server. I've spent some time getting configuration pieces into OpenAM's console. (Post a comment if those notes would be useful in a post.) Now I need to read them at startup time. Simple. Get a handle on the ServiceConfigManager class and go get what you need. Well, not quite.

It turns out that if you call some of these core pieces of OpenAM before they are all brought up you end up shooting OpenAM in the brain. It won't ever recover and no requests to the server will ever get an answer. OpenAM's docs says that the best way to know if OpenAM is up and running is to look at isAlive.jsp rooted at the web-app-root/isAlive.jsp. And it is true. If you request that URL as soon as you start tomcat the browser will spin for about 20 seconds on my box and finally the page will come up indicating Server is ALIVE.

So I thought, there must be some blocking code in isAlive.jsp that does that magic. So I tried using its few lines of code. No go. They too access sensitive ground and OpenAM plays dead. So I stepped back to calling it over http. Now, I can get the webapp's root path from ServletContext. But what port is my server running on? That is only available to servlet code when an incoming http request appears. Until then there is no standard way to get it. PLEASE correct me if I'm wrong there. I'd love to learn that secret. But the specs don't cover it except in the case of an http request.

Then I thought, hey, I can get a Dispatcher and call it without going down to the http protocol. Right? And that is where the magic just happened to appear. Or rather, it disappeared; into null.

In the following code the astute will note that I'm using SpringFramework constructs. Yes I have Spring's Dispatcher servlet defined in OpenAM's web.xml for dependency injection and some REST endpoints that I've added to my local code base. But what I'm doing here can be done with a simple servlet in its init method where you get the ServletContext from the ServletConfig passed to you. That being the case lets look at the code:

@Controller
public class RuntimeServiceController implements ServletContextAware {

    @Override
    public void setServletContext(ServletContext sc) {
        final String cp = sc.getContextPath();
        final String url = cp + "/isAlive.jsp";

        RequestDispatcher d = sc.getRequestDispatcher(url);
        HttpServletRequest req = createRequest();
        HttpServletResponse res = createResponse();
        try {
            d.forward(req, res);
        } catch (ServletException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        // then evaluate captured content
        if (isServerIsUp(res)) {
            // go look at config stuff here
        }
    };
}

When this code ran I got an NPE when calling d.forward(req, res). It doesn't take rocket science to tell that my dispatcher, d, was null. Hmmm. Weird. Maybe the JSP engine is not up yet. If I keep checking will I eventually get a dispatcher? After all, I know that the resource exists at that URL or will at some point in time. So I wrote another version like so:

@Controller
public class RuntimeServiceController implements ServletContextAware {

    @Override
    public void setServletContext(final ServletContext sc) {
        final String cp = sc.getContextPath();
        final String url = cp + "/isAlive.jsp";

        Runnable r = new Runnable() {

            @Override
            public void run() {
                RequestDispatcher d = null;
                long start = System.currentTimeMillis();

                while (d == null) {
                    d = sc.getRequestDispatcher(url);

                    if (d != null) {
                        // now call isAlive.jsp
                    }
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        return;
                    }
                }
            }
        };
        Thread t = new Thread(r);
        t.setName("RuntimeServiceController isAlive.jsp Accessor");
        t.setDaemon(true);
        t.start();
    }

}

I'm launching a Thread to keep testing for the dispatcher so that I don't lock up the web container's start up processes. And this still ended up giving an error. But not until I attempted to call the dispatcher's forward. The dispatcher did indeed eventually appear after about 20 seconds on my box. And that was about how long it took for isAlive.jsp to appear in the browser. Hmmm. Was there a connection? Is OpenAM ready for business once the dispatcher is available?

I stripped out my code for calling isAlive.jsp and added config calls like the code in isAlive.jsp itself and guess what? It worked!!! Now to be fair, I don't know if that is guaranteed to always work. But so far it hasn't failed. Perhaps others familiar with OpenAM can answer that. Any Forgerock gurus around that can answer that? And what about other containers? I don't know. I'm running on tomcat8 on JDK 1.6. If you test it on your server post a comment here and let the rest of us know.

Enjoy.


Friday, October 3, 2014

Open AM as Radius Server

Update: Wondering how this turned out? See OpenAM Answers its 1st Radius Request and following posts for more detail.


It has been a long productive summer with little time to post. So here comes. I've been delving further into Open AM during that time and need to capture progress for when others come across my contributions and wonder what on earth I was doing with a certain bit of code. Most likely that will be me in a year or two. :-)

One requirement of my team is to implement multi-factor authentication for our enterprise web access infrastructure. And since we were implementing it there, we were asked to implement it for VPN too using the same multi-factor mechanisms. I know, doesn't make sense. They are two completely different technologies. Heavy desktop client versus browser applications. HTTP versus...not sure what at the time. But we dug in to see what was possible.

Open AM Backing our VPN?

Over a couple of weeks there appeared several reasons why this could be possible. First, Open AM supplies a number of Authentication Modules that can be leveraged to protect resources including SMS One Time Passcode (OTP) and Google Authenticator. Just what we wanted to start out with or our second factor. Furthermore, their interface is published for adding your own implementations and contributing them back if desired which has results in quite a number of modules being available in baseline. And these can be "chained" together forcing users to provide other factors after the initial username and password are submitted implementing the multi-factor feature.

Second, using Open AM's rest endpoint to authenticate (see the developer guide) a given authentication module with which a user is interacting during authentication exposes the requirements of the user in JSON along with a session token keeping track of where they are in the authentication process, the labels that should be exposed for a given piece of data expected from a user, and delineated locations within the JSON to inject the user's answers. This structure is then submitted back to the server potentially followed by another such response for the next module in the chain. Not that I'll be using the rest endpoint although it could, but more importantly, Open AM must clearly provide that same information if our solution was going to be running within Open AM and we'd need that information when soliciting additional values from the user when accessing VPN.

Third, it turns out that the VPN software that we use is a desktop application that speaks to a Network Access Server or NAS. The NAS can use two mechanisms on the back end to authentication users for VPN: an LDAP directory or a Radius server. And one of those Open AM authentication modules speaks the Radius protocol allowing Open AM to act as a Radius client and authenticate against a Radius server.

To sum up then, Open AM already had support for speaking the Radius protocol. Open AM authentication can be a multi-step processes by using an authentication chain with a distinct authentication module handling each step with clear information required from the user at each step and allowing the user to move from one step to the next only after they have provided the information required for that step. And VPN clients can speak to Radius servers.

The only remaining question was this: can a VPN interaction be a multi-step process such as submitting username and password followed by a prompt for more information, then more, and so on until an authentication chain was completed and the user successfully authenticated with all authentication modules?

Radius Protocol

At its core, the Radius protocol is quite simple. The RFC is just over 70 pages. There a numerous related RFCs but most appear to be simply adding and defining additional fields that are added to these packets to enable other overlying protocols to use Radius to achieve their authentication. The protocol itself consists of one client initiated packet, an ACCESS_REQUEST, and three potential server responses; ACCESS_ACCEPT, ACCESS_REJECT, and ACCESS_CHALLENGE respectfully for "successfully authenticated", "failed authentication", and "hey, I need more information".  That last one caught my attention.

I dug into Open AM's openam-auth-radius module. It had java objects for each packet type but only supported marshaling from the ACCESS_REQUEST object to the on-the-wire bytes and from the on-the-wire response bytes into the other three objects. I first had to make that marshaling bidirectional which took a day. Then, I added a RadiusListener that opened a UDP server socket to receive requests followed by some state flow classes to response in different ways to see how the VPN client reacted.

It was beautiful! That ACCESS_CHALLENGE response caused the VPN client to present a screen with an input text field labeled "Answer" and a message area presenting the content of a Reply-Message field included in the response and a Continue button. When pressed, the client then issues another ACCESS_REQUEST to the server as shown in the image in the next section. And in that next request the same user name is again submitted and the entered answer comes through in the User-Password field which was odd but works.

Further, the ACCESS_CHALLENGE response packet type also supports the State field which can be filled with state information of my choice that gets passed back "as-is" in the following ACCESS_REQUEST message enabling my prototype Radius Server to be stateless with all state being preserved via the state field in the client between requests. And there appeared to be no limit to the number of challenge/response cycles. The cycle only terminated when an ACCESS_ACCEPT or ACCESS_REJECT was returned.

That answered the question of supporting a multi-step flow. It was a resounding YES!

Multi-Factor Proof Of Concept

The next step was to give this multi-factor authentication a try. But one thing became critically clear pretty early. Provisioning MUST be discussed and planned. By provisioning I mean what multi-factor channels are going to be supported, how are users going to elect which one they can or want to use, and how will the system know for a given user which channel has been selected by them?

To drive that point home I built a POC that intentionally supported four different mechanisms. (Actually I was only going for three and I learned of a fourth, synthesized voice call, while on vacation and dropped it in within a few hours one evening.) The four mechanisms are SMS OTP, voice delivered OTP, Google authenticator, and what I call a Smart Phone Responder which I'll explain in a following post. I've yet to get to the Google Authenticator due to project pressures. And I may not ever get there since my next step is to move this into Open AM and use their authentication modules directly. At time of writing this post the POC is currently a standalone java process.

Having more than one channel demands that provisioning be considered. To push the limits of VPN I decided to implement a flow allowing the users to provision their channel "Just-In-Time". By this I mean if they didn't already have a selected channel they would be presented with an ACCESS_CHALLENGE message to that effect and offering selection of the choices available. That actually worked quite well. At least it did from a developer's point of new. You business analysts and technical writers will no doubt cringe at my content. Here is what a user sees when they submit their username and password and no previously selected channel:


Yeah, that last line being below the view screen bugs me. But there was no way to that I could discover to tell the client to present more space. And the vendor wasn't very helpful on that front so I had to live with it. No worries. Its only a POC. Right?

By entering the corresponding letter, that user has then elected to use that channel. The POC then persists that election in a file. When they try again, it knows what their preference is and they go right to the next factor for authentication. Time to implement some actual channels. I'll cover the different channels that were implemented in the next few posts.

Until then, Enjoy.

Thursday, May 15, 2014

Jetty 9.x Swallowing Handler Exceptions?

Over the last couple of days I've been pulling out what little hair I had and hoped to save someone else from the same fate. In migrating the WAMulator's embedded Jetty server to version 9.1.5 my builds were suddenly running really slow, tests were failing, and I was seeing numerous lines like this from tests that posted credentials to the WAMulator's sign-in endpoint:

May 14, 2014 6:58:30 AM org.apache.commons.httpclient.HttpMethodDirector executeWithRetry
INFO: Retrying request

No problem. Right? I'd expect some runtime issues. But that line was really peculiar. It was almost as if the sign-in endpoint, implemented as a Jetty Handler, was dropping the connection without sending a response. A lot of digging showed that was definitely the problem when I uncovered some swallowed stack traces like this on the client side of these test meaning in the unit tests themselves not in the WAMulator fielding those http requests:

java.net.SocketTimeoutException: Read timed out
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:152)
...

 Furthermore, when I looked at the difference in time from when I made the call to when that stack trace was thrown it was exactly 30 seconds give or take a couple of milliseconds. That looked like a TCP timeout.

So I started up the WAMulator with a debugger and stepped through the sign-in handler line by line until something happened. The short version is that a call to the to addCookie() method of Jetty's Response object was internally calling cookie.isHttpOnly() which was causing a NoMethodFoundException to be thrown.

Now that is definitely a problem that I can and should dig into. But more important to me is why I had to spend so much time tracing through code to figure that out? Why was I not seeing a stack trace along those lines in the console output?

Pay Attention to the Output Stupid

To figure that out I stepped further into Jetty's depths as that exception was caught and re-thrown and finally handled in Jetty's HttpChannel which was indeed logging the exception. Yes, it was being logged. The only problem was that the logger library being used by Jetty was SLF4J and it was logging everything with an instance of NOPLogger which does nothing with such events since it is a "no operation" logger.

I was questioning the quality of a library that doesn't log such errors and forces consumers to dig to these depths to figure out what is wrong. But then I happened to notice some lines in the console output relative to SLF4J now that it was on my mind:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

Hmmm...that looks like it might be related and I'm looking more like the idiot than the library. So I google it and sure enough, I needed to add something to tell SLF4J where to route its logs since it is just a facade that everyone can leverage to avoid the logging library wars of some years back. And it then requires you to choose to what underlying library of your choice will receive those events. So I added the following dependency in my Maven project to route logging events to the java.util.logging mechanism:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jdk14</artifactId>
    <version>1.7.7</version>
</dependency>


And suddenly, viola, my swallowed stack traces were suddenly visible and pointing exactly to where the problem was and where I needed to focus my attention:

May 15, 2014 9:25:16 AM org.eclipse.jetty.util.thread.QueuedThreadPool$3 run
WARNING: 
java.lang.NoSuchMethodError: javax.servlet.http.Cookie.isHttpOnly()Z
at org.eclipse.jetty.server.Response.addCookie(Response.java:239)
at org.lds.sso.appwrap.ui.rest.SelectUserHandler.doHandle(SelectUserHandler.java:193)
at org.lds.sso.appwrap.rest.RestHandlerBase.handle(RestHandlerBase.java:56)
at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:109)
...

The Moral of the Story

So if you think Jetty is swallowing exceptions, it probably is. And rightly so. You didn't tell it where to route them. And SLF4J was telling you that when it wrote those lines to the console. If they are there, then you're not getting the whole picture.

Oh, and my underlying problem? Jetty 9 requires Servlet's 3.0. And I was including a dependency on the 2.4 version from my Jetty 6.x days so long ago. Once I changed that, things worked a whole lot better.

Thursday, March 27, 2014

Open AM Adding DELETE and PUT Http Actions to Policies

I'm investigating Forge Rock's Open AM product again this morning. Version 11 out of the box only supports http actions of GET and POST when configuring access policies. With a little research on the internet and some digging through the LDAP configuration directory I found an attribute named sunServiceSchema in entry ou=1.0,ou=iPlanetAMWebAgentService,ou=services,dc=openam,dc=forgerock,dc=org. This contained the following XML document. Note the GET and POST http actions highlighted. That looked promising:


<?xml version="1.0" encoding="UTF-8"?>


<ServicesConfiguration><Service name="iPlanetAMWebAgentService" version="1.0"><Schema i18nFileName="amWebAgent"  i18nKey="iplanet-am-web-agent-service-description"  revisionNumber="10" 
<Global validate="yes" >
  <AttributeSchema cosQualifier="default"  i18nKey=""  isSearchable="no"  name="serviceObjectClasses"  syntax="string"  type="list" >
<DefaultValues>
      <Value>iplanet-am-web-agent-service</Value>
</DefaultValues>
</AttributeSchema>
</Global>

  <Policy>
<AttributeSchema cosQualifier="default"  i18nKey="GET"  isSearchable="no"  name="GET"  syntax="boolean"  type="single"  uitype="radio" >
<IsResourceNameAllowed></IsResourceNameAllowed>
<BooleanValues>
<BooleanTrueValue i18nKey="allow" >allow</BooleanTrueValue>
<BooleanFalseValue i18nKey="deny" >deny</BooleanFalseValue>
</BooleanValues>
</AttributeSchema>
<AttributeSchema cosQualifier="default"  i18nKey="POST"  isSearchable="no"  name="POST"  syntax="boolean"  type="single"  uitype="radio" >
<IsResourceNameAllowed></IsResourceNameAllowed>
<BooleanValues>
<BooleanTrueValue i18nKey="allow" >allow</BooleanTrueValue>
<BooleanFalseValue i18nKey="deny" >deny</BooleanFalseValue>
</BooleanValues>
</AttributeSchema>
</Policy>

</Schema></Service></ServicesConfiguration>

On a hunch I added the following two additional attribute schema elements by copying the existing ones and modifying their i18nKey and name as highlighted below:

<AttributeSchema cosQualifier="default"  i18nKey="PUT"  isSearchable="no"  name="PUT"  syntax="boolean"  type="single"  uitype="radio" >
 <IsResourceNameAllowed></IsResourceNameAllowed>
 <BooleanValues>
     <BooleanTrueValue i18nKey="allow" >allow</BooleanTrueValue>
     <BooleanFalseValue i18nKey="deny" >deny</BooleanFalseValue>
  </BooleanValues>
</AttributeSchema>
<AttributeSchema cosQualifier="default"  i18nKey="DELETE"  isSearchable="no"  name="DELETE"  syntax="boolean"  type="single"  uitype="radio" >
 <IsResourceNameAllowed></IsResourceNameAllowed>
 <BooleanValues>
    <BooleanTrueValue i18nKey="allow" >allow</BooleanTrueValue>
     <BooleanFalseValue i18nKey="deny" >deny</BooleanFalseValue>
 </BooleanValues>
</AttributeSchema>

I then bounced the Open AM server. Upon selecting a policy and editing its rule I then saw that DELETE and PUT were now showing in the UI. The question then was will they be honored and work with policy evaluation. Since I don't yet have an application behind an agent to verify that they work I turned to the /authorize restful endpoint. I first acquired a token via the /authenticate restful endpoint:

curl --request POST --header "X-OpenAM-Username: <my-user>" --header "X-OpenAM-Password: <my-password>" --header "Content-Type: application/json" --data "{}" https://signin-int.lds.org/openam/json/authenticate { "tokenId": "AQIC5wM2LY4SfcxzmBcakM-l7x_FXkMzkT21Ok9Bhgf2zQs.*AAJTSQACMDIAAlNLABQtNTIxNjI5NTA3ODMyMzM1OTQxMAACUzEAAjAx*", "successUrl": "/openam/console" }

Once I had a token I was ready to hit the /authorize restful endpoint. But first off my policy looked like the following:


Policy Name: test.lds.org
Rules:
 Name: /directory
 Resource Name: http://test.lds.org/directory/*
 Actions Checked: DELETE, GET, POST
Subjects:

 Name: Allow All Users
 Authentication Module: Authenticated Users

Conditions: none
Response Providers: none

One call to test access is shown below with the token from the /authenticate call above truncated for this post but included in its entirety in the real call. Note that the documentation as of this post did not indicate that an action parameter was supported but a quick google search turned up a blog that mentioned it. Good thing we have bloggers eh? :-)

curl "https://signin-int.lds.org/openam/identity/authorize?uri=http%3A%2F%2Ftest.lds.org%2Fdirectory%2Ftest&action=DELETE&subjectid=AQIC...*" boolean=true

I ran the the test specifying a different action each time in the query parameter. The results showed as follows for each action tested. Note that I also tested a lowercase version of the POST action and learned that the value of the action query parameter is case sensitive. That is nice to know and would make a good addition to the documentation:

DELETE: boolean=true
GET: boolean=true
POST: boolean=true
post: exception.name=com.sun.identity.idsvcs.GeneralFailure Invalid action name: post for service: iPlanetAMWebAgentService.
PUT: boolean=false

I then changed the actions in the policy as follows:

Actions Checked: DELETE, POST, PUT

And the results then showed these responses:

DELETE: boolean=true
GET: boolean=false
POST: boolean=true
PUT: boolean=true

So it appears that adding those sections to that XML document in the configuration directory did indeed add support for DELETE and PUT as desired. Once I get an agent and application combination set up I'll verify these changes there as well and cover that in another post. Enjoy.