The Hello example was created to illustrate how a typical Jini
technology-based application can be configured and optionally secured
using the configuration and Jini extensible remote invocation (Jini ERI)
facilities that arrived in version 2.0 of the Jini Technology Starter Kit
. The components built from the files making up this example
are a server component that implements a Hello
interface with a sayHello()
method, and a client
component that employs a lookup service to obtain a reference to
the server through which the client invokes the sayHello()
method.
One of the primary purposes of this example is to demonstrate how an application can be written in such a way that it needs to be built only once, but can be run to use various communication protocols and security policies by merely deploying under different configurations without having to change a single line of Java(TM) programming language source code! Thus, although there is only a single example presented in this document, there are a number of scenarios under which this example can be run. The first part of this document provides instructions for getting started quickly, and the second part provides more detailed descriptions of each scenario.
The combinations of ways in which the components of this simple example can be configured are derived from options like the following:
This example also demonstrates the use of the
Configuration
,
Exporter
,
and
ProxyPreparer
interfaces to configure the client, server, lookup service, and
activation system
(Phoenix)
for the desired remote communication and security. Additionally,
references to the source files for the application as well as the
various configuration and policy files that can be used to run the
application in different ways are also presented.
To aid in working with the example, this page is divided into the following sections:
Although running the example under the secure configurations requires a little bit of one-time setup, you should be able to run each of the non-secure configurations "out of the box", with no setup at all. For those who wish to jump right in, the quick start section presents the basic steps necessary to run the example under each of the possible configurations without a lot of additional explanation. That section also contains a convenient table that presents the various scripts one can use to run the example. In that table, the scripts are organized in a fashion that is intended to make it obvious which scripts should be executed for a particular configuration.If, after acquiring some experience with running the example, you would like to experiment with modifying the example's source code, the section on making changes presents a set of steps that can be followed to rebuild the example to include those changes. Should problems be encountered while running the example, a small of set of troubleshooting tips are presented that may be helpful in diagnosing and solving some of the more common problems. Finally, for those who desire a deeper understanding and are looking for a starting point for designing and configuring applications, the contents of the source code, configuration files, and policy files employed in this example can be explored by accessing the references contained in the section that takes a deeper look.
Please note that the keystores, truststore, and associated certificates are provided only for convenience. Using those items in anything but an example environment could pose a security risk because all parties who obtain this example have access to the same information; in particular, the same set of pre-generated private keys and passwords. To address any concerns one might have regarding this situation, the pre-generated keystores, password files, and truststore can be replaced with new versions in which the old private keys and passwords are replaced with different values that you supply, and the associated certificates are replaced with new certificates that are either self-signed or are signed by a valid certificate authority (CA). To generate your own private keys, as well as to create your own self-signed certificates, you can use the keytool provided in the JDK(TM) software.
A Kerberos environment typically includes not only the clients and servers wishing to securely communicate with each other, but also an authentication server referred to as a Key Distribution Center, or KDC. The KDC runs on a KDC host and belongs to a realm. Each client and server - referred to as Kerberos servers for the purposes of this discussion - is associated with a Kerberos principal name as well as a key. Using its principal name, each Keberos server retrieves its associated key from a keytab file and uses that key to request the services of the authentication server running on the KDC host. This example assumes that either a new or pre-existing authentication server, with the appropriate principal names, is operational in the environment in which the example runs.
In addition to an authentication server, the following steps must be taken to set up this example to run under the configurations that use Kerberos:
config
directory, create a
keytab file named krb-servers.keytab
that
contains the keys associated with each principal name
registered in the authentication server for this example.
scripts
directory, edit the file
krb-setenv.sh (or krb-setenv.bat
if on Windows). Set the realm name, the KDC host name, and each
principal name to the appropriate values. (Note that if any
of the environment variables being set in the script have
already been defined in the environment, the current values
of those variables override the values being specified in the
script.)
jsk-policy.jar
is copied into the Java extensions directory; specifically:
jsk-policy.jar
file in the
lib-ext
directory of the Apache River release.
jre/lib/ext
directory inside your
Java 2 SDK (or JRE) distribution.
jsk-policy.jar
file from the Apache River release
to the jre/lib/ext
directory as located in step 2.
After following the steps outlined above, if the virtual machine
for the Java platform associated with the ext
directory
is available in your path, then the features provided by the security
policy provider included in the Apache River release will be available when
running the example.
To start, open five command windows, one for each of the following:
>>
')
in the c. window,
c. >> scripts/jrmp-reggie.shwhereas on Windows, one would type one of the following:
c. >> scripts\jrmp-reggie c. >> scripts\jrmp-reggie.batUpon opening the five command windows described above, in each window, change the directory to the hello directory. That is,
a. >> cd <riverDir
>/examples/hello b. >> cd <riverDir
>/examples/hello c. >> cd <riverDir
>/examples/hello d. >> cd <riverDir
>/examples/hello e. >> cd <riverDir
>/examples/hello
lib
directory and the Apache River release lib-dl
directory. The
class server
provided in the Apache River release is used for this purpose. Note that once
started, there is no need to shut down the class server (cntrl-c) until
it is no longer needed. This is because that single class server can be
shared by each configuration. To start the class server, type the
following in the a. command window:
a. >> scripts/httpd.sh
For each primary characteristic, there are secondary characteristics associated with a particular configuration that further distinguishes one configuration from the others. As with the primary characteristic, the secondary characteristics are generally related to behavior demonstrated by the example's server. For example, is the server activatable or non-activatable? Are remote calls confirmed or not?
The quick start instructions in the table below are organized and presented based on these primary and secondary characteristics. Each row represents one of the configurations under which the example may be run. The column labelled Execution presents the scripts that must be executed to run the example under that particular configuration. The column labelled Upon Completion presents what must be done to "clean up" and prepare for executing another configuration. The indicated letters refer to the window in which the associated script or command should be executed.
Note that with respect to the username that must be provided in the Kerberos configurations, as well as the password that is required by both the Kerberos and the SSL configurations, the values shown in the table do not necessarily represent the actual values that must be input by the user. The password that the table shows for the SSL configurations is the same as the password used when generating the private keystore supplied with this example for the client component. Thus, as long as the client's keystore is not replaced with a new keystore with a different password, the password shown below can be used as shown.
With respect to the Kerberos configurations, the actual values to input for the username and password, are the Kerberos principal name and corresponding password that are registered with the particular Kerberos authentication server (KDC) that will be used when running the example; and which are associated with the client component through the krb-setenv.sh script (or krb-setenv.bat command file).
ID | Distinguishing Characteristic(s) | Execution | Upon Completion |
---|---|---|---|
A-1 | JRMP server | c. >> scripts/jrmp-reggie.sh d. >> scripts/jrmp-server.sh e. >> scripts/client.sh
|
d. >> cntrl-c c. >> cntrl-c |
B-2 | Jini ERI server | c. >> scripts/jrmp-reggie.sh d. >> scripts/jeri-server.sh e. >> scripts/client.sh
|
d. >> cntrl-c c. >> cntrl-c |
B-3 | Jini ERI server client displays call-confirming dialog box server displays call-confirming dialog box |
c. >> scripts/jrmp-reggie.sh d. >> scripts/confirming-jeri-server.sh e. >> scripts/client.sh click "OK" in dialog displayed by client click "OK" in dialog displayed by server |
d. >> cntrl-c c. >> cntrl-c |
B-4 | Activatable Jini ERI server JRMP Phoenix |
b. >> scripts/jrmp-phoenix.sh c. >> scripts/jrmp-reggie.sh d. >> scripts/activatable-jeri-server.sh e. >> scripts/client.sh
|
c. >> cntrl-c b. >> cntrl-c -- UNIX/Linux/OS X -- b. >> rm -rf lib/phoenix-log -- windows -- b. >> rmdir /S /Q lib\phoenix-log |
B-5 | Activatable Jini ERI server Jini ERI Phoenix |
b. >> scripts/jeri-phoenix.sh c. >> scripts/jrmp-reggie.sh d. >> scripts/activatable-jeri-server.sh e. >> scripts/client.sh
|
c. >> cntrl-c b. >> cntrl-c -- UNIX/Linux/OS X -- b. >> rm -rf lib/phoenix-log -- windows -- b. >> rmdir /S /Q lib\phoenix-log |
C-6 | SSL client displays login dialog box |
c. >> scripts/ssl-reggie.sh d. >> scripts/ssl-server.sh e. >> scripts/ssl-client.sh Keystore password: clientpw |
d. >> cntrl-c c. >> cntrl-c |
C-7 | SSL client displays login dialog box client displays call-confirming dialog box server displays call-confirming dialog box |
c. >> scripts/ssl-reggie.sh d. >> scripts/confirming-ssl-server.sh e. >> scripts/ssl-client.sh Keystore password: clientpw click "OK" in dialog displayed by client click "OK" in dialog displayed by server |
d. >> cntrl-c c. >> cntrl-c |
C-8 | SSL Activatable SSL server SSL Phoenix client displays login dialog box |
b. >> scripts/ssl-phoenix.sh c. >> scripts/ssl-reggie.sh d. >> scripts/activatable-ssl-server.sh e. >> scripts/ssl-client.sh Keystore password: clientpw |
c. >> cntrl-c b. >> cntrl-c -- UNIX/Linux/OS X -- b. >> rm -rf lib/phoenix-log -- windows -- b. >> rmdir /S /Q lib\phoenix-log |
D-9 | Kerberos client displays login dialog box |
c. >> scripts/krb-reggie.sh d. >> scripts/krb-server.sh e. >> scripts/krb-client.sh Kerberos username[<default>]: client Kerberos password for client: clientpw |
d. >> cntrl-c c. >> cntrl-c |
D-10 | Kerberos client displays login dialog box client displays call-confirming dialog box server displays call-confirming dialog box |
c. >> scripts/krb-reggie.sh d. >> scripts/confirming-krb-server.sh e. >> scripts/krb-client.sh Kerberos username[<default>]: client Kerberos password for client: clientpw click "OK" in dialog displayed by client click "OK" in dialog displayed by server |
d. >> cntrl-c c. >> cntrl-c |
D-11 | Kerberos Activatable Kerberos server Kerberos Phoenix client displays login dialog box |
b. >> scripts/krb-phoenix.sh c. >> scripts/krb-reggie.sh d. >> scripts/activatable-krb-server.sh e. >> scripts/krb-client.sh Kerberos username[<default>]: client Kerberos password for client: clientpw |
c. >> cntrl-c b. >> cntrl-c -- UNIX/Linux/OS X -- b. >> rm -rf lib/phoenix-log -- windows -- b. >> rmdir /S /Q lib\phoenix-log |
build.xml
-
build script for recompiling the source and regenerating the example
JAR files using Apache Ant
server.mf
- Manifest for server.jar,
the non-activatable server
server-act.mf
- Manifest for
server-act.jar, the activatable server
client.mf
- Manifest for client.jar
build.xml
, the version of the
Java operating environment used must be at least 1.4 and the
version of Ant used must be at least 1.6.2.
Note that all operations shown below are performed from the
hello
directory of the example, and assume a UNIX,
Linux, or OS X operating system. If the operating system is
Windows, then replace $ANT_HOME
with %ANT_HOME%
,
and replace all instances of the UNIX file separtor '/' with the
Windows file separator '\'.
>> $ANT_HOME/bin/ant compile
>> $ANT_HOME/bin/ant client.jar >> $ANT_HOME/bin/ant server.jar >> $ANT_HOME/bin/ant server-act.jar >> $ANT_HOME/bin/ant server-dl.jar >> $ANT_HOME/bin/ant mdprefld.jarThe following command will generate a selected subset of the JAR files:
>> $ANT_HOME/bin/ant client.jar server.jar mdprefld.jarFinally, the following command will generate all of the JAR files of the example in a single command:
>> $ANT_HOME/bin/ant jars
>> $ANT_HOME/bin/ant doc
>> $ANT_HOME/bin/ant trust
>> $ANT_HOME/bin/ant clean >> $ANT_HOME/bin/ant
java.lang.ClassNotFoundException: com.sun.jini.reggie.RegistrarProxy (could not obtain preferred value)
java.lang.SecurityException: Dynamic permission grants are not supported .... Caused by: java.lang.UnsupportedOperationException: grants not supportedThe cause of the above problem is typically an incorrectly installed (or not installed at all)
jsk-policy.jar
file. Another
(only slightly) less common cause is a 'typo' in the system property
java.security.properties
referenced on the command line.
For example,
-Djava.security.properties=config/dynamic-policy.security-propertiesIf the file referenced in the property above does not exist or is unreachable, or if there is a typo in either the property name or the file name, an exception trace like that above will occur.
INFO: exception while preparing lookup service proxy java.rmi.ConnectIOException: I/O exception connecting to BasicObjectEndpoint[...]; nested exception is: net.jini.io.UnsupportedConstraintException: sun.security.validator.ValidatorException: No trusted certificate foundThis problem occurs when running the SSL configurations and is typically encountered when one or more public key certificates cannot be found. A common cause of this problem is an incorrect
javax.net.ssl.trustStore
system property. For example,
-Djavax.net.ssl.trustStore=prebuiltkeys/truststoreAlthough a missing or inaccessible file can cause this problem, one of the most common mistakes made regarding this property is to not capitalize the second '
S
' in the
'trustStore
' component of
javax.net.ssl.trustStore
.
java.net.MalformedURLException: unknown protocol: httpmdOn the other hand, if the lookup service is run under one of the basic configurations, but the client or server is run under a secure configuration, the output will probably contain information like the following:
INFO: exception while preparing lookup service proxy
java.lang.SecurityException: object is not trusted: com.sun.jini.reggie.RegistrarProxy[...]
at net.jini.security.Security.verifyObjectTrust(Security.java:<lineNo
>)
Pre-authentication information was invalid (24) - Preauthentication failed.Often this is an indication of skew between the clock on the KDC host (running the authentication server) and the clock on the host running the Kerberos server wishing to authenticate. Those clocks should be set to the same time zones and synchronized to be within 5 minutes of each other.
Recall that a common way to view a service is as a provider of some sort of resource. A client of a service can then be viewed as a user (or consumer) of the resource provided by the service. For example, consider the Jini technology lookup service itself. The lookup service provides multiple resources that can be useful to both clients and other services. One resource provided by the lookup service is "residency" in the lookup service. Through the residency a lookup service provides to another service, that other service advertises the availability of its own resource(s). Another resource provided by the lookup service is a "query resource". Clients can query the lookup service for, and obtain references to, other services having resources the client wishes to use.
For each and every configuration in this example, the service
component of the example discovers the lookup service and then
requests and is granted residency in the lookup service.
The client also discovers the lookup service, but rather than
requesting residency, the client queries the lookup service
for a service of type Hello
, and the lookup service
provides the client with a reference (a proxy) to that
service. Once the client has obtained a proxy to the service
of interest, through that proxy, the client uses the
service to display a greeting, which is the resource the service
provides. To see the implementation details, examine the contents
of the following source files. Note that it is the proxy object
that implements the Hello
interface, which is the
service type the client requests from the lookup service.
Hello.java
- the interface which represents the service typeServer.java
- the server component that exports the remote objectProxy.java
- the proxy object obtained by the client from the lookup serviceClient.java
- the client component
The point of this example is to show how the source can be
written and built only once, yet executed using different
(possibly mixed) remote communication mechanisms with different
controls and constraints placed on how that communication occurs;
by merely running under different configurations that are
specified at runtime. No matter what configuration is used to
run the example, the client's goal is always the same: to use
a lookup service to find a Hello
service, and
then to use the Hello
service to display a greeting.
As will be further explained in the sub-sections below, the
differences in the configurations lie in how the remote calls
between the components are implemented and controlled.
Tip: with respect to the emphasis on remote communication between the components of the system, if the example is being run on a single machine, it might help to view the separate windows as separate machines on which each component runs and communicates with the others.
Basic configuration files
Recall that remote objects must be exported. The process of exporting a remote object results in the creation of a proxy object, which is passed to other components so that those other components may communicate with (make remote calls on) the component that was exported. When examining the configuration files above, notice that the configuration files for the lookup service (Reggie
)
and the server each specifies an
exporter.
From this, one can conclude that the lookup service as well as the
server each export a remote object. Note that although the client's
configuration file doesn't specify an exporter, it happens that a remote
object actually is exported by the client component. Upon examining
the client's configuration file, one can see that the client specifies
a
ServiceDiscoveryManager
(SDM).
The SDM utility exports a remote listener object, using a default
exporter if no exporter is specified in the configuration. Of course,
the client configuration file above could have explicitly specified an
exporter in a way similar to what is done in the client's secure
configurations, which can be seen by examining the files in the next
section.
Through the configured exporter, one can specify the protocol to use for remote communication; in this case, JRMP or Jini ERI. Thus, when the remote object is exported using the exporter specified in the configuration, a proxy is created through which the other components in the system can communicate with the remote object using the protocol that is specified with that exporter. For example, to configure a basic exporter that will create a proxy that communicates back to the remote object using JRMP, the configuration would contain an entry like the following:
exporter = new JrmpExporter();Compare this with an exporter that specifies its remote communication to occur over Jini ERI,
private static endPoint = TcpServerEndpoint.getInstance(0); private static ilFactory = new BasicILFactory(); exporter = new BasicJeriExporter(endPoint, ilFactory);Note that the values of the private entries in the configuration snippet above will be automatically substituted for the parameters of the exporter specification when the exporter is instantiated. Using substitution in this way is not required; that is, the private values could have been entered directly in the exporter specification. Private entries are used here and in the other snippets presented in this document to aid in readability.
From the snippets above, it should be clear that one specifies the
use of JRMP or Jini ERI by configuring either a "JRMP exporter" or a
"Jini ERI exporter" respectively. As will be further demonstrated in the
Secure configuration files section
below, when using a
BasicJeriExporter
,
one has additional flexibility with respect to the transport over
which communication will occur. Through the endPoint
parameter of BasicJeriExporter
, one can specify non-secure
transports such as TCP (as was done above) and HTTP, or transports
that support security, such as SSL, HTTPS, and Kerberos.
config/dynamic-policy.security-properties
.
With respect to the example's server component, each of that
component's secure configurations employs a specially defined
preferred class provider
,
which uses
HTTPMD URLs
to ensure integrity of data and downloaded
code. Additionally, each secure configuration of the server component
specifies a ServerPermission
to control access to the resource(s) the server provides.
prebuiltkeys
directory
that have names of the form *.password
. Notice that
the prebuiltkeys
directory contains no password files
associated with the client component of the example. When running
the example, you should observe that only the client component
prompts for a password; thus, no password file is required for the
client in the prebuiltkeys
directory. This is because,
unlike the client, the lookup service, the server, and Phoenix are
each configured to automatically retrieve their respective passwords
from a file, rather than from an interactive dialog box in which
a human user supplies the principal name and appropriate password.
Whether running an SSL configuration or a Kerberos configuration, the authentication mechanism used is the Java Authentication and Authorization Service (JAAS). To use JAAS, a component "logs in"; a process that requires configuration. To understand how each component of the example is configured for JAAS login, compare the contents of the following files:
JAAS login configuration files for SSL configurations
JAAS login configuration files for Kerberos configurations If you compare these files, you will see some parallels between the SSL login configuration files and the Kerberos login configuration files. For example, there is an indication of the principal name for the corresponding component (keyStoreAlias
for SSL, principal
for Kerberos), where to find the
credentials that will be used for authentication (keyStoreURL
and keyTab
), and for SSL, the location from which to
retrieve the associated password (keyStorePasswordURL
).
If you examine the client's SSL login configuration file, you will
see that there is no keyStorePasswordURL
, and its
Kerberos login configuration file sets doNotPrompt
to
false
(by default); both of which will cause the client's
login module
to prompt for a password.
SSL configuration files
Kerberos configuration files While examining the above configuration files, one should notice the relationship between the export process, the proxies that result from exporting, and the constraint mechanism in conjunction with the standard security policy mechanism. As with the basic configurations, each component in the example exports a remote object; thus, each secure configuration specifies an exporter. Furthermore, the exporter that is configured is the mechanism through which one can specify the security requirements that will be enforced from the point of view of the exported remote object.Because components that receive a proxy produced by an exporter will use the proxy to communicate with the exported remote object, those components will want to specify the security requirements that will be enforced from the point of view of the component that receives and uses the proxy. Additionally, because acquisition and reconstruction of the proxy, as well as the invocation of any remote methods on the proxy, typically involve not only data transmitted in-band as part of the remote call, but also code downloaded out-of-band, enforcing object integrity (both data and code) will become a priority for many components that interact with the proxies they receive.
Note that whether viewing constraints from the point of view of an
exported remote object, or from the point of view of a component
that receives and uses the proxy associated with a remote object,
when a set of constraints specify integrity
(Integrity.YES
)
on the remote calls between a proxy and its remote object,
the integrity of both the data and any downloaded code must be
verified. That is, during the execution of a remote call in which
serialized objects are transmitted and reconstructed, it must be
verified that neither the serialized data that is tranmitted in-band,
nor the code defining each object's behavior that is downloaded
out-of-band (if any), has not been compromised.
Whereas security requirements are specified through an exporter for a remote object's communication, the security requirements for communication through a proxy are specified through what is referred to as a proxy preparer. Thus, while examining the contents of the secure configuration files above, you will notice not only exporters being specified, but also proxy preparers. For a particular configuration file, the exporters are specified for the remote objects that are exported by the specific component associated with that configuration; whereas the proxy preparers are specified for the proxies that that component receives from the other components in the system.
Upon examining the exporters specified in the secure configurations, you will see that in addition to specifying the transport to use, through the invocation layer, constraints can be set and aspects of access control can be specified. Compare this to the security-related items that are specified for the proxy preparers. When configuring a proxy preparer, the following is specified:
Exporters and security
To make this discussion regarding exporters and proxy preparers more concrete, first consider the following snippet showing the configuration of an exporter one might wish to use to export the remote object defined by the example's server. This exporter is configured for SSL. Note that, as before, private entries are used for readability.
private static endPoint = SslServerEndpoint.getInstance(0); private static integrityOnly = new InvocationConstraints(Integrity.YES, null); private static constraints = new BasicMethodConstraints(integrityOnly); private static ilFactory = new ProxyTrustILFactory(constraints, ServerPermission.class); exporter = new BasicJeriExporter(endPoint, ilFactory);This exporter is similar to the Jini ERI exporter presented in the basic configurations section. Note the differences though. This exporter specifies the use of secure sockets (SSL) for communication and, through the invocation layer factory, specifies that when a component attempts to invoke a remote method on the exported object, integrity must be enforced, and the component must have permission - specifically, a
ServerPermission
-
to access the particular method on which the invocation is
being attempted.
There are a couple of additional things to note about the
configuration of the exporter above. First, with respect to
the exporter's constraints, the only constraint that is
specified is integrity. This is not an uncommon security model
for remote server objects. Rather than using additional
constraints to enforce access control, the strategy demonstrated
here is to exploit the standard Java security policy mechanism
to control access to the exported object's remote methods using
a ServerPermission
.
The second thing to note is that to change the above SSL exporter
to a Kerberos exporter, one merely has to change the endpoint
to a Kerberos endpoint. That is,
private static endPoint = KerberosServerEndpoint.getInstance(0);
Proxy preparers
Next, consider a snippet showing the configuration of the proxy preparer one might wish to apply to the proxy the client receives from the server (by way of the lookup service):
private static verifyTrust = true; private static requirements = new InvocationConstraint[] { Integrity.YES, ClientAuthentication.YES, ServerAuthentication.YES, new ServerMinPrincipal(serverUser) }; private static preferences = null; private static invocationConstraints = new InvocationConstraints(requirements,preferences); private static methodConstraints = new BasicMethodConstraints(invocationConstraints); private static dynamicPermissions = new Permission[] { new AuthenticationPermission(clientUser,serverUser,"connect") }; preparer = new BasicProxyPreparer(verifyTrust, methodConstraints, dynamicPermissions);Upon examining the snippet above, one can see that before the proxy of interest can be used, it must be verified that the proxy can be trusted. That is, the first step in proxy preparation is trust verification (if requested). Once it is known that the proxy can be trusted, constraints can be attached to the proxy. This example presents four constraints that represent a common model for proxy preparation:
Integrity.YES
ClientAuthentication.YES
ServerAuthentication.YES
serverUser
-
ServerMinPrincipal(serverUser)
serverUser
, when the proxy is used to
communicate back to that server, the proxy is granted
permission to connect
to the server as the principal
clientUser
. Think of the proxy as foreign code
executing in the client's environment; code produced by an
entity - the server - that authenticates as the server's principal.
The value of the last argument of the proxy preparer configuration
shown above specifies that the proxy is allowed to communicate back
to that server under the client's principal; as if it were the
client itself. The proxy is allowed to do this because the first
step in the proxy preparation process established that the client
trusts that proxy to act on the client's behalf.
In addition to exporters and proxy preparers, there are a number of other items specified in the configuration files, along with some in-line explanatory documentation. You should examine and compare the contents of all the configuration files.
To see the details of how the custom invocation layer factory, the custom dispatcher, and the custom invocation handler are implemented, examine the contents of the following source files.
ConfirmingILFactory.java
- custom IL factory with dispatcherConfirmingInvocationHandler.java
- custom invocation handlerconfig/confirming-jeri-server.config
config/confirming-ssl-server.config
config/confirming-krb-server.config
To understand the explanations below, it might be instructive
to review some terminology. As described previously, when a
remote object is exported, a proxy object is produced. Through
a reference to that proxy object, other components of an
application can communicate with the exported remote object (the
server). The proxy object is often referred to as the front end
of the server, and the various aspects of its communication
with the remote object are referred to as the client side
communication. As one might expect, when the proxy is referred
to as the front end of the server, the associated remote object
with which that proxy communicates is referred to as the
back end of the server, and its communication with the
proxy is referred to as the server side communication.
Additionally, the term client is generally used to
refer to the component holding the reference to the proxy,
but can also refer to the proxy object itself (depending on
context). Similarly, the term server generally refers
to the remote object that was exported, or the component that
performed the export and holds the remote object. Given this
terminology, it should come as no surprise then that this
example provides a class named
Client
and a class
named Server
.
With the above terms in mind, one way to view the activation system is as a "container" that can be used to execute server back ends in shared or separate virtual machines embodied as activation groups. That is, the activation system (Phoenix) is started, and then the server one wishes to execute is registered with the activation system, which ultimately executes the server in a VM that the activation system spawns and maintains. As one might expect, for the example being described in this document, the only component with a configuration that supports this model is the server component. To support the server component's interaction with activation, the following class is provided:
ActivatableServer
extends Server
, providing
additional functionality for registering the server with the
activation system. Thus, ActivatableServer
acts as
both the server component of this example and the mechanism used
to register the server with the activation system. Note that this
strategy of combining server functionality with the activation
registration mechanism is just one possible strategy for
implementing the activatable form of the server component.
Another common strategy is to provide a separate registration
mechanism (for example, the class
SharedActivatableServiceDescriptor
from the Apache River release's
service starter framework).
As with the confirming configurations, the activatable configurations
associated with ActivatableServer
span both the
basic and secure configurations described above. For the details,
see the configurations with IDs B-4, B-5, C-8, and D-11 from the
quick start table, and examine the
contents of the following configuration files:
Configuration files for ActivatableServer
config/start-activatable-jeri-server.config
config/start-activatable-ssl-server.config
config/start-activatable-krb-server.config
config/activatable-jeri-server.config
config/activatable-ssl-server.config
config/activatable-krb-server.config
ActivatableServer
is to interact with Phoenix to
register the server (in this case, to register itself) for activation,
not how either Phoenix or the server are themselves
configured. For example, if you examine
config/start-activatable-jeri-server.config
,
you will see that it is in this file that one specifies the
actual configuration to use for the server that is to be registered
and ultimately activated (look for the entry that references
config/activatable-jeri-server.config
).
When the server is activated, the server configures itself from the
entries it retrieves from that second configuration. Note that it
is also in that start configuration file that options and system
properties can be specified for the VM in which the server runs.
To specifically configure Phoenix, for each configuration under which Phoenix may be run in this example (JRMP, Jini ERI, SSL, Kerberos), there are Phoenix-specific configuration file pairs: one for configuring the activation group created by Phoenix, and one that specifies the actual configuration of Phoenix itself. That is,
Configuration files for activation group, created by Phoenix, in which server runs
config/jrmp-phoenix-group.config
config/jeri-phoenix-group.config
config/ssl-phoenix-group.config
config/krb-phoenix-group.config
config/jrmp-phoenix.config
config/jeri-phoenix.config
config/ssl-phoenix.config
config/krb-phoenix.config
Reggie
).
To start Reggie under any of the configurations of this example,
a container-type application, referred to as the
service starter framework,
is used. As when Phoenix, through the
ActivatableServer
class, is used to start the activatable form of the server
component of this example, when the service starter framework is
used to start a server such as Reggie, each configuration is
represented by a pair of configuration files. Specifically,
Configuration files for service starter framework used to start Reggie
Configuration files for Reggie itself Finally, note that because the activatable form of the server component actually runs in a VM (activation group) spawned by Phoenix, the mechanism for stopping the activatable server is to actually stop Phoenix. This is why the instructions in the quick start table indicate that a cntrl-c (kill signal) should be executed in the command window in which Phoenix was started, rather than the server's command window. Additionally, also note that to support the management of the environment in which the activatable server runs, Phoenix persists certain state. Thus, for convenience, and to avoid unnecessary attempts at restarting any old, unwanted configurations of the server component, the instructions in the quick start table also recommend removing the current persisted state (log files and directory) whenever Phoenix is shut down.ProtectionDomain
class, is discussed. Through the
security policy,
a protection domain is mapped to a desired set of
permissions, and based on the particular security policy in place, the
access control mechanism can be used to allow or deny
access to various resources. To understand security policy and its relation to the protection
domain of currently executing code, one can view it as a mechanism in which a
set of permissions are granted to:
URL
- but also by any
possible signers.
Through the security policy, permissions may be granted to
any combination of the items in the list above. That is,
whereas permissions may certainly be granted to a set of
classes signed by some set of signers, obtained from a
particular URL
and loaded by a given class loader,
and running as a particular set of principals, permisssions
may also be granted to code simply executing on behalf of
a set of principals, with no regard to class loader or code
source; or to code based on only the location from which the
code was obtained, etc.
grant codeBase "file:lib/phoenix.jar" principal "phoenix" { permission java.lang.RuntimePermission "accessDeclaredMembers"; permission java.lang.RuntimePermission "shutdownHooks"; }; grant principal "phoenix" { permission com.sun.jini.phoenix.SystemPermission "com.sun.jini.phoenix.activeGroup"; permission com.sun.jini.phoenix.SystemPermission "java.rmi.activation.ActivationSystem.activeGroup"; permission com.sun.jini.phoenix.SystemPermission "java.rmi.activation.ActivationSystem.shutdown"; }; grant codebase "file:lib/phoenix.jar" { permission java.lang.RuntimePermission "getClassLoader"; permission java.lang.RuntimePermission "getProtectionDomain"; }; grant { permission java.util.PropertyPermission "customProp1", "read"; permission java.util.PropertyPermission "customProp2", "read"; }; grant codebase "file:lib/jsk-platform.jar" { permission java.security.AllPermission; }; grant { permission java.security.AllPermission; };Because security policy file format does not allow one to specifically indicate a class loader to which to grant permissions, the above grant entries present examples of permissions being granted to various combinations of principal(s) and code source. For example, the first entry grants the indicated permissions to only the code from the local JAR file indicated in the
file:
URL, with the additional restriction that the
code from that source must be executing as the indicated principal.
Compare this with the second entry. That entry grants the indicated
permissions to code from any source, so long as that code is
running as the given principal. Alternatively, in the third entry,
the indicated permissions are granted to code running as
any principal, but only so long as the code was obtained from
the indicated code source location.
Moving down the list, the fourth entry grants the indicated permissions
(but only those specific permissions) to code from any source,
running as any principal, and the entry after that, grants
all permissions
(java.security.AllPermission
)
to specific code with no restriction on principal; that is, code from
the specific source indicated, executing as any principal. Finally,
the last entry grants all permissions to all code, no
matter what principal(s) that code may, or may not, be running as.
lib/phoenix.jar
, and who has logged in (or
authenticated) as the principal phoenix
. Similarly, when
discussing the second grant entry, one might talk about granting
the indicated permissions to any parties who have simply authenticated
as the principal phoenix
.
Principle 1 - never grant "all permissions" to all parties
To understand the importance of the first principle, consider the situation where that principle is violated. When an entity grants "all permissions" to all code, the code to which "all permissions" is granted will include any code that is downloaded from remote sources; that is, foreign code, which is not necessarily trusted. This can pose a significant security risk because access to all of the granting entity's resources is being provided to parties that may be both unknown and untrusted. Thus, to prevent exposure to the risks presented when "all permissions" is granted to all code, each of the policy files provided with this example satisfies the first principle.
Principle 2 - only grant "all permissions" to local code
The second principle is designed to provide some flexibility to the policy, without sacrificing security. What is meant by the term local code, as it is used in that principle, is code that has been obtained from a trusted source and that has been installed locally on your system. For example, consider the JDK (or JRE) code you are running. Presumably, you obtained this code from a vendor that you trust will not supply a JDK or JRE that does "bad things". Even if the version of Java you are using was not installed by you, it was probably pre-installed by the (trusted) vendor who supplied your system. Similarly, the Apache River release itself was obtained from a trusted source (the Jini team), and was installed by someone you must certainly trust - you! Code that is installed and runs locally (including your own application code) can be safely granted "all permissions" because you know its source, and you trust that it will not behave badly.
Another way of viewing local code is as "code that is not downloaded
during the execution of the application". To understand this, consider
the policy file for both the
basic configuration and the
SSL configuration of Reggie
from the lists below. In both files,
you will see that "all permissions" is granted to the JAR files from only
the lib
directory
of the Apache River release, with no entries granting "all permissions" to
http: URL
s referencing any of the JAR files from the
lib-dl
directory.
Recall that files such as jsk-platform.jar
,
reggie.jar
, etc. are contained in the Apache River release's
lib
directory whereas only downloadable JAR
files (such as jsk-dl.jar
, reggie-dl.jar
, etc.)
are contained in the lib-dl
directory. The pattern
promoted by the Apache River release is that the JAR files from the
lib
directory are expected to be locally installed,
not downloaded; whereas the JAR files contained in the lib-dl
directory are expected to be downloaded. Thus, another way of stating
the second principle is, "never grant all permissions to downloaded code".
Principle 3 - specific permissions may be granted to all parties (non-secure configurations)
With respect to an application of the third principle, one might wonder why configurations that are labelled as "non-secure" are concerned with fine grained security such as that expressed in the principle; or any security at all for that matter. It should be clear from the first two principles that the term "non-secure", as it is applied to the configurations described in this document, does not equate with "no security at all". Rather, that term is meant to imply that the mechanisms of the Jini technology security model, mechanisms that provide items such as authentication, authorization, confidentiality, integrity, and trust verification, are not employed when remote calls are made and code is downloaded. But this does not mean that access to all other aspects of an entity's resources should be granted to other parties. The position taken by this example, as expressed in the third principle, is that it still makes good sense to use the mechanisms of the standard Java security model to allow open access to only those resources that are necessary and can be considered low risk. In this way, functionality can be provided while minimizing one's vulnerability to attacks from third parties.
A concrete example of the third principle above can be seen in the
policy file for the basic
configuration of Phoenix. In that file you will notice an
entry that grants various instances of
ExecOptionPermission
to all parties. To understand the need for that permission, recall
that Phoenix spawns a VM, referred to as an
activation group,
in which the server to be activated will be executed. When starting
the activation group, a number of options and properties are usually
set. To see the properties and options that are set for the activation
group under the basic configuration, examine the contents of the
configuration file
for the class that is used to start the activatable form of the
server under Jini ERI. To see how those options and properties
are actually set, take a look at the code in
ActivatableServer.java
.
In order to register the server with Phoenix for activation, a
proxy to the activation system is obtained and, through the remote
registerGroup
method on that proxy, an
activation group descriptor,
containing the necessary options and properties, is registered with
Phoenix. For simplicity (to avoid the complications that can
arise when separating the server's classpath and codebase from that
of the activation group), the classpath and codebase properties that
the ActivatableServer
sets for the activation group are
also the classpath used to execute the server, and the codebase used to
annotate the server's classes. Thus, after identifying the options and
properties specified in the
configuration file,
and then examining the contents of Phoenix's policy file
as well as the source from ActivatableServer.java
,
the role of ExecOptionPermission
should be apparent. Using
ExecOptionPermission
, Phoenix expresses the access control
policy for what activation group descriptors can be registered. That is,
through ExecOptionPermission
, Phoenix controls which options
and properties can be set.
In the case of the non-secure configurations, the reason that Phoenix
grants the various instances of ExecOptionPermission
to all parties rather than specific parties is because the
registration is performed through a remote call made by an entity
with no client subject; that is, the entity does not authenticate
as any principal(s) to Phoenix. Because there are no principals to
which Phoenix can grant the required permissions, and because
the source of the code making the call is not in Phoenix's access
control context, the necessary permissions must be granted to the
empty protection domain (any principal, any code source); otherwise
Phoenix's access control policy will reject the call. Compare this
to the policy files provided with the secure configurations of
Phoenix. When interacting with Phoenix under the secure configurations,
the activatable form of the server authenticates itself to Phoenix
as the principal server
, and in Phoenix's associated
policy files, the necessary instances of ExecOptionPermission
are granted to any code source that authenticates as that specific
principal. As described below, this is a result of the fourth principle.
Before discussing the fourth principle, another question that might
have occurred while examining Phoenix's non-secure configurations
in light of the third principle is why, in the file
config/jeri-phoenix-group.config
,
is the Phoenix activation group configured to export
its ActivationInstantiator
with an
AccessILFactory
rather than a
BasicILFactory
?
As explained in the
Phoenix documentation,
by default, the activation group exports itself as a JRMP unicast
remote object, which is a limitation of the existing activation
system design. If the configuration for the activation group
specifies an exporter for the ActivationInstantiator
through the configuration entry named instantiatorExporter
,
the activation group is unexported from the JRMP runtime and then
re-exported using the exporter specified in the configuration. So
this explains why an exporter that employs Jini ERI is specified
for that configuration. To understand why that exporter is
configured to use an AccessILFactory
rather than a
BasicILFactory
, recall that BasicILFactory
returns an invocation dispatcher that accepts calls from any host
(local or remote), whereas AccessILFactory
returns an
invocation dispatcher that only accepts calls from the local host.
Thus, by configuring the exporter for the ActivationInstantiator
to employ an AccessILFactory
, additional control can
be enforced on access to the activation group.
Principle 4 - server-side access control is provided through security policy (secure configurations)
The fourth principle of the policy employed in this example addresses the issue of restricting access to remote calls; that is, granting or denying permission to invoke a remote method on an object. Recall that the proxy through which a remote call is made is created when a remote object is exported, and is obtained by the caller through some means (for example, through a lookup service). As noted previously, such a call is initiated on the client side of the remote call, and is ultimately executed on the server side, in the remote object that was exported. It is the policy file of the remote object on the server side of the call that is addressed by this last principle. Rather than configuring the object's exporter with constraints to enforce access control, the remote object uses policy and access permissions to restrict access to each of its remote methods to only those parties who are trusted to make use of that method.
To see a concrete example of the fourth principle, examine the contents of any of the policy files for the secure configurations from the lists below. In particular, take a look at the following snippets from the policy files for the SSL configuration of the client component, the server component, and the lookup service:
Client policy
grant principal "reggie" { permission net.jini.security.AccessPermission "notify"; permission net.jini.security.AccessPermission "getProxyVerifier"; };
Server policy
grant principal "client" { permission com.sun.jini.example.hello.ServerPermission "sayHello"; }; grant { permission com.sun.jini.example.hello.ServerPermission "getProxyVerifier"; };
Lookup service policy
grant { permission com.sun.jini.reggie.RegistrarPermission "getProxyVerifier"; }; grant principal "server" { permission com.sun.jini.reggie.RegistrarPermission "register"; permission com.sun.jini.reggie.RegistrarPermission "cancelServiceLease"; permission com.sun.jini.reggie.RegistrarPermission "renewServiceLease"; permission com.sun.jini.reggie.RegistrarPermission "cancelLeases"; permission com.sun.jini.reggie.RegistrarPermission "renewLeases"; }; grant principal "client" { permission com.sun.jini.reggie.RegistrarPermission "lookup"; permission com.sun.jini.reggie.RegistrarPermission "notify"; permission com.sun.jini.reggie.RegistrarPermission "cancelEventLease"; permission com.sun.jini.reggie.RegistrarPermission "renewEventLease"; permission com.sun.jini.reggie.RegistrarPermission "cancelLeases"; permission com.sun.jini.reggie.RegistrarPermission "renewLeases"; };To help with understanding the grant entries in the snippets above, a review of the remote interactions between the components of this example might be of value. When running under the secure configurations, the server component, represented by the principal named
server
, and
the client component, represented by the principal named
client
, both invoke various remote methods on
the lookup service, represented by the principal named
reggie
. As for the interaction between the
client and server, that interaction is restricted to the
client's invocation of the server's remote methods; the
server makes no remote calls on the client. Finally, although
the lookup service makes no remote calls into the server
component, the lookup service does make remote calls into
the client.
To understand how the lookup service makes remote calls into the client, recall that because this example's client component wishes to receive remote events from the lookup service, it registers a remote event listener with the lookup service. Thus, the client must export a remote listener object whose proxy will be used by the lookup service to make remote calls back into the client to provide the client with event notifications. This means that with respect to the client's participation in the lookup service's event mechanism, the client is actually on the server side of the remote calls, and the lookup service is on the client side.
The next thing to consider when examining the grant entries above
is the
AccessPermission
class. That class represents permission to invoke the remote method
whose name is indicated in the target component of the permission.
In the client snippet above, there is a single entry in which
the principal reggie
is granted permission to invoke
two remote methods on the listener proxy provided by the client
component: getProxyVerifier
and notify
.
Permission to invoke the listener's getProxyVerifier
method must be granted so that the lookup service can perform
trust verification on that listener's proxy, and permission to
invoke the listener's notify
method must be granted
so that the lookup service can send events to the client.
Upon comparing the snippet from the client's policy file to
the remaining snippets, one will notice that whereas the client
policy uses AccessPermission
to control who can invoke
the client's remote methods, both the server and the lookup service
enforce that control using their own custom subclasses of
AccessPermission
(the subclasses
ServerPermission
and
RegistrarPermission
respectively).
Using a simple subclass of
AccessPermission
such as those used in this example
is a common mechanism that entities can use to express separation
of grants for the various remote objects those entities export.
The choice of using AccessPermission
or a custom
subclass is a matter of taste, and thus, is left to the discretion
of the developer; although this example employs a common
pattern whereby only the servers (the hello service and the lookup
service) use a custom, server-specific permission.
The last thing to understand in the snippets above is "who" is being granted permission to invoke the indicated remote methods provided by the associated component. Considering how the components interact with each other in this example, the pattern should not be surprising. In general, the parties who are granted the permissions represent the entities from which the granting entity expects to receive remote calls. Additionally, rather than granting such parties permission to call all remote methods provided by the granting entity, those parties are granted permission to call only those methods the granting entity expects a particular party to need. The criteria used to determine the parties and the permissions they should be granted is generally based on the granting entity's perception of a particular party's "role" or expected model of interaction, together with the general level of risk that access to particular methods might pose to the granting entity. Organizing grant entries in this way allows the entity to express its access control policy as a separation of concerns.
Once again, consider the client policy. Because the client expects
only the lookup service to interact with it using the remote event
listener exported by the client, the client grants to the principal
reggie
(and only that principal), permission to call
the two remote methods provided by the listener. Compare this with
the lookup service's policy. With respect to how each interacts with
the lookup service, the concerns of the client are separate from
those of the server. That is, there are methods the client is
allowed to call that the server is not allowed to call, and vice
versa. Additionally, although there is no overlap of concerns in
this particular case, there is nothing to preclude two components
such as this example's client and server from also needing access
to a number of common methods in order to fulfill their respective
roles as Jini technology-enabled client and service.
When examining the snippets from the lookup service's policy and
the server's policy, one might wonder why the entries that grant
permission to call getProxyVerifier
grant that
permission to all parties, rather than a specific principal, as
was done in the client's policy. Recall that when running under the secure
configurations, before using a proxy, that proxy should be
prepared; where the first step in proxy preparation is
verifying that the proxy can be trusted. A fundamental element
of trust verification is an object referred to as a
trust verifier.
With respect to the remote object that provides the proxy, the
trust verification mechanism defined in the Jini technology
security model requires that some means be provided for
other parties to verify that the proxy can be trusted. In this
example, each remote object provides that means through the
implementation of the
ProxyTrust
interface, which requires the remote object to implement the
remote method
getProxyVerifier
.
Thus, each component that exports a remote object must grant
permission to call that method to any parties that are expected
to attempt to verify trust in the resulting proxy.
The policy snippets above express two different philosophies
regarding who to grant permission to call getProxyVerifier
.
The first philosphy says that permission to call any
remote method - including getProxyVerifier
- should
only be granted to specific principals. The client's policy is
an example of this first philosphy. Both the server's policy
and the lookup service's policy are examples of the second
philosophy, which allows for all parties to be granted permission
to call certain low risk methods. This allows for better separation
of concerns, and can be a bit more flexible and convenient. This
example takes the position that both philosophies have merit;
therefore, the fourth security policy principle presented above
was written to allow for both.
Note that the method getProxyVerifier
is considered
low risk, no matter who calls it, because all that method allows
the calling party to do is verify trust in the proxy. There is
nothing more that can be done through that method. Thus, with
respect to the invocation of getProxyVerifier
, all
parties are considered trusted (as required by the fourth principle).
Finally, one might observe that unlike the policy presented in this example, other applications specify a very fine grained policy, in which only the minimum set of permissions that are needed are granted in the policy file; no more, no less. As alluded to above, such a policy can be quite inflexible when addressing code changes that require new permissions be granted. This is because to accommodate any new permissions resulting from changes to code running under a fine grained policy, changes in the various policy files will also be required. Thus, although not necessarily the appropriate model for every application, the principles followed by the policy employed in this example provide a flexible mechanism that allows for a wide array of changes in the future.
For easy reference, a list of all of the policy files employed in the various configurations of this example follows:
Policy file for service starter framework
Policy files for ActivatableServer that registers server with activation system
Policy files for Basic configurations
config/start-activatable-server.policy
config/start-activatable-ssl-server.policy
config/start-activatable-krb-server.policy
Policy files for SSL configurations
config/phoenix.policy
config/reggie.policy
config/server.policy
config/activatable-server.policy
config/client.policy
Policy files for Kerberos configurations
config/ssl-phoenix.policy
config/ssl-reggie.policy
config/ssl-server.policy
config/activatable-ssl-server.policy
config/ssl-client.policy
config/krb-phoenix.policy
config/krb-reggie.policy
config/krb-server.policy
config/activatable-krb-server.policy
config/krb-client.policy
Preferred list(s)
The downloadable JAR file of the server component of this example
(server-dl.jar
) specifies preferred classes to be
recognized by a
preferred class loader
implementation to ensure that the downloaded versions of invocation
handlers and proxy classes are used. The loader implementation employed
in this example is the
PreferredClassLoader
class, and the preferred settings are specified by the following file:
# net.jini.jeri.level = INFOto
net.jini.jeri.level = FINEST
Please visit jini.org to explore the many resources provided by the Jini Community(SM).