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.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/>
No comments:
Post a Comment