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#doAs
vs Subject#doAsPrivileged
The default authorization algorithm employed by the AccessController
is based on Permission
intersection: If all of an AccessControlContext
's ProtectionDomain
s, potentially combined with a Subject
's Principal
s, have, statically and/or as per the Policy
in effect, the Permission
being checked, the evaluation succeeds; otherwise it fails.
Subject#doAs
does not work in your case because your Permission
is grant
ed to the combination of your ProtectionDomain
and your Principal
, but not to the domain itself. Specifically, at the time of the AccessController#checkPermission(customPermission)
invocation, the effective AccessControlContext
included following relevant (as far as Permission
evaluation is concerned) frames:
Frame # | ProtectionDomain | Permissions
--------+---------------------------+---------------------------------------------
2 | "file:./bin/" | { CustomPermission("someMethod"),
| + CustomPrincipal("user") | permissions statically assigned by default
| | by the ClassLoader }
--------+---------------------------+---------------------------------------------
1 | "file:./bin/" | { AuthPermission(
| | "createLoginContext.MyLoginModule"),
| | AuthPermission("doAs"), default as above }
--------+---------------------------+---------------------------------------------
The intersection of those frames' permissions does of course not include the desired CustomPermission
.
Subject#doAsPrivileged
, when given a null 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 which doAsPrivileged
gets invoked. What actually happens is that the null
(blank) context gets treated by the AccessController
as if it were a context whose permission evaluation yields AllPermission
; in other words:
AllPermission
⋂ permissionsframe2 = { CustomPermission("someMethod")
, default ones } ,
which is (save for the minimal set of seemingly extraneous statically-assigned Permnission
s) 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 to doAsPrivileged
instead of the null
one.
Principal
implementation forced to override equals
and hashCode
?The following stack trace snippet illustrates why:
at java.lang.Thread.dumpStack(Thread.java:1329)
at com.foo.bar.PrincipalImpl.equals(PrincipalImpl.java:53)
at javax.security.auth.Subject$SecureSet.contains(Subject.java:1201)
at java.util.Collections$SynchronizedCollection.contains(Collections.java:2021)
at java.security.Principal.implies(Principal.java:92)
at sun.security.provider.PolicyFile.addPermissions(PolicyFile.java:1374)
at sun.security.provider.PolicyFile.getPermissions(PolicyFile.java:1228)
at sun.security.provider.PolicyFile.getPermissions(PolicyFile.java:1191)
at sun.security.provider.PolicyFile.getPermissions(PolicyFile.java:1132)
at sun.security.provider.PolicyFile.implies(PolicyFile.java:1086)
at java.security.ProtectionDomain.implies(ProtectionDomain.java:281)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:450)
at java.security.AccessController.checkPermission(AccessController.java:884)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
...
Further reading: