Wednesday, December 2, 2015

Using OpenAM 13's Radius Server

It has been a long and busy few months as we work to migrate from Oracle's OAM product to OpenAM. With OpenAM v13 just around the corner I've been asked to kick the tires of the new Radius Server that is included therein. So I've taken time to summarize my results here. If you are one of those readers that peeks at the last chapter before reading a book I'll save you that step: it works, and works nicely.

Getting It Installed

After downloading, I opened the nightly build zip and copied OpenAM-13.0.0-SNAPSHOT.war to my tomcat 8's webapps directory with name sso13.war. Then in a bash shell I created ~/sso13 and fired up tomcat running on Java 7. I used default configuration and it found and filled my ~/sso13 directory. Once configuration was complete I followed its provided link to the sign in page and signed in as amadmin.

Wow. The forgerock crew has been busy. What was previously the Access Control tab is now the Realms tab or rather the Realms page and that portion of the admin console is now moved to XUI. Looks great.

Turning It On

I select the Configuration link and get dumped back into the legacy JATO UI. Maybe by v14 it will be removed altogether. Here's hoping.

I select Global and then Radius Server. I enable the server on port 1813 since I have another instance of OpenAM running with the server on 1812. I then poke around in the ~/sso13/sso13 directory structure looking for log files indicating that Radius configuration was changed but didn't find anything.

So I went to the admin console's debug page at /sso13/Debug.jsp. Sure enough, in the Category drop down box at the top there is now a Radius option. I select it, set the level to Message, and press Submit. It asked me to confirm the change which I did.

Still didn't see any radius related log file. Decided to try generating some traffic.

Using Console Client

Back in May I outlined a tool for testing communication with any radius server. There was discussion of rolling console client into the ssoadmin tool. But time did not permit prior to v13's release. Is console client still available via an executable jar? When forgerock engineer Jamie Bowen rolled the Radius Server code into baseline, he split the code base into three distinct maven modules as manifest by the following jars in the WEB-INF/lib directory:

openam-auth-radius-13.0.0-SNAPSHOT.jar
openam-radius-common-13.0.0-SNAPSHOT.jar
openam-radius-server-13.0.0-SNAPSHOT.jar

The first holds the legacy radius authentication module. The second holds the radius protocol classes. And the third holds the radius server functionality. On a whim I attempted to execute the server jar and low and behold, console client revealed it was indeed still available. Nice! (Note: For anyone reading this using an version beyond v13.0.0 check the documentation to see if it has been moved to ssoadmin in your version.)

$ java -jar openam-radius-server-13.0.0-SNAPSHOT.jar
Missing required config file 'radius.properties' in current directory /Users/boydmr/tomcat8/apache-tomcat-8.0.9/webapps/sso13/WEB-INF/lib/.
Must Contain:
 secret=<shared-secret-with-server>
 host=<hostname-or-ip-address>
 port=<port-on-target-host>

So I created radius.properties with the following values. Don't tell anyone my password. :-)

secret=letmein
host=127.0.0.1
port=1813
show-traffic=true

Now I ran console client again with the following output and it hung as expected since I did not define a client in the Radius Server:

$ java -jar openam-radius-server-13.0.0-SNAPSHOT.jar
? Username: amadmin
? Password: password1

Packet To 127.0.0.1:1813
DebugConfiguration:12/02/2015 08:10:02:028 AM MST: Thread[main,5,main]
'/debugconfig.properties' isn't valid, the default configuration will be used instead: Can't find the configuration file '/debugconfig.properties'.
  ACCESS_REQUEST [1]
    - USER_NAME : amadmin
    - USER_PASSWORD : *******
    - NAS_IP_ADDRESS : localhost/127.0.0.1
    - NAS_PORT : 0

Accessing Radius Logs

At this point I ran bash's find while sitting in ~/sso13/sso13 and found the file that I was looking for:

./debug/Radius

When I cat that file I found what I was looking for. You can see the dropped packet warning indicating a client with that IP address was not defined.

$ cat ./debug/Radius
amRadiusServer:12/02/2015 08:10:02:054 AM MST: Thread[RADIUS-1813-Listener,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-154]
RadiusServerEventRegistrar.packetReceived() called by EventBus
amRadiusServer:12/02/2015 08:10:02:056 AM MST: Thread[RADIUS-1813-Listener,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-154]
RadiusServerEventRegistrar.packetReceived() - total now 1
amRadiusServer:12/02/2015 08:10:02:056 AM MST: Thread[RADIUS-1813-Listener,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-154]
WARNING: No Defined RADIUS Client matches IP address /127.0.0.1. Dropping request.

Defining a Client

Back in the admin console in Configuration/Global/Radius Server I now press the New button in the Secondary Instances table. (Wish we could give that table a name rather than that generic term. But I digress.) The form auto-populates a number of fields including the realm and chain properties for the default handler and a default IP address for testing from the same machine as is running OpenAM. Sweet. I name the client test, enable packet content logging, set the shared secret to the one used in my radius.properties file, and press the Add button. 

Contemplating Logs

The tailed Radius log file shows configuration changing... three times. Don't know why. The following lines appear to include three repeats of configuration changing as if there were three listeners registered for configuration changes. That might need attention if it caused problems. But as you'll see, it appears to be benign since thinks work just fine.

