I have built a client/server app with JAAS. The login seems to work OK, as logins are successful. It is when trying to grant permissions to specific methods that the AccessController starts casting AccessControlException.
java.security.AccessControlException: access denied ("myPackage.CustomPermission" "someMethod")
The permission-class:
public class CustomPermission extends BasicPermission {
public CustomPermission(String name) {
super(name);
}
public CustomPermission(String name, String method) {
super(name, method);
}
}
I have a custom Principal-class as well:
public class CustomPrincipal implements Principal {
private String name;
public CustomPrincipal(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
When logging in, a user provides a username and a password. In the DB the usernames and passwords have a "user level" associated to them. These user levels are used as principal-names, added to the Subject created when logging in. I add it when handling the commit-method in the LoginModule:
public boolean commit() throws LoginException {
if(status == ConfigValues.NOT || subject == null)
return false;
principal = new CustomPrincipal(userlvl);
if(subject.getPrincipals().add(principal)) {
username = null;
password = null;
status = ConfigValues.COMMIT;
return true;
}
return false;
}
For the sake of it, this is how I instantiate the login-procedure:
LoginContext lc = new LoginContext("MyLoginModule", new RemoteCallbackHandler(username, password));
lc.login();
new ServerImpl(lc.getSubject());
The Subject is then used in a "proxy" server implementation to check for permissions, like this (user = lc.getSubject):
public String someMethod() throws RemoteException, PrivilegedActionException {
return Subject.doAs(user, new PrivilegedExceptionAction<String>() {
@Override
public String run() throws PrivilegedActionException, RemoteException, ServerNotActiveException {
AccessController.checkPermission(new CustomPermission("someMethod"));
return realServerImplObj.someMethod();
}
});
}
The .policy-file:
grant codeBase "file:./bin/-" Principal myPackage.CustomPrincipal "user" {
permission myPackage.CustomPermission "someMethod";
};
The "user" is of course one of the user levels you could login with.
I've tried to add some extra grants, like:
grant codeBase "file:./bin/-" {
permission javax.security.auth.AuthPermission "createLoginContext.MyLoginModule";
permission javax.security.auth.AuthPermission "doAs";
};
I have a config for the LoginModule as well:
MyLoginModule {
myPackage.MyLoginModule required debug=true;
};
I set the properties before all this, of course: edit: the files are located in the root of the project
System.setProperty("java.security.auth.login.config", "file:./MyConfig.config");
System.setProperty("java.security.policy", "file:./MyPolicy.policy");
The server is run with the -Djava.security.manager argument. The client does not use any arguments, nor any config- or policy-files.
I've tried to remove the codeBase to see if my path was wrong. If I add permissions java.util.AllPermissions, then all is fine (but... of course it's not fine, as this is definitely not as intended). What am doing wrong here? I guess it's a combination of the Principal-, Permission, .policiy- and LoginModule-implementation.
EDIT
It is when the AccessController.checkPermissions(...) is called in the "proxy" server implementation that the exception is thrown.
EDIT 2 I have tried with the following edit of the code:
return AccessController.doPrivileged(new PrivilegedExceptionAction<String>() {
@Override
public String run() throws PrivilegedActionException, RemoteException, ServerNotActiveException {
//AccessController.checkPermission(new CustomPermission("someMethod"));
return realServerImplObj.someMethod();
}
});
Note that I've changed Subject.doAs(user, new Privile.... to AccessController.doPrivileged(new Privilege.
The user is no longer parsed in the parameter, and we use the static method AccessController.doPrivileged, rather than the Subject.doAs.
Another note:
//AccessController.checkPermission(new CustomPer...
has been commented.
But now everyone can invoke any method, as we actually never check for permissions. It is as if the AccessController never is aware of the permissions granted in the .policy-file.
EDIT 3 It seemed to be an issue with the implementation of the Principal that was the issue.
Just for clarification, these are the edits that have been made:
@Override
public String someMethod() throws PrivilegedActionException {
return Subject.doAsPrivileged(user, new PrivilegedExceptionAction<String>() {
@Override
public String run() throws PrivilegedActionException, RemoteException, ServerNotActiveException {
AccessController.checkPermission(new CustomPermission("someMethod"));
return realServerImplObj.someMethod();
}
}, null);
}
Difference is the return Subject.doAsPrivileged(user, ... , null);. Note the null at the end as well.
Implemented two methods in the CustomPrincipal-class, #equals(Object) and #hashCode(). See here for a general-purpose example of both methods, and a sample Principal implementation in general.
Also added (even though it seems to be running without, actually) the following the the .policy-file
grant codeBase "file:./bin/-" {
permission javax.security.auth.AuthPermission "createLoginContext.MyLoginModule";
permission javax.security.auth.AuthPermission "doAs";
permission javax.security.auth.AuthPermission "doAsPrivileged";
};
permission javax.security.auth.AuthPermission "doAsPrivileged"; is the newly added entry.
Subject#doAsvsSubject#doAsPrivilegedThe default authorization algorithm employed by the
AccessControlleris based onPermissionintersection: If all of anAccessControlContext'sProtectionDomains, potentially combined with aSubject'sPrincipals, have, statically and/or as per thePolicyin effect, thePermissionbeing checked, the evaluation succeeds; otherwise it fails.Subject#doAsdoes not work in your case because yourPermissionisgranted to the combination of yourProtectionDomainand yourPrincipal, but not to the domain itself. Specifically, at the time of theAccessController#checkPermission(customPermission)invocation, the effectiveAccessControlContextincluded following relevant (as far asPermissionevaluation is concerned) frames:The intersection of those frames' permissions does of course not include the desired
CustomPermission.Subject#doAsPrivileged, when given anull AccessControlContext, on the other hand, does the trick, because it "trims" the effective context's stack to its top-most frame, i.e., the one from whichdoAsPrivilegedgets invoked. What actually happens is that thenull(blank) context gets treated by theAccessControlleras if it were a context whose permission evaluation yieldsAllPermission; in other words:AllPermission⋂ permissionsframe2 = {CustomPermission("someMethod"), default ones } ,which is (save for the minimal set of seemingly extraneous statically-assigned
Permnissions) the desired outcome.Of course, in cases where such potentially arbitrary privilege escalation is undesired, a custom context, whose encapsulated domains' permissions express the maximum set of privileges you are willing to grant (to e.g. some
Subject), can be passed todoAsPrivilegedinstead of thenullone.Why is the
Principalimplementation forced to overrideequalsandhashCode?The following stack trace snippet illustrates why:
Further reading: