Feedback |
The previous tutorial, Use of JAAS Login Utility and Java GSS-API for Secure Message Exchanges, demonstrated how two applications, in particular a client and a server, could use the Java GSS-API to establish a secure context between them and then securely exchange messages.
There are additional operations the context acceptor (the server in our client/server example) can perform once the context has been established with the context initiator (the client). Basically, the server can "impersonate" the client. The level of impersonation depends upon whether or not the client has delegated credentials to the server.
One possible type of client impersonation the server can do is causing code to be executed on behalf of the same entity (user) the client code is executed on behalf of. Normally, a method executed by a thread uses the access control settings for that thread itself. However, when impersonating a client in this tutorial, the server uses the client's access control settings so that the server has access to exactly those resources that the client itself has when it runs.
One major benefit of the approach used in this tutorial is that the JAAS authorization component can be used for access control. Without the JAAS authorization component, the server principal would need access to any resources accessed by the code executed on behalf of the client user, and the server code would need to include access control logic to determine whether the user was authorized to access such resources. By utilizing JAAS authorization, providing principal-based access control, the access control is handled automatically. Permissions for the security-sensitive operations in such code only need to be granted to that user and not also to the server code. See the JAAS Authorization tutorial for more information on JAAS authorization.
Basic Approach
How does the server "impersonate" the client to execute code on behalf of the user running the client code? Essentially the same way the client code is set up to be run on behalf of that user. All the server code needs to know is the user's principal name, which it can obtain from the context established with the client.
Recall that JAAS authentication of the user executing the client code results in creation of a Subject containing a Principal with the user (principal) name. The Subject is subsequently associated with a new access control context (via a Subject.doAsPrivileged call from the Login utility) and the client code is considered to be executed on behalf of the user; subsequent access control decisions are based on whether or not that particular user, executing the client code, is granted the required permissions.
The server code is similarly handled, except in that case the Principal specified for authentication is typically a "service principal", not a user principal. Again, a Subject containing a Principal with the specified principal name is created, Subject.doAsPrivileged is called, and the server code is considered to be executed on behalf of the specified principal; subsequent access control decisions are based on whether or not that particular principal, executing the server code, is granted the required permissions.
Once the client and server have established a mutual context, the context initiator's name (the client's principal name) can be determined by the following:
GSSName clientGSSName = context.getSrcName();The context acceptor (the server) can use this name to construct a Subject containing a Principal that represents the same entity. For example, if you are using a JRE from Sun Microsystems, you can construct such a Subject via the following:
Subject client = com.sun.security.jgss.GSSUtil.createSubject(clientGSSName, null);The createSubject method creates a new Subject from the GSSName and GSSCredential specified as arguments. If the server code is just going to execute code on behalf of the user in the local JVM, the user's credentials are not required - and in fact cannot even be obtained unless the client has delegated credentials to the server, as discussed in Using Credentials Delegated From the Client. Since the credentials are not needed here, we pass a null for the GSSCredential argument.
Note: If you are not using a JRE from Sun Microsystems, an alternative way to do this is to construct a KerberosPrincipal instance as follows:KerberosPrincipal principal = new KerberosPrincipal(clientGSSName.toString());Then use this principal to construct a new Subject or populate this principal in the principal set of an existing Subject.
The code that the server would like to execute on behalf of the user should be initiated from the run method of a class that implements java.security.PrivilegedAction (or java.security.PrivilegedExceptionAction). That is, the code can either be in such a run method or invoked from such a run method.
The server code can pass the Subject, along with an instance of the PrivilegedAction (or PrivilegedExceptionAction), to Subject.doAsPrivileged to execute the subsequent code, starting with the run method in the PrivilegedAction, on behalf of the principal (user) in the specified Subject.
For example, suppose the PrivilegedAction class is called ReadFileAction and it takes as an argument a String with the principal name. You can create an instance of this class by
String clientName = clientGSSName.toString(); PrivilegedAction readFile = new ReadFileAction(clientName);The call to doAsPrivileged is then
Subject.doAsPrivileged(client, readFile, null);Sample Code and Policy File
The following sample code and policy file illustrate the server impersonating the client in order to execute code whose security-sensitive operations are only permitted to be done by the specific user executing the client.
SampleServerImp.java
The SampleServerImp.java file is exactly the same as the SampleServer.java file from the previous (Use of JAAS Login Utility and Java GSS-API for Secure Message Exchanges) tutorial, except that after exchanging messages with the client, it has the following code to perform a ReadFileAction as the client user:
System.out.println("Impersonating client."); /* * Extract the KerberosPrincipal from the client GSSName and * populate it in the principal set of a new Subject. Pass in a * null for credentials since credentials will not be needed. */ GSSName clientGSSName = context.getSrcName(); System.out.println("clientGSSName: " + clientGSSName); Subject client = com.sun.security.jgss.GSSUtil.createSubject(clientGSSName, null); /* * Construct an action that will read a file meant only for the * client */ String clientName = clientGSSName.toString(); PrivilegedAction readFile = new ReadFileAction(clientName); /* * Invoke the action via a doAsPrivileged. This allows the * action to be executed as the client subject, and it also * runs that code as privileged. This means that any permission * checking that happens beyond this point applies only to * the code being run as the client. */ Subject.doAsPrivileged(client, readFile, null);ReadFileAction.java
The ReadFileAction.java file contains the ReadFileAction class. Its constructor takes as an argument a String for the name of the client user. The client username is used to construct a file name for a file from which ReadFileAction will attempt to read. The file name will be:
where <name> is the client username without its corresponding realm. For example, if the full username is mjones@KRBNT-OPERATIONS.ABC.COM, then the file name is./data/<name>_info.txtNote: On Microsoft Windows systems the forward slashes will be backward slashes../data/mjones_info.txtThe ReadFileAction run method reads the specified file and prints its contents.
serverimp.policy
ReadFileAction attempts to read a file, which is a security-checked operation. Since ReadFileAction is considered to be executed as the client user (Principal), the appropriate permission must be granted not only to the ReadFileAction code itself, but to the client Principal as well.
Assuming the ReadFileAction class is placed in a JAR file named ReadFileAction.jar, and the user principal name is mjones@KRBNT-OPERATIONS.ABC.COM, this permission can be granted via the following in a policy file:
The serverimp.policy file is exactly the same as the server.policy file from the previous (Use of JAAS Login Utility and Java GSS-API for Secure Message Exchanges) tutorial, except that it grants the SampleServer code the javax.security.auth.AuthPermission "doAsPrivileged" permission it needs in order to call the doAsPrivileged method, and it has the following placeholder for granting the FilePermission shown above:grant CodeBase "file:./ReadFileAction.jar" Principal javax.security.auth.kerberos.KerberosPrincipal "mjones@KRBNT-OPERATIONS.ABC.COM" { permission java.io.FilePermission "data/mjones_info.txt", "read"; };grant CodeBase "file:./ReadFileAction.jar" Principal javax.security.auth.kerberos.KerberosPrincipal "your_user_name@your_realm" { permission java.io.FilePermission "data/your_user_name_info.txt", "read"; };You must substitute your Kerberos realm for your_realm, and your user name for your_user_name in both your_user_name@your_realm and data/your_user_name_info.txt. If you are working on a Microsoft Windows sytem, you also replace the "/" in data/your_user_name_info.txt with a "\".
Running the Sample Code
To run the sample code illustrating the server impersonating the client, do everything listed in Running the SampleClient and SampleServer Programs in the previous tutorial, except for the following:
- In the "Prepare SampleServer for Execution" step:
- Use SampleServerImp.java instead of SampleServer.java. Compile it and create a JAR file named SampleServerImp.jar containing SampleServerImp.class via the following:
javac SampleServerImp.java jar -cvf SampleServerImp.jar SampleServerImp.class
- Use the serverimp.policy policy file instead of server.policy.
- Use the csImpLogin.conf login configuration file instead of cs.conf.
- Copy ReadFileAction.java to the same directory as the other files. Compile it and place it in a JAR file via the following:
javac ReadFileAction.java jar -cvf ReadFileAction.jar ReadFileAction.class- In csImpLogin.conf, replace "service_principal@your_realm" with the Kerberos name of the service principal that represents SampleServer.
- In serverimp.policy, replace "service_principal@your_realm" in both places it appears with the Kerberos name of the service principal that represents SampleServer. (The same name as that used in the login configuration file.) In addition, substitute your Kerberos realm for your_realm, and your user name for your_user_name in both your_user_name@your_realm and data/your_user_name_info.txt. If you are running on a Microsoft Windows system, then replace the "/" in data/your_user_name_info.txt with a "\".
- Create a data subdirectory of your current directory and create a short text file of the specified name in that directory. For example, if your user name is mjones, the file to be placed in the data subdirectory should be named mjones_info.txt.
- In the "Execute SampleServer" step:
- Use the following commands instead of those specified in that section so that SampleServerImp is executed, serverimp.policy and csImpLogin.conf are used, and ReadFileAction.jar is included.
Important: In these commands, replace <port_number> with an appropriate port number (a high port number such as 4444), <your_realm> with your Kerberos realm, and <your_kdc> with your Kerberos KDC.
Here is the command for Microsoft Windows systems:
java -classpath Login.jar;SampleServerImp.jar;ReadFileAction.jar -Djava.security.manager -Djava.security.krb5.realm=<your_realm> -Djava.security.krb5.kdc=<your_kdc> -Djava.security.policy=serverimp.policy -Djava.security.auth.login.config=csImpLogin.conf Login SampleServerImp <port_number>Here is the command for UNIX systems:
java -classpath Login.jar:SampleServerImp.jar:ReadFileAction.jar -Djava.security.manager -Djava.security.krb5.realm=<your_realm> -Djava.security.krb5.kdc=<your_kdc> -Djava.security.policy=serverimp.policy -Djava.security.auth.login.config=csImpLogin.conf Login SampleServerImp <port_number>As usual, type the full command on one line. Multiple lines are used here for legibility. If the command is too long for your system, you may need to place it in a .bat file (for Microsoft Windows) or a .sh file (for UNIX) and then run that file to execute the command.
As when running SampleServer, you will be prompted for the Kerberos password for the service principal under which SampleServerImp is expected to be run. The Kerberos login module specified in the login configuration file will log the service principal into Kerberos. Once authentication is successfully completed, the SampleServerImp code will be executed on behalf of the service principal. It will listen for socket connections on the specified port.
After you follow the "Prepare SampleClient for Execution" and "Execute SampleClient" instructions as usual and perform the user login, the client code will request a socket connection with SampleServerImp. Once SampleServerImp accepts the connection, SampleClient and SampleServerImp establish a shared context and then exchange messages as described in the previous tutorial.
After the message exchange, SampleServerImp determines the principal name of the user executing the client code, creates a new Subject containing a Principal with that name, and calls Subject.doAsPrivileged to execute the code in ReadFileAction on behalf of the specified user. ReadFileAction reads the file named your_user_name_info.txt (where your_user_name represents the actual user name) in the data subdirectory of the current directory, and prints out its contents.
For login troubleshooting suggestions, see Troubleshooting.
The most complete type of client impersonation is possible if the client delegates its credentials to the server.
Recall that prior to context establishment with the context acceptor (the server in our previous tutorial), the context initiator (the client) sets various context options. If the initiator calls the requestCredDeleg method on the context object with a true argument, as in
then this requests that the initiator's credentials be delegated to the acceptor during context establishment.context.requestCredDeleg(true);Delegation of credentials from the initiator to the acceptor enables the acceptor to authenticate itself as an agent or delegate of the initiator.
First, after context establishment, the acceptor must determine whether or not credential delegation actually took place. It does so by calling the getCredDelegState method:
boolean delegated = context.getCredDelegState();If credentials were delegated, the acceptor can obtain those credentials by calling the getDelegCr method:
GSSCredential clientCr = context.getDelegCr();The resulting GSSCredential object can then be used to initiate subsequent GSS-API contexts as a "delegate" of the initiator. For example, the server could authenticate as the client to a backend server that cares more about who the original client was than who the intermediate server is.
Acting as the client, the server can establish a connection with the backend server, establish a joint security context, and exchange messages in basically the same manner that the client and server did.
One way it could be done is that when the server calls the createContext method of a GSSManager, it could pass createContext the delegated credentials instead of passing a null.
Alternatively, the server code could first call the com.sun.security.jgss.GSSUtil createSubject method and pass it the delegated credentials. That method returns a Subject containing those credentials as the default credentials. The server could then associate this Subject with the current AccessControlContext, as described in How Do You Associate a Subject with an Access Control Context? in the JAAS Authorization tutorial. Then, when the server code calls the GSSManager createContext method, it can pass a null (indicating the credentials for the "current" Subject should be used). In other words, the server would effectively become the client. Subsequent connections to backend servers using GSS could be made exactly as described in the previous tutorials. This approach is useful if you want the code that will use the delegated credentials to be identical to the code that uses the default local credentials.
Permission Required In Order to Delegate Credentials
In order to delegate credentials, the context initiator (SampleClient in our previous tutorial) must have a javax.security.auth.kerberos.DelegationPermission. An example using placeholders in italics for actual values is the following:
permission javax.security.auth.kerberos.DelegationPermission "\"service_principal@your_realm\" \"krbtgt/your_realm@your_realm\"";Note that DelegationPermission has a single target in quotes that contains two items, both of which are quoted. Each inner quote is escaped by a "\". Thus the first item is
and the second is"service_principal@your_realm""krbtgt/your_realm@your_realm"This basically gives the code executing on behalf of the client the permission to forward a Kerberos ticket to the specified peer (service_principal), where the Kerberos ticket is meant to avail service from krbtgt/your_realm@your_realm.
Substitute your realm for all places your_realm appears. Also substitute the service principal name for the service principal representing the server for service_principal@your_realm. (See Kerberos User and Service Principal Names in the previous tutorial.) Suppose your realm is KRBNT-OPERATIONS.ABC.COM and the service principal is "sample/raven.abc.com@KRBNT-OPERATIONS.ABC.COM". Then the permission could appear in a policy file as
permission javax.security.auth.kerberos.DelegationPermission "\"sample/raven.abc.com@KRBNT-OPERATIONS.ABC.COM\" \"krbtgt/KRBNT-OPERATIONS.ABC.COM@KRBNT-OPERATIONS.ABC.COM\"";
Feedback |