amRadiusServer:12/02/2015 08:24:26:980 AM MST: Thread[smIdmThreadPool,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-7]
Entering globalConfigChange()
amRadiusServer:12/02/2015 08:24:26:981 AM MST: Thread[smIdmThreadPool,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-7]
Leaving globalConfigChange()
amRadiusServer:12/02/2015 08:24:26:983 AM MST: Thread[RADIUS-RadiusServerManager,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-263]
Exiting ConfigChangeListener.waitForConfigChange, returning RADIUS Config Changed. Loading...
amRadiusServer:12/02/2015 08:24:26:994 AM MST: Thread[smIdmThreadPool,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-10]
Entering globalConfigChange()
amRadiusServer:12/02/2015 08:24:26:995 AM MST: Thread[smIdmThreadPool,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-10]
Leaving globalConfigChange()
amRadiusServer:12/02/2015 08:24:26:999 AM MST: Thread[smIdmThreadPool,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-12]
Entering globalConfigChange()
amRadiusServer:12/02/2015 08:24:26:999 AM MST: Thread[smIdmThreadPool,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-12]
Leaving globalConfigChange()
amRadiusServer:12/02/2015 08:24:27:987 AM MST: Thread[RADIUS-RadiusServerManager,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-263]
RADIUS Config Changed. Loading...
amRadiusServer:12/02/2015 08:24:28:029 AM MST: Thread[RADIUS-RadiusServerManager,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-263]
New RADIUS configuration loaded.[RadiusServiceConfig YES 1813 P( 1, 10, 10, 20), C( /127.0.0.1=test, letmein, true, org.forgerock.openam.radius.server.spi.handlers.OpenAMAuthHandler, {realm=/, chain=ldapService})]

amRadiusServer:12/02/2015 08:24:28:030 AM MST: Thread[RADIUS-RadiusServerManager,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-263]
Updating client configs.
amRadiusServer:12/02/2015 08:24:28:030 AM MST: Thread[RADIUS-RadiusServerManager,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-263]
Entering ConfigChangeListener.waitForConfigChange()
amRadiusServer:12/02/2015 08:24:28:030 AM MST: Thread[RADIUS-RadiusServerManager,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-263]
Exiting ConfigChangeListener.waitForConfigChange, returning RADIUS Config Changed. Loading...
amRadiusServer:12/02/2015 08:24:29:034 AM MST: Thread[RADIUS-RadiusServerManager,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-263]
RADIUS Config Changed. Loading...
amRadiusServer:12/02/2015 08:24:29:034 AM MST: Thread[RADIUS-RadiusServerManager,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-263]
New RADIUS configuration loaded.[RadiusServiceConfig YES 1813 P( 1, 10, 10, 20), C( /127.0.0.1=test, letmein, true, org.forgerock.openam.radius.server.spi.handlers.OpenAMAuthHandler, {realm=/, chain=ldapService})]

amRadiusServer:12/02/2015 08:24:29:034 AM MST: Thread[RADIUS-RadiusServerManager,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-263]
Updating client configs.
amRadiusServer:12/02/2015 08:24:29:034 AM MST: Thread[RADIUS-RadiusServerManager,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-263]
Entering ConfigChangeListener.waitForConfigChange()
amRadiusServer:12/02/2015 08:24:29:035 AM MST: Thread[RADIUS-RadiusServerManager,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-263]
Exiting ConfigChangeListener.waitForConfigChange, returning RADIUS Config Changed. Loading...
amRadiusServer:12/02/2015 08:24:30:039 AM MST: Thread[RADIUS-RadiusServerManager,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-263]
RADIUS Config Changed. Loading...
amRadiusServer:12/02/2015 08:24:30:040 AM MST: Thread[RADIUS-RadiusServerManager,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-263]
New RADIUS configuration loaded.[RadiusServiceConfig YES 1813 P( 1, 10, 10, 20), C( /127.0.0.1=test, letmein, true, org.forgerock.openam.radius.server.spi.handlers.OpenAMAuthHandler, {realm=/, chain=ldapService})]

amRadiusServer:12/02/2015 08:24:30:040 AM MST: Thread[RADIUS-RadiusServerManager,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-263]
Updating client configs.
amRadiusServer:12/02/2015 08:24:30:040 AM MST: Thread[RADIUS-RadiusServerManager,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-263]
Entering ConfigChangeListener.waitForConfigChange()

Trying Again

Now we run the console client again and this time with a successful result:

$ java -jar openam-radius-server-13.0.0-SNAPSHOT.jar
? Username: amadmin
? Password: password1

Packet To 127.0.0.1:1813
DebugConfiguration:12/02/2015 08:28:59:279 AM MST: Thread[main,5,main]
'/debugconfig.properties' isn't valid, the default configuration will be used instead: Can't find the configuration file '/debugconfig.properties'.
  ACCESS_REQUEST [1]
    - USER_NAME : amadmin
    - USER_PASSWORD : *******
    - NAS_IP_ADDRESS : localhost/127.0.0.1
    - NAS_PORT : 0

Packet From 127.0.0.1:1813
  ACCESS_ACCEPT [1]

---> SUCCESS! You've Authenticated!

In the tailed Radius log file a ton of lines scroll by. But embedded within them are the important ones that we are looking for:

...
amRadiusServer:12/02/2015 08:28:59:357 AM MST: Thread[pool-24-thread-1,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-154]
WARNING:
Packet from test:
  ACCESS_REQUEST [1]
    - USER_NAME : amadmin
    - USER_PASSWORD : *******
    - NAS_IP_ADDRESS : /127.0.0.1
    - NAS_PORT : 0
...
amRadiusServer:12/02/2015 08:28:59:404 AM MST: Thread[pool-24-thread-1,5,main]: TransactionId[73182e78-f842-4c29-ac57-9809fef53cfe-154]
WARNING:
Packet to test:
  ACCESS_ACCEPT [1]

Conclusions

The Radius Server functionality debuting in OpenAM 13 is working as expected. Open source and the collaborative process rocks. Doesn't it? Yes there are some new features that many of us would like to add in and will certainly start work on once the V13 code base is finalized and ready to roll. But what a great start for a Radius platform that can leverage many of the existing authentication features of OpenAM. Kudos to Jamie and the other engineers at forgerock for their work adding the Radius Server into baseline.

Enjoy.

Wednesday, May 27, 2015

Installing RADIUS Extensions in OpenAM 12

In in my previous post I shared a link for downloading a jar that contained the RADIUS extensions that we have made to the native RADIUS authentication module in OpenAM. In this post I'll show you how to tell OpenAM to expose and use the Radius Server functionality resident in that jar.

Installing

To use the Radius extensions in that jar you must perform the following simple steps:

  1. Shut down your servlet container such as tomcat.
  2. Replace the existing jar (the openam-auth-radius.jar file located in the WEB-INF/lib directory of a deployed, expanded OpenAM war) with the downloaded openam-auth-radius-1.0.1.jar. (The name of the jar is not important and does not have any impact on the runtime system.) The existing authentication module code has not been modified and will continue to work out of the new jar.
  3. Inject the following context listener declaration shown in bold text into the WEB-INF/web.xml file either above or below the existing guice context listener as shown.

    <!-- Initialises the Guice Injector. -->
       <listener>
            <listener-class>
            org.forgerock.guice.core.GuiceInitialisationFilter
            </listener-class>
        </listener>
        <listener>
            <listener-class>
    com.sun.identity.authentication.modules.radius.server.config.ServletContextListenerLauncher
            </listener-class>
        </listener>

  4. Restart your servlet container.
That is it. You now have the extended Radius support. 

As OpenAM starts up you'll now see the following lines logged to catalina.out since I used java.logging in the current implementation and not the OpenAM Debug logger. For brevity I've trimmed out the level, data, time, class, and method doing the logging and added line numbers for the discussion that follows:

1) [localhost-startStop-1] ---> ServletContextListenerLauncher starting RadiusServiceStarter

2) [localhost-startStop-1] Loaded OpenAM Authn Radius Module = 1.0.1 built 2015-05-21 19:54 UTC


3) [RADIUS-RadiusServiceStarter]  RadiusServerService not found. Loading...
4) [RADIUS-RadiusServiceStarter] Service Descriptor file for RadiusServerService found at: jar:file:/Users/boydmr/tomcat8/apache-tomcat-8.0.9/webapps/sso/WEB-INF/lib/openam-auth-radius-1.0.1.jar!/RadiusServerService.xml
5) [RADIUS-RadiusServiceStarter] Loading RADIUS Config...
6) [RADIUS-RadiusServiceStarter] --- Loaded Config ---
7) [RadiusServiceConfig NO 1812 P( 1, 10, 10, 10)]
8) [RADIUS-RadiusServiceStarter] RADIUS service disabled.

There are several important things to note in these lines:  

  • Lines 1 and 2 show the context listener being called and logging its version and build information. 
  • All lines related to Radius after that are logged by a thread launched by the context listener and suitably named as the  RADIUS-RadiusServiceStarter
  • Lines 3 and 4 will change for every start hereafter. It turns out that there is an admin console configuration page that is part of the feature set. When starting up, that service is looked for. If not found, as noted in line 3 due to this being our first startup on the extended features, the service descriptor file will be loaded out of the jar as noted in line 4. When next starting, a version of line 3 will indicate that the service was found in OpenAM configuration and hence doesn't need to be loaded.
  • Line 5 shows that configuration is being loaded from the admin console service which included suitable defaults for loading prior to an administrator ever accessing its page.
  • Lines 6 and 7 then show the specific pieces of information being loaded with the values in line 7 correlating with values found in the main configuration page. Namely, whether the Radius UDP listener is enabled, on what port it will listen if started, and the thread pool parameters. If we have any clients defined we would see an additional line per each configured client.
  • Finally, line 8 indicates that the Radius listener is not currently active to receive requests.
When you now access the admin console you'll see that the Radius configuration pages that I outlined in a previous post will now be accessible and can be used to enable the Radius Listener and define clients that can connect to it.

Enjoy.



Friday, May 15, 2015

Radius Connection Test Tool

I've mentioned in previous posts that changes contributed to OpenAM include making its radius jar executable so that it runs what I refer to as the Console Client. This is a very simple command line tool that should allow you to test connection to any radius server and corresponding server side configuration of the related client. I've used it many times with my enhanced OpenAM instance. Then again it is using the same code that the server is for the conversation. So leave a comment if your mileage varies with some other server.

To run this tool you first need to have an instance of the jar. Since the code is not yet in an instance of OpenAM and won't be until version 13 at the soonest, you can download a copy of the jar by clicking this link. Upon clicking that link you'll get the 1.0.1 version of the contributed code in a jar file named openam-auth-radius-1.0.1.jar.

Once you have the jar you run the console client like this:

java -jar openam-auth-radius-1.0.1.jar

The tool needs a configuration file to be located in the current directory named radius.properties. If not found it will write the following to the command line and exit. I've highlighted some interesting parts:

May 15, 2015 2:56:00 PM com.sun.identity.authentication.modules.radius.server.config.RadiusServiceStarter logModuleBuildVersion
INFO: Loaded OpenAM Authn Radius Module = 1.0.1 built 2015-05-12 22:36 UTC

Missing required config file 'radius.properties' in current directory /Users/boydmr/.
Must Contain:
 secret=<shared-secret-with-server>
 host=<hostname-or-ip-address>
 port=<port-on-target-host>

May Contain:
 show-traffic=true

Create a radius.properties file with suitable values for your test. For example, as noted I have OpenAM with the radius server enhancement running on my local machine. Therefore I create that file with these values pointing to my local server.

secret=letmein
host=127.0.0.1
port=1812
show-traffic=true

It works equally well going against a server located elsewhere. Now when I run Console Client it will prompt me for username and password before starting the radius conversation. I've entered my values already in this screen capture. You'll have to use a user and password that exist in your environment.

May 15, 2015 3:00:40 PM com.sun.identity.authentication.modules.radius.server.config.RadiusServiceStarter logModuleBuildVersion
INFO: Loaded OpenAM Authn Radius Module = 1.0.1 built 2015-05-12 22:36 UTC

? Username: demo
? Password: password 


Now what happens after entering the password depends on if you have defined a client on the server and the nature of the configured flow for this client. You will always see an access-request be sent to the server as shown below. But if, for example, the client is not yet defined, you'll see the following traffic be fired to the server and nothing will happen thereafter. The JVM is waiting for a response that will never come since the radius RFC requires that requests from unregistered IP addresses be dropped silently.

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 : 0

If the client is defined on the server with the IP address from which you are making this UDP request but the shared secret is incorrect then that packet to the server will be followed by this console output and the client will exit.

Packet From 127.0.0.1:1812
  ACCESS_REJECT [1]

---> Sorry. Not Authenticated.

On the server side for OpenAM it doesn't  log this case but simply indicates that an access-reject response was returned. This is because there is no way to distinguish between an incorrect secret and a packet that is corrupted or tampered with. So access is rejected.

If your server issues challenge responses you'll see a response like the following and the client will allow you to enter whatever information is suitable for that challenge, here a one time passcode ostensibly sent via SMS, and wait for you to press the Enter key to send it back to the server.


Packet From 127.0.0.1:1812
  ACCESS_CHALLENGE [1]
    - STATE : fa66c0a9
    - REPLY_MESSAGE : Enter One-Time Passcode (OTP) A one-time validation code has been sent to *******964

---> Enter One-Time Passcode (OTP) A one-time validation code has been sent to *******964
? Answer:

Once you enter your value it will now cause another access-request packet to be sent to the server. But now it also contains the state field if received in the challenge, and its value is unchanged from what it received. Additionally, your answer to the challenge is embedded within the password field.

Packet To 127.0.0.1:1812
  ACCESS_REQUEST [2]
    - USER_NAME : boydmr
    - USER_PASSWORD : *******
    - NAS_IP_ADDRESS : localhost/127.0.0.1
    - NAS_PORT : 0
    - STATE : fa66c0a9

Once all such challenges are successfully answered, or after username and password are entered if no challenges are issued, you will see either an access-accept response indicating that authentication was successful like so:

Packet From 127.0.0.1:1812
  ACCESS_ACCEPT [2]

---> SUCCESS! You've Authenticated!

Or you'll see an access-reject like the following:

Packet From 127.0.0.1:1812
  ACCESS_REJECT [2]

---> Sorry. Not Authenticated.

In both cases the Console Client will output a suitable message indicating the success or failure as shown and will exit.

Oh, and one caveat on using this jar. If you want to leverage the radius updates in OpenAM it takes a couple more steps than just dropping this version into your WEB-INF/lib directory, removing the old one, and restarting tomcat. That is almost all that there is to it. But not quite. There are two more steps. If anyone wants to give it a test run leave me a comment and I'll add another post illustrating those steps. It isn't hard. I'm just out of time for today. :-)

Enjoy.

Reading Values from Custom OpenAM Admin Console Pages

Back in December I outlined how to add configuration pages into OpenAM's admin console and promised to show how to read those values out. This post fulfills that promise.

The first thing to be aware of before reading content from OpenAM's configuration is that if you access that configuration before OpenAM's infrastructure has started up completely, OpenAM essentially becomes brain dead and won't respond until the container is restarted. See my previous post on that topic to know how to avoid that problem. With that out of the way lets dive into listeners.

Listening for Changes


Since your admin console page can be accessed at any point in time, values could be changing that impact how your code should function. For example, in that previous post on adding console pages I used Radius configuration as the requirements for my page. One of the properties indicates if the radius server should be enabled or not. If it is not running and we enable it in that page and press the Save button we would expect it to start up immediately after. Listeners are the means of doing that.

