6721651: Security problem with out-of-the-box management
Reviewed-by: emcmanus, lmalvent
This commit is contained in:
parent
6c11535cdd
commit
576a962dcb
@ -111,6 +111,22 @@ public abstract class MBeanServerAccessController
|
|||||||
*/
|
*/
|
||||||
protected abstract void checkWrite();
|
protected abstract void checkWrite();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the caller can create the named class. The default
|
||||||
|
* implementation of this method calls {@link #checkWrite()}.
|
||||||
|
*/
|
||||||
|
protected void checkCreate(String className) {
|
||||||
|
checkWrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the caller can unregister the named MBean. The default
|
||||||
|
* implementation of this method calls {@link #checkWrite()}.
|
||||||
|
*/
|
||||||
|
protected void checkUnregister(ObjectName name) {
|
||||||
|
checkWrite();
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------
|
//--------------------------------------------
|
||||||
//--------------------------------------------
|
//--------------------------------------------
|
||||||
//
|
//
|
||||||
@ -148,7 +164,7 @@ public abstract class MBeanServerAccessController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call <code>checkWrite()</code>, then forward this method to the
|
* Call <code>checkCreate(className)</code>, then forward this method to the
|
||||||
* wrapped object.
|
* wrapped object.
|
||||||
*/
|
*/
|
||||||
public ObjectInstance createMBean(String className, ObjectName name)
|
public ObjectInstance createMBean(String className, ObjectName name)
|
||||||
@ -158,7 +174,7 @@ public abstract class MBeanServerAccessController
|
|||||||
MBeanRegistrationException,
|
MBeanRegistrationException,
|
||||||
MBeanException,
|
MBeanException,
|
||||||
NotCompliantMBeanException {
|
NotCompliantMBeanException {
|
||||||
checkWrite();
|
checkCreate(className);
|
||||||
SecurityManager sm = System.getSecurityManager();
|
SecurityManager sm = System.getSecurityManager();
|
||||||
if (sm == null) {
|
if (sm == null) {
|
||||||
Object object = getMBeanServer().instantiate(className);
|
Object object = getMBeanServer().instantiate(className);
|
||||||
@ -170,7 +186,7 @@ public abstract class MBeanServerAccessController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call <code>checkWrite()</code>, then forward this method to the
|
* Call <code>checkCreate(className)</code>, then forward this method to the
|
||||||
* wrapped object.
|
* wrapped object.
|
||||||
*/
|
*/
|
||||||
public ObjectInstance createMBean(String className, ObjectName name,
|
public ObjectInstance createMBean(String className, ObjectName name,
|
||||||
@ -181,7 +197,7 @@ public abstract class MBeanServerAccessController
|
|||||||
MBeanRegistrationException,
|
MBeanRegistrationException,
|
||||||
MBeanException,
|
MBeanException,
|
||||||
NotCompliantMBeanException {
|
NotCompliantMBeanException {
|
||||||
checkWrite();
|
checkCreate(className);
|
||||||
SecurityManager sm = System.getSecurityManager();
|
SecurityManager sm = System.getSecurityManager();
|
||||||
if (sm == null) {
|
if (sm == null) {
|
||||||
Object object = getMBeanServer().instantiate(className,
|
Object object = getMBeanServer().instantiate(className,
|
||||||
@ -196,7 +212,7 @@ public abstract class MBeanServerAccessController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call <code>checkWrite()</code>, then forward this method to the
|
* Call <code>checkCreate(className)</code>, then forward this method to the
|
||||||
* wrapped object.
|
* wrapped object.
|
||||||
*/
|
*/
|
||||||
public ObjectInstance createMBean(String className,
|
public ObjectInstance createMBean(String className,
|
||||||
@ -209,7 +225,7 @@ public abstract class MBeanServerAccessController
|
|||||||
MBeanException,
|
MBeanException,
|
||||||
NotCompliantMBeanException,
|
NotCompliantMBeanException,
|
||||||
InstanceNotFoundException {
|
InstanceNotFoundException {
|
||||||
checkWrite();
|
checkCreate(className);
|
||||||
SecurityManager sm = System.getSecurityManager();
|
SecurityManager sm = System.getSecurityManager();
|
||||||
if (sm == null) {
|
if (sm == null) {
|
||||||
Object object = getMBeanServer().instantiate(className,
|
Object object = getMBeanServer().instantiate(className,
|
||||||
@ -222,7 +238,7 @@ public abstract class MBeanServerAccessController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call <code>checkWrite()</code>, then forward this method to the
|
* Call <code>checkCreate(className)</code>, then forward this method to the
|
||||||
* wrapped object.
|
* wrapped object.
|
||||||
*/
|
*/
|
||||||
public ObjectInstance createMBean(String className,
|
public ObjectInstance createMBean(String className,
|
||||||
@ -237,7 +253,7 @@ public abstract class MBeanServerAccessController
|
|||||||
MBeanException,
|
MBeanException,
|
||||||
NotCompliantMBeanException,
|
NotCompliantMBeanException,
|
||||||
InstanceNotFoundException {
|
InstanceNotFoundException {
|
||||||
checkWrite();
|
checkCreate(className);
|
||||||
SecurityManager sm = System.getSecurityManager();
|
SecurityManager sm = System.getSecurityManager();
|
||||||
if (sm == null) {
|
if (sm == null) {
|
||||||
Object object = getMBeanServer().instantiate(className,
|
Object object = getMBeanServer().instantiate(className,
|
||||||
@ -394,45 +410,45 @@ public abstract class MBeanServerAccessController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call <code>checkWrite()</code>, then forward this method to the
|
* Call <code>checkCreate(className)</code>, then forward this method to the
|
||||||
* wrapped object.
|
* wrapped object.
|
||||||
*/
|
*/
|
||||||
public Object instantiate(String className)
|
public Object instantiate(String className)
|
||||||
throws ReflectionException, MBeanException {
|
throws ReflectionException, MBeanException {
|
||||||
checkWrite();
|
checkCreate(className);
|
||||||
return getMBeanServer().instantiate(className);
|
return getMBeanServer().instantiate(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call <code>checkWrite()</code>, then forward this method to the
|
* Call <code>checkCreate(className)</code>, then forward this method to the
|
||||||
* wrapped object.
|
* wrapped object.
|
||||||
*/
|
*/
|
||||||
public Object instantiate(String className,
|
public Object instantiate(String className,
|
||||||
Object params[],
|
Object params[],
|
||||||
String signature[])
|
String signature[])
|
||||||
throws ReflectionException, MBeanException {
|
throws ReflectionException, MBeanException {
|
||||||
checkWrite();
|
checkCreate(className);
|
||||||
return getMBeanServer().instantiate(className, params, signature);
|
return getMBeanServer().instantiate(className, params, signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call <code>checkWrite()</code>, then forward this method to the
|
* Call <code>checkCreate(className)</code>, then forward this method to the
|
||||||
* wrapped object.
|
* wrapped object.
|
||||||
*/
|
*/
|
||||||
public Object instantiate(String className, ObjectName loaderName)
|
public Object instantiate(String className, ObjectName loaderName)
|
||||||
throws ReflectionException, MBeanException, InstanceNotFoundException {
|
throws ReflectionException, MBeanException, InstanceNotFoundException {
|
||||||
checkWrite();
|
checkCreate(className);
|
||||||
return getMBeanServer().instantiate(className, loaderName);
|
return getMBeanServer().instantiate(className, loaderName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call <code>checkWrite()</code>, then forward this method to the
|
* Call <code>checkCreate(className)</code>, then forward this method to the
|
||||||
* wrapped object.
|
* wrapped object.
|
||||||
*/
|
*/
|
||||||
public Object instantiate(String className, ObjectName loaderName,
|
public Object instantiate(String className, ObjectName loaderName,
|
||||||
Object params[], String signature[])
|
Object params[], String signature[])
|
||||||
throws ReflectionException, MBeanException, InstanceNotFoundException {
|
throws ReflectionException, MBeanException, InstanceNotFoundException {
|
||||||
checkWrite();
|
checkCreate(className);
|
||||||
return getMBeanServer().instantiate(className, loaderName,
|
return getMBeanServer().instantiate(className, loaderName,
|
||||||
params, signature);
|
params, signature);
|
||||||
}
|
}
|
||||||
@ -579,12 +595,12 @@ public abstract class MBeanServerAccessController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call <code>checkWrite()</code>, then forward this method to the
|
* Call <code>checkUnregister()</code>, then forward this method to the
|
||||||
* wrapped object.
|
* wrapped object.
|
||||||
*/
|
*/
|
||||||
public void unregisterMBean(ObjectName name)
|
public void unregisterMBean(ObjectName name)
|
||||||
throws InstanceNotFoundException, MBeanRegistrationException {
|
throws InstanceNotFoundException, MBeanRegistrationException {
|
||||||
checkWrite();
|
checkUnregister(name);
|
||||||
getMBeanServer().unregisterMBean(name);
|
getMBeanServer().unregisterMBean(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,11 +31,17 @@ import java.security.AccessControlContext;
|
|||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
import java.util.Collection;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import javax.management.MBeanServer;
|
import javax.management.MBeanServer;
|
||||||
|
import javax.management.ObjectName;
|
||||||
import javax.security.auth.Subject;
|
import javax.security.auth.Subject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,7 +52,8 @@ import javax.security.auth.Subject;
|
|||||||
* not allowed; in this case the request is not forwarded to the
|
* not allowed; in this case the request is not forwarded to the
|
||||||
* wrapped object.</p>
|
* wrapped object.</p>
|
||||||
*
|
*
|
||||||
* <p>This class implements the {@link #checkRead()} and {@link #checkWrite()}
|
* <p>This class implements the {@link #checkRead()}, {@link #checkWrite()},
|
||||||
|
* {@link #checkCreate(String)}, and {@link #checkUnregister(ObjectName)}
|
||||||
* methods based on an access level properties file containing username/access
|
* methods based on an access level properties file containing username/access
|
||||||
* level pairs. The set of username/access level pairs is passed either as a
|
* level pairs. The set of username/access level pairs is passed either as a
|
||||||
* filename which denotes a properties file on disk, or directly as an instance
|
* filename which denotes a properties file on disk, or directly as an instance
|
||||||
@ -56,14 +63,50 @@ import javax.security.auth.Subject;
|
|||||||
* has exactly one access level. The same access level can be shared by several
|
* has exactly one access level. The same access level can be shared by several
|
||||||
* usernames.</p>
|
* usernames.</p>
|
||||||
*
|
*
|
||||||
* <p>The supported access level values are <i>readonly</i> and
|
* <p>The supported access level values are {@code readonly} and
|
||||||
* <i>readwrite</i>.</p>
|
* {@code readwrite}. The {@code readwrite} access level can be
|
||||||
|
* qualified by one or more <i>clauses</i>, where each clause looks
|
||||||
|
* like <code>create <i>classNamePattern</i></code> or {@code
|
||||||
|
* unregister}. For example:</p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* monitorRole readonly
|
||||||
|
* controlRole readwrite \
|
||||||
|
* create javax.management.timer.*,javax.management.monitor.* \
|
||||||
|
* unregister
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>(The continuation lines with {@code \} come from the parser for
|
||||||
|
* Properties files.)</p>
|
||||||
*/
|
*/
|
||||||
public class MBeanServerFileAccessController
|
public class MBeanServerFileAccessController
|
||||||
extends MBeanServerAccessController {
|
extends MBeanServerAccessController {
|
||||||
|
|
||||||
public static final String READONLY = "readonly";
|
static final String READONLY = "readonly";
|
||||||
public static final String READWRITE = "readwrite";
|
static final String READWRITE = "readwrite";
|
||||||
|
|
||||||
|
static final String CREATE = "create";
|
||||||
|
static final String UNREGISTER = "unregister";
|
||||||
|
|
||||||
|
private enum AccessType {READ, WRITE, CREATE, UNREGISTER};
|
||||||
|
|
||||||
|
private static class Access {
|
||||||
|
final boolean write;
|
||||||
|
final String[] createPatterns;
|
||||||
|
private boolean unregister;
|
||||||
|
|
||||||
|
Access(boolean write, boolean unregister, List<String> createPatternList) {
|
||||||
|
this.write = write;
|
||||||
|
int npats = (createPatternList == null) ? 0 : createPatternList.size();
|
||||||
|
if (npats == 0)
|
||||||
|
this.createPatterns = NO_STRINGS;
|
||||||
|
else
|
||||||
|
this.createPatterns = createPatternList.toArray(new String[npats]);
|
||||||
|
this.unregister = unregister;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String[] NO_STRINGS = new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Create a new MBeanServerAccessController that forwards all the
|
* <p>Create a new MBeanServerAccessController that forwards all the
|
||||||
@ -87,8 +130,8 @@ public class MBeanServerFileAccessController
|
|||||||
throws IOException {
|
throws IOException {
|
||||||
super();
|
super();
|
||||||
this.accessFileName = accessFileName;
|
this.accessFileName = accessFileName;
|
||||||
props = propertiesFromFile(accessFileName);
|
Properties props = propertiesFromFile(accessFileName);
|
||||||
checkValues(props);
|
parseProperties(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -123,14 +166,14 @@ public class MBeanServerFileAccessController
|
|||||||
* #setMBeanServer} method after doing access checks based on read and
|
* #setMBeanServer} method after doing access checks based on read and
|
||||||
* write permissions.</p>
|
* write permissions.</p>
|
||||||
*
|
*
|
||||||
* <p>This instance is initialized from the specified properties instance.
|
* <p>This instance is initialized from the specified properties
|
||||||
* This constructor makes a copy of the properties instance using its
|
* instance. This constructor makes a copy of the properties
|
||||||
* <code>clone</code> method and it is the copy that is consulted to check
|
* instance and it is the copy that is consulted to check the
|
||||||
* the username and access level of an incoming connection. The original
|
* username and access level of an incoming connection. The
|
||||||
* properties object can be modified without affecting the copy. If the
|
* original properties object can be modified without affecting
|
||||||
* {@link #refresh} method is then called, the
|
* the copy. If the {@link #refresh} method is then called, the
|
||||||
* <code>MBeanServerFileAccessController</code> will make a new copy of the
|
* <code>MBeanServerFileAccessController</code> will make a new
|
||||||
* properties object at that time.</p>
|
* copy of the properties object at that time.</p>
|
||||||
*
|
*
|
||||||
* @param accessFileProps properties list containing the username/access
|
* @param accessFileProps properties list containing the username/access
|
||||||
* level entries.
|
* level entries.
|
||||||
@ -145,8 +188,7 @@ public class MBeanServerFileAccessController
|
|||||||
if (accessFileProps == null)
|
if (accessFileProps == null)
|
||||||
throw new IllegalArgumentException("Null properties");
|
throw new IllegalArgumentException("Null properties");
|
||||||
originalProps = accessFileProps;
|
originalProps = accessFileProps;
|
||||||
props = (Properties) accessFileProps.clone();
|
parseProperties(accessFileProps);
|
||||||
checkValues(props);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -155,14 +197,14 @@ public class MBeanServerFileAccessController
|
|||||||
* #setMBeanServer} method after doing access checks based on read and
|
* #setMBeanServer} method after doing access checks based on read and
|
||||||
* write permissions.</p>
|
* write permissions.</p>
|
||||||
*
|
*
|
||||||
* <p>This instance is initialized from the specified properties instance.
|
* <p>This instance is initialized from the specified properties
|
||||||
* This constructor makes a copy of the properties instance using its
|
* instance. This constructor makes a copy of the properties
|
||||||
* <code>clone</code> method and it is the copy that is consulted to check
|
* instance and it is the copy that is consulted to check the
|
||||||
* the username and access level of an incoming connection. The original
|
* username and access level of an incoming connection. The
|
||||||
* properties object can be modified without affecting the copy. If the
|
* original properties object can be modified without affecting
|
||||||
* {@link #refresh} method is then called, the
|
* the copy. If the {@link #refresh} method is then called, the
|
||||||
* <code>MBeanServerFileAccessController</code> will make a new copy of the
|
* <code>MBeanServerFileAccessController</code> will make a new
|
||||||
* properties object at that time.</p>
|
* copy of the properties object at that time.</p>
|
||||||
*
|
*
|
||||||
* @param accessFileProps properties list containing the username/access
|
* @param accessFileProps properties list containing the username/access
|
||||||
* level entries.
|
* level entries.
|
||||||
@ -184,16 +226,36 @@ public class MBeanServerFileAccessController
|
|||||||
* Check if the caller can do read operations. This method does
|
* Check if the caller can do read operations. This method does
|
||||||
* nothing if so, otherwise throws SecurityException.
|
* nothing if so, otherwise throws SecurityException.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void checkRead() {
|
public void checkRead() {
|
||||||
checkAccessLevel(READONLY);
|
checkAccess(AccessType.READ, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the caller can do write operations. This method does
|
* Check if the caller can do write operations. This method does
|
||||||
* nothing if so, otherwise throws SecurityException.
|
* nothing if so, otherwise throws SecurityException.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void checkWrite() {
|
public void checkWrite() {
|
||||||
checkAccessLevel(READWRITE);
|
checkAccess(AccessType.WRITE, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the caller can create MBeans or instances of the given class.
|
||||||
|
* This method does nothing if so, otherwise throws SecurityException.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void checkCreate(String className) {
|
||||||
|
checkAccess(AccessType.CREATE, className);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the caller can do unregister operations. This method does
|
||||||
|
* nothing if so, otherwise throws SecurityException.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void checkUnregister(ObjectName name) {
|
||||||
|
checkAccess(AccessType.UNREGISTER, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -218,14 +280,13 @@ public class MBeanServerFileAccessController
|
|||||||
* @exception IllegalArgumentException if any of the supplied access
|
* @exception IllegalArgumentException if any of the supplied access
|
||||||
* level values differs from "readonly" or "readwrite".
|
* level values differs from "readonly" or "readwrite".
|
||||||
*/
|
*/
|
||||||
public void refresh() throws IOException {
|
public synchronized void refresh() throws IOException {
|
||||||
synchronized (props) {
|
Properties props;
|
||||||
if (accessFileName == null)
|
if (accessFileName == null)
|
||||||
props = (Properties) originalProps.clone();
|
props = (Properties) originalProps;
|
||||||
else
|
else
|
||||||
props = propertiesFromFile(accessFileName);
|
props = propertiesFromFile(accessFileName);
|
||||||
checkValues(props);
|
parseProperties(props);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Properties propertiesFromFile(String fname)
|
private static Properties propertiesFromFile(String fname)
|
||||||
@ -240,7 +301,7 @@ public class MBeanServerFileAccessController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkAccessLevel(String accessLevel) {
|
private synchronized void checkAccess(AccessType requiredAccess, String arg) {
|
||||||
final AccessControlContext acc = AccessController.getContext();
|
final AccessControlContext acc = AccessController.getContext();
|
||||||
final Subject s =
|
final Subject s =
|
||||||
AccessController.doPrivileged(new PrivilegedAction<Subject>() {
|
AccessController.doPrivileged(new PrivilegedAction<Subject>() {
|
||||||
@ -250,39 +311,234 @@ public class MBeanServerFileAccessController
|
|||||||
});
|
});
|
||||||
if (s == null) return; /* security has not been enabled */
|
if (s == null) return; /* security has not been enabled */
|
||||||
final Set principals = s.getPrincipals();
|
final Set principals = s.getPrincipals();
|
||||||
|
String newPropertyValue = null;
|
||||||
for (Iterator i = principals.iterator(); i.hasNext(); ) {
|
for (Iterator i = principals.iterator(); i.hasNext(); ) {
|
||||||
final Principal p = (Principal) i.next();
|
final Principal p = (Principal) i.next();
|
||||||
String grantedAccessLevel;
|
Access access = accessMap.get(p.getName());
|
||||||
synchronized (props) {
|
if (access != null) {
|
||||||
grantedAccessLevel = props.getProperty(p.getName());
|
boolean ok;
|
||||||
}
|
switch (requiredAccess) {
|
||||||
if (grantedAccessLevel != null) {
|
case READ:
|
||||||
if (accessLevel.equals(READONLY) &&
|
ok = true; // all access entries imply read
|
||||||
(grantedAccessLevel.equals(READONLY) ||
|
break;
|
||||||
grantedAccessLevel.equals(READWRITE)))
|
case WRITE:
|
||||||
return;
|
ok = access.write;
|
||||||
if (accessLevel.equals(READWRITE) &&
|
break;
|
||||||
grantedAccessLevel.equals(READWRITE))
|
case UNREGISTER:
|
||||||
|
ok = access.unregister;
|
||||||
|
if (!ok && access.write)
|
||||||
|
newPropertyValue = "unregister";
|
||||||
|
break;
|
||||||
|
case CREATE:
|
||||||
|
ok = checkCreateAccess(access, arg);
|
||||||
|
if (!ok && access.write)
|
||||||
|
newPropertyValue = "create " + arg;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
if (ok)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new SecurityException("Access denied! Invalid access level for " +
|
SecurityException se = new SecurityException("Access denied! Invalid " +
|
||||||
"requested MBeanServer operation.");
|
"access level for requested MBeanServer operation.");
|
||||||
|
// Add some more information to help people with deployments that
|
||||||
|
// worked before we required explicit create clauses. We're not giving
|
||||||
|
// any information to the bad guys, other than that the access control
|
||||||
|
// is based on a file, which they could have worked out from the stack
|
||||||
|
// trace anyway.
|
||||||
|
if (newPropertyValue != null) {
|
||||||
|
SecurityException se2 = new SecurityException("Access property " +
|
||||||
|
"for this identity should be similar to: " + READWRITE +
|
||||||
|
" " + newPropertyValue);
|
||||||
|
se.initCause(se2);
|
||||||
|
}
|
||||||
|
throw se;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkValues(Properties props) {
|
private static boolean checkCreateAccess(Access access, String className) {
|
||||||
Collection c = props.values();
|
for (String classNamePattern : access.createPatterns) {
|
||||||
for (Iterator i = c.iterator(); i.hasNext(); ) {
|
if (classNameMatch(classNamePattern, className))
|
||||||
final String accessLevel = (String) i.next();
|
return true;
|
||||||
if (!accessLevel.equals(READONLY) &&
|
}
|
||||||
!accessLevel.equals(READWRITE)) {
|
return false;
|
||||||
throw new IllegalArgumentException(
|
}
|
||||||
"Syntax error in access level entry [" + accessLevel + "]");
|
|
||||||
}
|
private static boolean classNameMatch(String pattern, String className) {
|
||||||
|
// We studiously avoided regexes when parsing the properties file,
|
||||||
|
// because that is done whenever the VM is started with the
|
||||||
|
// appropriate -Dcom.sun.management options, even if nobody ever
|
||||||
|
// creates an MBean. We don't want to incur the overhead of loading
|
||||||
|
// all the regex code whenever those options are specified, but if we
|
||||||
|
// get as far as here then the VM is already running and somebody is
|
||||||
|
// doing the very unusual operation of remotely creating an MBean.
|
||||||
|
// Because that operation is so unusual, we don't try to optimize
|
||||||
|
// by hand-matching or by caching compiled Pattern objects.
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
StringTokenizer stok = new StringTokenizer(pattern, "*", true);
|
||||||
|
while (stok.hasMoreTokens()) {
|
||||||
|
String tok = stok.nextToken();
|
||||||
|
if (tok.equals("*"))
|
||||||
|
sb.append("[^.]*");
|
||||||
|
else
|
||||||
|
sb.append(Pattern.quote(tok));
|
||||||
|
}
|
||||||
|
return className.matches(sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseProperties(Properties props) {
|
||||||
|
this.accessMap = new HashMap<String, Access>();
|
||||||
|
for (Map.Entry<Object, Object> entry : props.entrySet()) {
|
||||||
|
String identity = (String) entry.getKey();
|
||||||
|
String accessString = (String) entry.getValue();
|
||||||
|
Access access = Parser.parseAccess(identity, accessString);
|
||||||
|
accessMap.put(identity, access);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Properties props;
|
private static class Parser {
|
||||||
|
private final static int EOS = -1; // pseudo-codepoint "end of string"
|
||||||
|
static {
|
||||||
|
assert !Character.isWhitespace(EOS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String identity; // just for better error messages
|
||||||
|
private final String s; // the string we're parsing
|
||||||
|
private final int len; // s.length()
|
||||||
|
private int i;
|
||||||
|
private int c;
|
||||||
|
// At any point, either c is s.codePointAt(i), or i == len and
|
||||||
|
// c is EOS. We use int rather than char because it is conceivable
|
||||||
|
// (if unlikely) that a classname in a create clause might contain
|
||||||
|
// "supplementary characters", the ones that don't fit in the original
|
||||||
|
// 16 bits for Unicode.
|
||||||
|
|
||||||
|
private Parser(String identity, String s) {
|
||||||
|
this.identity = identity;
|
||||||
|
this.s = s;
|
||||||
|
this.len = s.length();
|
||||||
|
this.i = 0;
|
||||||
|
if (i < len)
|
||||||
|
this.c = s.codePointAt(i);
|
||||||
|
else
|
||||||
|
this.c = EOS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Access parseAccess(String identity, String s) {
|
||||||
|
return new Parser(identity, s).parseAccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Access parseAccess() {
|
||||||
|
skipSpace();
|
||||||
|
String type = parseWord();
|
||||||
|
Access access;
|
||||||
|
if (type.equals(READONLY))
|
||||||
|
access = new Access(false, false, null);
|
||||||
|
else if (type.equals(READWRITE))
|
||||||
|
access = parseReadWrite();
|
||||||
|
else {
|
||||||
|
throw syntax("Expected " + READONLY + " or " + READWRITE +
|
||||||
|
": " + type);
|
||||||
|
}
|
||||||
|
if (c != EOS)
|
||||||
|
throw syntax("Extra text at end of line");
|
||||||
|
return access;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Access parseReadWrite() {
|
||||||
|
List<String> createClasses = new ArrayList<String>();
|
||||||
|
boolean unregister = false;
|
||||||
|
while (true) {
|
||||||
|
skipSpace();
|
||||||
|
if (c == EOS)
|
||||||
|
break;
|
||||||
|
String type = parseWord();
|
||||||
|
if (type.equals(UNREGISTER))
|
||||||
|
unregister = true;
|
||||||
|
else if (type.equals(CREATE))
|
||||||
|
parseCreate(createClasses);
|
||||||
|
else
|
||||||
|
throw syntax("Unrecognized keyword " + type);
|
||||||
|
}
|
||||||
|
return new Access(true, unregister, createClasses);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseCreate(List<String> createClasses) {
|
||||||
|
while (true) {
|
||||||
|
skipSpace();
|
||||||
|
createClasses.add(parseClassName());
|
||||||
|
skipSpace();
|
||||||
|
if (c == ',')
|
||||||
|
next();
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String parseClassName() {
|
||||||
|
// We don't check that classname components begin with suitable
|
||||||
|
// characters (so we accept 1.2.3 for example). This means that
|
||||||
|
// there are only two states, which we can call dotOK and !dotOK
|
||||||
|
// according as a dot (.) is legal or not. Initially we're in
|
||||||
|
// !dotOK since a classname can't start with a dot; after a dot
|
||||||
|
// we're in !dotOK again; and after any other characters we're in
|
||||||
|
// dotOK. The classname is only accepted if we end in dotOK,
|
||||||
|
// so we reject an empty name or a name that ends with a dot.
|
||||||
|
final int start = i;
|
||||||
|
boolean dotOK = false;
|
||||||
|
while (true) {
|
||||||
|
if (c == '.') {
|
||||||
|
if (!dotOK)
|
||||||
|
throw syntax("Bad . in class name");
|
||||||
|
dotOK = false;
|
||||||
|
} else if (c == '*' || Character.isJavaIdentifierPart(c))
|
||||||
|
dotOK = true;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
String className = s.substring(start, i);
|
||||||
|
if (!dotOK)
|
||||||
|
throw syntax("Bad class name " + className);
|
||||||
|
return className;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance c and i to the next character, unless already at EOS.
|
||||||
|
private void next() {
|
||||||
|
if (c != EOS) {
|
||||||
|
i += Character.charCount(c);
|
||||||
|
if (i < len)
|
||||||
|
c = s.codePointAt(i);
|
||||||
|
else
|
||||||
|
c = EOS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void skipSpace() {
|
||||||
|
while (Character.isWhitespace(c))
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String parseWord() {
|
||||||
|
skipSpace();
|
||||||
|
if (c == EOS)
|
||||||
|
throw syntax("Expected word at end of line");
|
||||||
|
final int start = i;
|
||||||
|
while (c != EOS && !Character.isWhitespace(c))
|
||||||
|
next();
|
||||||
|
String word = s.substring(start, i);
|
||||||
|
skipSpace();
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IllegalArgumentException syntax(String msg) {
|
||||||
|
return new IllegalArgumentException(
|
||||||
|
msg + " [" + identity + " " + s + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Access> accessMap;
|
||||||
private Properties originalProps;
|
private Properties originalProps;
|
||||||
private String accessFileName;
|
private String accessFileName;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
# passwords. To be functional, a role must have an entry in
|
# passwords. To be functional, a role must have an entry in
|
||||||
# both the password and the access files.
|
# both the password and the access files.
|
||||||
#
|
#
|
||||||
# Default location of this file is $JRE/lib/management/jmxremote.access
|
# The default location of this file is $JRE/lib/management/jmxremote.access
|
||||||
# You can specify an alternate location by specifying a property in
|
# You can specify an alternate location by specifying a property in
|
||||||
# the management config file $JRE/lib/management/management.properties
|
# the management config file $JRE/lib/management/management.properties
|
||||||
# (See that file for details)
|
# (See that file for details)
|
||||||
@ -16,7 +16,7 @@
|
|||||||
# The file format for password and access files is syntactically the same
|
# The file format for password and access files is syntactically the same
|
||||||
# as the Properties file format. The syntax is described in the Javadoc
|
# as the Properties file format. The syntax is described in the Javadoc
|
||||||
# for java.util.Properties.load.
|
# for java.util.Properties.load.
|
||||||
# Typical access file has multiple lines, where each line is blank,
|
# A typical access file has multiple lines, where each line is blank,
|
||||||
# a comment (like this one), or an access control entry.
|
# a comment (like this one), or an access control entry.
|
||||||
#
|
#
|
||||||
# An access control entry consists of a role name, and an
|
# An access control entry consists of a role name, and an
|
||||||
@ -29,10 +29,38 @@
|
|||||||
# role can read measurements but cannot perform any action
|
# role can read measurements but cannot perform any action
|
||||||
# that changes the environment of the running program.
|
# that changes the environment of the running program.
|
||||||
# "readwrite" grants access to read and write attributes of MBeans,
|
# "readwrite" grants access to read and write attributes of MBeans,
|
||||||
# to invoke operations on them, and to create or remove them.
|
# to invoke operations on them, and optionally
|
||||||
# This access should be granted to only trusted clients,
|
# to create or remove them. This access should be granted
|
||||||
# since they can potentially interfere with the smooth
|
# only to trusted clients, since they can potentially
|
||||||
# operation of a running program
|
# interfere with the smooth operation of a running program.
|
||||||
|
#
|
||||||
|
# The "readwrite" access level can optionally be followed by the "create" and/or
|
||||||
|
# "unregister" keywords. The "unregister" keyword grants access to unregister
|
||||||
|
# (delete) MBeans. The "create" keyword grants access to create MBeans of a
|
||||||
|
# particular class or of any class matching a particular pattern. Access
|
||||||
|
# should only be granted to create MBeans of known and trusted classes.
|
||||||
|
#
|
||||||
|
# For example, the following entry would grant readwrite access
|
||||||
|
# to "controlRole", as well as access to create MBeans of the class
|
||||||
|
# javax.management.monitor.CounterMonitor and to unregister any MBean:
|
||||||
|
# controlRole readwrite \
|
||||||
|
# create javax.management.monitor.CounterMonitorMBean \
|
||||||
|
# unregister
|
||||||
|
# or equivalently:
|
||||||
|
# controlRole readwrite unregister create javax.management.monitor.CounterMBean
|
||||||
|
#
|
||||||
|
# The following entry would grant readwrite access as well as access to create
|
||||||
|
# MBeans of any class in the packages javax.management.monitor and
|
||||||
|
# javax.management.timer:
|
||||||
|
# controlRole readwrite \
|
||||||
|
# create javax.management.monitor.*,javax.management.timer.* \
|
||||||
|
# unregister
|
||||||
|
#
|
||||||
|
# The \ character is defined in the Properties file syntax to allow continuation
|
||||||
|
# lines as shown here. A * in a class pattern matches a sequence of characters
|
||||||
|
# other than dot (.), so javax.management.monitor.* matches
|
||||||
|
# javax.management.monitor.CounterMonitor but not
|
||||||
|
# javax.management.monitor.foo.Bar.
|
||||||
#
|
#
|
||||||
# A given role should have at most one entry in this file. If a role
|
# A given role should have at most one entry in this file. If a role
|
||||||
# has no entry, it has no access.
|
# has no entry, it has no access.
|
||||||
@ -42,7 +70,10 @@
|
|||||||
#
|
#
|
||||||
# Default access control entries:
|
# Default access control entries:
|
||||||
# o The "monitorRole" role has readonly access.
|
# o The "monitorRole" role has readonly access.
|
||||||
# o The "controlRole" role has readwrite access.
|
# o The "controlRole" role has readwrite access and can create the standard
|
||||||
|
# Timer and Monitor MBeans defined by the JMX API.
|
||||||
|
|
||||||
monitorRole readonly
|
monitorRole readonly
|
||||||
controlRole readwrite
|
controlRole readwrite \
|
||||||
|
create javax.management.monitor.*,javax.management.timer.* \
|
||||||
|
unregister
|
||||||
|
Loading…
x
Reference in New Issue
Block a user