A listener need only be added once during the lifetime of Open AM's instance in its container. The radius code is enabled with a ServletContextListener. Its contextInitialized method is called once by a single thread as part of the guarantee of such listeners. So I instantiate a ConfigLoader class that has an instance variable that it uses to hold the ID of the registered listener. If that variable is empty then I am safe to register it. If you are using some other mechanism to start your services up and it is possible to have multiple threads in there at the same time, then you'll have to take extra precautions with your concurrency. Another place where concurrency is important is when receiving events as we'll see below.

To register a listener you must implement the com.sun.identity.sm.ServiceListener class located in the openam-core jar. It has the following methods:

public void schemaChanged(String serviceName, String version);
public void globalConfigChanged(String serviceName, String version, 
         String groupName, String serviceComponent, int type);
public void organizationConfigChanged(String serviceName, 
         String version, String orgName, String groupName, 
         String serviceComponent, int type);

As noted in my previous post on adding pages to the console, the radius configuration only uses global pieces (radius server specific pieces like what port to listen on) with subschema items (defined radius clients). When any of those change the only method that gets called is globalConfigChanged. I have not tested beyond that to know when the other two methods are called.

But remember that the configuration may change at any point in time including immediately after it was just called. For example, suppose that I enabled the radius server and pressed Save and then remembered that I needed to change the port as well. So I chang the port and hit Save again. I know, finicky user syndrome. But that's the way it goes sometimes. What happens if I use the thread that is calling my listener to drive the startup and shutdown of the radius server listener? That could be a problem in this case if the service that is calling me doesn't ensure sequential, non-overlapping calls. One thread could be attempting to open the port while the second is attempting to shut it down and then restart on another port. Better to be certain of what is happening and when.

So in the radius server listener's case it accepts an instance of ArrayBlockingQueue in its constructor. Then, when changes occur, it calls the queue's offer method. What you pass to that call is really irrelevant and can be whatever makes sense for your application. I'm passing a String that I then log in the receiver (not shown). And you can see that I'm handling the case where my offer is rejected due to a full queue:

public void globalConfigChanged(String serviceName, String version, String groupName, String serviceComponent, int type) {
    boolean accepted = configChangedQueue.offer("RADIUS Config Changed. Loading...");

    if (! accepted) {
        cLog.log(Level.INFO, "RADIUS Client handlerConfig changed but change queue is full. Only happens when previous " +
            "change event takes too long to load changes. Existing queued events will force loading of these changes. " +
            "Therefore, dropping event.");
    }
}

What is important is that you can separately have a daemon thread waiting for items to be put into the queue, handling updates in configuration, adjusting the server as needed when an item shows up, and then go back waiting for the next item to appear only after all effects from the current change have settled.

Registering Your Listener


To register your listener you need an instance of com.sun.identity.sm.ServiceConfigManager also found in openam-core. Since ServiceConfigManager is a mouthful I'll refer to it as the  SCM in the rest of this post. In my previous post I noted that the name attribute of the Service element in your service descriptor file would be necessary to access your configured values. Well here is where that comes into play. To instantiate an instance of the SCM we pass that value to its constructor. Along with it we need an admin token and once we have the SCM instance for our service, we then register our listener via the addListener method as shown below.


SSOToken admTk = (SSOToken) AccessController.doPrivileged(
    AdminTokenAction.getInstance());
ServiceConfigManager mgr = new ServiceConfigManager(
    Constants.RADIUS_SERVICE_NAME, admTk);

if (mgr != null) {
    if (listenerId == null) {
        this.listenerId = mgr.addListener(
            new ConfigChangeListener(configChangedQueue));
    }
    ... other code as needed.
}

Loading Your Configuration


Now lets load the information. The SCM is specifically for my config page service since I handed it the name of that configuration. At this point I'll show what I was told to use by a forge rock employee but I can't explain much detail on the API  nor could he. I hope forge rock can make this clear via documentation at some point. And if you know of such documentation please leave a comment with a link to it.

Items beneath the Global element from the service descriptor file in my previous post appear in the Radius Server page such as port and whether it is enabled or not. Items in its SubSchema element are mapped to radius client instances and show in the Radius Server page's table. To get the items in the Radius Server page I do the following. No, I don't use strings directly in code like this but I put them in here for this post so that you can associate the handling here with the corresponding name attribute of the AttributeSchema elements in the service descriptor file. I've only included the handling for two of the server specific properties:

ServiceConfig serviceConf = mgr.getGlobalConfig("default");

if (serviceConf != null) {
    List<ClientConfig> definedClientConfigs = 
        new ArrayList<ClientConfig>();
    boolean isEnabled = false;
    int listenerPort = -1;
    Map<String, Set<String>> map = serviceConf.getAttributes();
    int coreThreads = -1;
    int maxThreads = -1;
    int queueSize = -1;
    int keepaliveSeconds = -1;

    for (Map.Entry<String, Set<String>> ent : map.entrySet()) {
        String key = ent.getKey();
        String value = extractValue(ent.getValue());
        if ("radiusListenerEnabled".equals(key)) {
            isEnabled = "YES".equals(value);
        }
        // don't need to catch NumberFormatException due to 
        // limiting values in the xml file enforced by console
        else if ("radiusServerPort".equals(key)) {
            listenerPort = Integer.parseInt(value);
        }
        ...handle other values/client instances (see below)
    }


The extractValue method handles an annoying issue. The object that we get for each attribute is a Set object even if there is only a single item contained there-in.  Its code looks as follows:


String extractValue(Set<String> wrappingSet) {
    String[] vals = wrappingSet.toArray(Constants.STRING_ARY);
    return vals[0];
}

To get the radius clients I do the following. On the ServiceConfig object for that "default" global configuration I call its getSubConfigNames method. The names here correspond to the name of each radius client and to the names that show in the Radius Server page's table. For each of those I then ask for its corresponding ServiceConfig object and get its defined attribute just like the handling above. Again, I've only showed handling for a couple of items:

    Set<String> names = serviceConf.getSubConfigNames();

    for (String s : names) {
        // object for holding values in memory
        ClientConfig clientConfig = new ClientConfig(); 
        clientConfig.name = s;

        // go get our admin console values
        ServiceConfig clientCfg = serviceConf.getSubConfig(s);
        map = clientCfg.getAttributes();

        // now just like above we pull out the values by field name
        for (Map.Entry<String, Set<String>> ent : map.entrySet()) {
            String key = ent.getKey();

            if ("clientIpAddress".equals(key)) {
                clientConfig.ipaddr = extractValue(ent.getValue());
            }
            ... other fields
            else if ("handlerConfig".equals(key)) {
                clientConfig.handlerConfig =
                    extractProperties(ent.getValue());
            }
        }
        definedClientConfigs.add(clientConfig);
    }

The handlerConfig attribute corresponds to the list box in the radius client configuration page in which we can enter any number of Strings. I expect it to be a name and value pair separated by an equals character. (I hope to replace this control altogether shortly with a drop down selection box to avoid user input errors but haven't had the time yet.) The  extractProperties method splits each of those into the name and value and injects them all into a Properties object.

Properties extractProperties(Set<String> wrappingSet) {
    String[] vals = wrappingSet.toArray(Constants.STRING_ARY);
    Properties cfg = new Properties();

    for(String val:vals) {
        int idx = val.indexOf('=');

        if (idx == -1) {
            cfg.setProperty(val, "");
        }
        else {
            cfg.setProperty(val.substring(0, idx), 
                val.substring(idx + 1));
        }
    }
    return cfg;
}


Conclusions And Plea

At this point I have the Radius Server page values and an array of ServiceConfig objects that are just POJOs to hold each client's value. And with that information the daemon thread then goes and make the corresponding changes to the radius server and goes back to listening.

That concludes what I've been able to figure out on how to load values managed within OpenAM's Admin Console and registering for and receiving notification of their changes. As yet I've been unable to write these values via code. If anyone has documentation on this API please leave a comment linking to those resources. It would be very beneficial to us all. I hope this has been helpful to someone.

Enjoy.


Wednesday, April 8, 2015

Open AM Realms and Authentication Chains

Wow. Two posts in one day. I've had this one ready to publish for a while and wanted to get it out there before I forget everything.

As I refine the RADIUS support being added to Open AM I've run across a few items that I want to preserve so that I don't loose them. I hope they are of benefit to others.

Realms

Open AM provides the concept of Realms. In a newly installed instance of Open AM there is one realm named '/' known as the root realm. Any number of realms can be defined and are conceptually presented in the admin console as being children of the root realm as shown below.


Authentication Chains

In OpenAM Users can only authenticate through another OpenAM construct called an authentication chain. An authentication chain is defined in a realm on the Authentication tab for that realm. A chain has a name and from one to many Authentication Module instances placed within it. These module instances must be created before they can be embedded in any chain. A single module instance can be embedded within multiple chains if desired since these instances are actually instances of configuration. When actually used for authentication, the underlying Pluggable Authentication Module class for the authentication module is instantiated, given a copy of this instance configuration, and is allowed to contribute its authentication behavior to the authentication experience provided to the user by the chain.

Modules in the chain have order meaning users must pass the requirements for an earlier module in the chain before incurring the requirements for modules later in the chain. This order is presented vertically in the admin console as shown below. Authentication by a user proceeds through the modules in the chain from top to bottom.



If any authentication chains exist in the top level realm when a new realm is created, those chains and correspondingly their module configuration instances will be copied into the new realm. If they are deleted from that new realm or any configuration changes are made to the configuration instances in that other realm it has no impact on those in the root realm.

LDAP Constructs

Interestingly, from an ldap perspective, the OpenAM DN at which OpenAM is installed (such as dc=openam,dc=forgerock,dc=org), has an ou=services child node that holds a number of child nodes mostly for the root realm. For example, in OpenAM version 11.0.0 the authentication chains defined for that realm show up in ou=services, ou=iPlanetAMAuthConfiguration, ou=1.0, ou=OrganizationConfig, ou=default, ou=Configurations with nodes therein each representing a given chain by name:


Any new realms show up as child nodes of the base ou=services node as well. And within each of those there is an ou=services node that has a subset of the top level ou=services nodes children copied within. Note the entries for the "again", "another", and "yet-another" in the image below corresponding to the realms listed in the first image above. And if you look in the corresponding path noted above for authentication chains you'll find the chains for that realm. Hence why changing config for a chain in a newly created realm does not affect the configuration that was copied from the root realm.



Programmatically Listing Realms and Their Chains

The set of defined realms that are currently defined in OpenAM can be acquired by the following code:

   SSOToken admTk = (SSOToken) AccessController.doPrivileged(
     AdminTokenAction.getInstance());

   // see what realms we have available to us
   OrganizationConfigManager ocm = null;
   try {
    ocm = new OrganizationConfigManager(admTk, "/");
   } catch (SMSException e) {
    cLog.log(Level.SEVERE, "----- Unable to get realms", e);
   }
   Set<String> realms = null;
   if (ocm != null) {
    realms = CollectionUtils.asOrderedSet("/");
    try {
     realms.addAll(ocm.getSubOrganizationNames("*", true));
     cLog.log(Level.INFO, "---->>> realms: " + realms);
    } catch (SMSException e) {
     cLog.log(Level.SEVERE, "----- Unable to get sub-realms", e);
    }
   }

Once I have the set of realm names I can loop through each and acquire the set of defined chains by name with the following code:

if (realms != null) {
 ConfiguredAuthServices cas = new ConfiguredAuthServices();

 for(String realm : realms) {
  Map parms = new HashMap();
  parms.put(Constants.ORGANIZATION_NAME, realm);
  parms.put(Constants.SSO_TOKEN, admTk);
  Map vals = cas.getChoiceValues(parms);
  cLog.log(Level.INFO, "--->>> '" + realm + "' chains: " + vals);
 }
}

The output of these lines is as shown below with highlighting added for clarity and the timestamp and class name and other cruft trimmed from the front of the lines. I can see the realms as shown in the first image above. I can see the chains of the root realm reflected in some of the other realms. Where they are not found is where I went into that realm and removed that chain. And you can see where I've added new chains to a given realm. Those Empty chains are a concern.

realms: [/, another, again, yet-another, more]
chains for realm /: {smsotp=smsotp, [Empty]=[Empty], ldapService=ldapService, local-radius-1815-server=local-radius-1815-server}
chains for realm another: {smsotp=smsotp, [Empty]=[Empty], ldapService=ldapService, local-radius-1815-server=local-radius-1815-server}
chains for realm again: {smsotp=smsotp, [Empty]=[Empty], againChain2=againChain2, ldapService=ldapService, againChain1=againChain1, againChain3=againChain3, local-radius-1815-server=local-radius-1815-server}
chains for realm yet-another: {smsotp=smsotp, [Empty]=[Empty], ldapService=ldapService, yetAnotherChain=yetAnotherChain, local-radius-1815-server=local-radius-1815-server}
chains for realm more: {smsotp=smsotp, testOne=testOne, [Empty]=[Empty], ldapService=ldapService, local-radius-1815-server=local-radius-1815-server}

At some point I hope to modify the current RADIUS Server config page to provide a dropdown of al l realm/chain combinations from which to select a value rather than require users to enter the realm and chain names as outlined in a previous blog.  These will be very useful notes to have whenever I get time to polish off that RADIUS support.

Enjoy.





Fixing Docker VPN Incompatibilities on OSX

I currently use a Macbook Pro as my dev box. And I have Cisco's AnyConnect for VPN access. From time to time I've noticed that I get into a state where I can't access my docker container running in boot2docker. For example I'll get the following behavior:

$ docker images
FATA[0032] An error occurred trying to connect: Get https://192.168.59.103:2376/v1.17/images/json: dial tcp 192.168.59.103:2376: i/o timeout


When in this state boot2docker works just fine. I can start it, stop it, and check its status just fine:

$ boot2docker status
running

But docker is nowhere to be found including contacting the virtual box host IP address which gives the biggest clue as to what is causing this problem:

$ boot2docker ip
192.168.59.103

$ ping 192.168.59.103
PING 192.168.59.103 (192.168.59.103): 56 data bytes
Request timeout for icmp_seq 0
Request timeout for icmp_seq 1
Request timeout for icmp_seq 2

Turns out that one VPN profile that I use, significantly alters the routing table entries. To see this, I captured my routing table contents after a fresh hard restart of the Mac. Here is the output with things that might be related showing:

$ netstat -rn
Routing tables

Internet:
Destination  Gateway       Flags   Refs    Use     Netif Expire
default      10.109.16.1   UGSc      90      0     en8
default      10.109.128.1  UGScI      2      0     en0
...
127          127.0.0.1     UCS        0      0     lo0
127.0.0.1    127.0.0.1     UH       135   7682     lo0
...

By capturing this output to a text file then starting boot2docker and capturing it again in a separate text file and perform a diff between them the only changes after factoring out changes in Refs, Use, and Expire is this line that has been added to the bottom of the table:

Destination  Gateway       Flags   Refs    Use   Netif Expire
...
192.168.59   link#14       UC         1      0 vboxnet

Depending on if you have run any docker commands after running boot2docker up or boot2docker start you may have a "host route" in there as well looking something like this:

Destination    Gateway         Flags  Refs   Use   Netif Expire
...
192.168.59.103 8:0:27:ac:51:8b UHLWIi    1    40 vboxnet   1197

If you haven't done much with networking issues, like me, pull up the man page for the netstat -r output to grasp what is going on here. It indicates:

The routing table display indicates the available routes and their status.  Each route consists of a destination host or network and a gateway to use in forwarding packets.  The flags field shows a collection of information about the route stored as binary choices.  The individual flags are discussed in more detail in the route(8) and route(4) manual pages.  The mapping between letters and flags is:

1  RTF_PROTO1     Protocol specific routing flag #1
2  RTF_PROTO2     Protocol specific routing flag #2
3  RTF_PROTO3     Protocol specific routing flag #3
B  RTF_BLACKHOLE  Just discard packets (during updates)
b  RTF_BROADCAST  The route represents a broadcast address
C  RTF_CLONING    Generate new routes on use
c  RTF_PRCLONING  Protocol-specified generate new routes on use
D  RTF_DYNAMIC    Created dynamically (by redirect)
G  RTF_GATEWAY    Destination requires forwarding by intermediary
H  RTF_HOST       Host entry (net otherwise)
I  RTF_IFSCOPE    Route is associated with an interface scope
i  RTF_IFREF      Route is holding a reference to the interface
L  RTF_LLINFO     Valid protocol to link address translation
M  RTF_MODIFIED   Modified dynamically (by redirect)
m  RTF_MULTICAST  The route represents a multicast address
R  RTF_REJECT     Host or net unreachable
r  RTF_ROUTER     Host is a default router
S  RTF_STATIC     Manually added
U  RTF_UP         Route usable
W  RTF_WASCLONED  Route was generated as a result of cloning
X  RTF_XRESOLVE   External daemon translates proto to link address
Y  RTF_PROXY      Proxying; cloned routes will not be scoped

     Direct routes are created for each interface attached to the local host; the gateway field for such entries shows the address of the outgoing interface.  The refcnt field gives the current number of active uses of the route.  Connection oriented protocols normally hold on to a single route for the duration of a connection while connectionless protocols obtain a route while sending to the same destination.  The use field provides a count of the number of packets sent using that route.  The interface entry indicates the network interface utilized for the route.  A route which is marked with the RTF_IFSCOPE flag is instantiated for the corresponding interface.  A cloning route which is marked with the RTF_PROXY flag will not generate new routes that are associated with its interface scope.


So the first new line is for a network itself and having the UC flags indicates that the route is useable and will generate (clone) a new route upon being used; namely a host route which is that second line that gets added when first you run a docker command and access the VM. Its flags, UHLWIi, respectively indicate it is useable, is a host route, performs address translation, was generated as a result of cloning, is associated with an interface, and is holding a reference to that interface which is indicated in the Netif column as vboxnet. And docker is happy as can be seen with any of its command such as:

$ docker version
Client version: 1.5.0
Client API version: 1.17
Go version (client): go1.4.1
Git commit (client): a8a31ef
OS/Arch (client): darwin/amd64
Server version: 1.5.0
Server API version: 1.17
Go version (server): go1.4.1
Git commit (server): a8a31ef

Interestingly, if I put the Macbook into sleep and come back at this point, when I look at the route table the host route now has changed. Notably the destination, gateway, and flags. That new flag, b, indicates that it is a broadcast address. Once exercised it reverts back to the same destination, gateway, and flags that we had before.

Destination    Gateway           Flags  Refs  Use    Netif Expire
...
92.168.59      link#14           UC        2    0  vboxnet
192.168.59.255 ff:ff:ff:ff:ff:ff UHLWbI    0   15  vboxnet

So far so good.

VPN to the Rescue (NOT)

Now I start up that VPN profile and try using docker which is no longer happy after quite a TCP timeout delay:

$ docker version
Client version: 1.5.0
Client API version: 1.17
Go version (client): go1.4.1
Git commit (client): a8a31ef
OS/Arch (client): darwin/amd64
FATA[0032] An error occurred trying to connect: Get https://192.168.59.103:2376/v1.17/version: dial tcp 192.168.59.103:2376: i/o timeout

Looking at the routing table, the host route has been removed completely and the 192.168.59 network routing has now been changed to:

Destination    Gateway           Flags  Refs  Use  Netif Expire
...
192.168.59     link#13           UCS       0    0  utun0

That changed entry is the problem. With this entry in the routing table the network to which 192.168.59.X traffic gets routed is the VPN's network adaptor and it knows nothing about any VM running locally.

In this state with VPN running I've been unable to remove that route with any of the following:

sudo route -n delete -net 192.168.59.0/24 -interface utun0
sudo route -n delete -net 192.168.59.0 -interface utun0 

Even though they appear to work, looking at the route table afterward shows the same route still in there. However, once VPN is turned off there is hope.


Fixing Once VPN is Off

Once I shut down my VPN and look at the route table that entry has disappeared and neither boot2docker start nore sudo route -n flush followed by boot2docker start will add it back in although restarting the Mac will fix it. But it can be added back in manually with the following command:

sudo route -n add -net 192.168.59.0/24 -interface vboxnet0

If you are uncertain if your VM is using the vboxnet0 interface you can find out with this command which  is returning vboxnet0 on my machine as shown:

$VBoxManage showvminfo boot2docker-vm --machinereadable | grep hostonlyadapter | cut -d '"' -f 2
vboxnet0

Once re-added, take a look at the table and you'll see:

Destination    Gateway           Flags  Refs  Use    Netif Expire
...
92.168.59      link#14           UCSc      0    0  vboxnet

And docker is happy again. 

If anyone figures out how to fix that link in the route table while VPN is open please leave a post so the rest of us can docker while we work.

Enjoy.