blob: a227e13f5d2e1643f5109942e8f6c09f5dc2ee24 [file] [log] [blame]
/*
* Copyright 2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
/*
* @test
* @bug 5108776
* @summary Test that the EventClientDelegate MBean does not require extra
* permissions compared with plain addNotificationListener.
* @author Eamonn McManus
* @run main/othervm -Dxjava.security.debug=policy,access,failure EventDelegateSecurityTest
*/
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.management.ManagementFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.AllPermission;
import java.security.PrivilegedExceptionAction;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
import javax.management.MBeanPermission;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.event.EventClient;
import javax.management.remote.JMXAuthenticator;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXPrincipal;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.MBeanServerForwarder;
import javax.security.auth.Subject;
public class EventDelegateSecurityTest {
private static final BlockingQueue<Notification> notifQ =
new SynchronousQueue<Notification>();
private static volatile long seqNo;
private static volatile long expectSeqNo;
private static class QueueListener implements NotificationListener {
public void handleNotification(Notification notification,
Object handback) {
try {
notifQ.put(notification);
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
}
private static final NotificationListener queueListener = new QueueListener();
public static interface SenderMBean {
public void send();
}
public static class Sender
extends NotificationBroadcasterSupport implements SenderMBean {
public void send() {
Notification n = new Notification("x", this, seqNo++);
sendNotification(n);
}
}
private static class LimitInvocationHandler implements InvocationHandler {
private MBeanServer nextMBS;
private final Set<String> allowedMethods = new HashSet<String>();
void allow(String... names) {
synchronized (allowedMethods) {
allowedMethods.addAll(Arrays.asList(names));
}
}
public Object invoke(Object proxy, Method m, Object[] args)
throws Throwable {
System.out.println(
"filter: " + m.getName() +
((args == null) ? "[]" : Arrays.deepToString(args)));
String name = m.getName();
if (name.equals("getMBeanServer"))
return nextMBS;
if (name.equals("setMBeanServer")) {
nextMBS = (MBeanServer) args[0];
return null;
}
if (m.getDeclaringClass() == Object.class ||
allowedMethods.contains(name)) {
try {
return m.invoke(nextMBS, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
} else {
System.out.println("...refused");
throw new SecurityException(
"Method refused: " + m.getDeclaringClass().getName() +
"." + m.getName() +
((args == null) ? "[]" : Arrays.deepToString(args)));
}
}
}
private static interface MakeConnectorServer {
public JMXConnectorServer make(JMXServiceURL url) throws IOException;
}
public static void main(String[] args) throws Exception {
JMXPrincipal rootPrincipal = new JMXPrincipal("root");
Subject rootSubject = new Subject();
rootSubject.getPrincipals().add(rootPrincipal);
Subject.doAsPrivileged(rootSubject, new PrivilegedExceptionAction<Void>() {
public Void run() throws Exception {
mainAsRoot();
return null;
}
}, null);
}
private static void mainAsRoot() throws Exception {
AccessControlContext acc = AccessController.getContext();
Subject subject = Subject.getSubject(acc);
System.out.println("Subject: " + subject);
final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("a:b=c");
mbs.registerMBean(new Sender(), name);
System.out.println("Test with no installed security");
test(mbs, name, new MakeConnectorServer() {
public JMXConnectorServer make(JMXServiceURL url) throws IOException {
return
JMXConnectorServerFactory.newJMXConnectorServer(url, null, null);
}
});
System.out.println("Test with filtering MBeanServerForwarder");
LimitInvocationHandler limitIH = new LimitInvocationHandler();
// We allow getClassLoaderRepository because the ConnectorServer
// calls it so any real checking MBeanServerForwarder must accept it.
limitIH.allow(
"addNotificationListener", "removeNotificationListener",
"getClassLoaderRepository"
);
final MBeanServerForwarder limitMBSF = (MBeanServerForwarder)
Proxy.newProxyInstance(
MBeanServerForwarder.class.getClassLoader(),
new Class<?>[] {MBeanServerForwarder.class},
limitIH);
// We go to considerable lengths to ensure that the ConnectorServer has
// no MBeanServer when the EventClientDelegate forwarder is activated,
// so that the calls it makes when it is later linked to an MBeanServer
// go through the limitMBSF.
test(mbs, name, new MakeConnectorServer() {
public JMXConnectorServer make(JMXServiceURL url) throws IOException {
JMXConnectorServer cs =
JMXConnectorServerFactory.newJMXConnectorServer(url, null, null);
limitMBSF.setMBeanServer(mbs);
cs.setMBeanServerForwarder(limitMBSF);
return cs;
}
});
final File policyFile =
File.createTempFile("EventDelegateSecurityTest", ".policy");
PrintWriter pw = new PrintWriter(policyFile);
String JMXPrincipal = JMXPrincipal.class.getName();
String AllPermission = AllPermission.class.getName();
String MBeanPermission = MBeanPermission.class.getName();
pw.println("grant principal " + JMXPrincipal + " \"root\" {");
pw.println(" permission " + AllPermission + ";");
pw.println("};");
pw.println("grant principal " + JMXPrincipal + " \"user\" {");
pw.println(" permission " + MBeanPermission + " \"*\", " +
" \"addNotificationListener\";");
pw.println(" permission " + MBeanPermission + " \"*\", " +
" \"removeNotificationListener\";");
pw.println("};");
pw.close();
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
policyFile.delete();
}
});
System.setProperty("java.security.policy", policyFile.getAbsolutePath());
System.setSecurityManager(new SecurityManager());
test(mbs, name, new MakeConnectorServer() {
public JMXConnectorServer make(JMXServiceURL url) throws IOException {
Map<String, Object> env = new HashMap<String, Object>();
env.put(JMXConnectorServer.AUTHENTICATOR, new JMXAuthenticator() {
public Subject authenticate(Object credentials) {
Subject s = new Subject();
s.getPrincipals().add(new JMXPrincipal("user"));
return s;
}
});
return
JMXConnectorServerFactory.newJMXConnectorServer(url, env, null);
}
});
}
private static void test(MBeanServer mbs, ObjectName name) throws Exception {
test(mbs, name, null);
}
private static void test(
MBeanServer mbs, ObjectName name, MakeConnectorServer make)
throws Exception {
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///");
JMXConnectorServer cs = make.make(url);
ObjectName csName = new ObjectName("a:type=ConnectorServer");
mbs.registerMBean(cs, csName);
cs.start();
try {
JMXServiceURL addr = cs.getAddress();
JMXConnector cc = JMXConnectorFactory.connect(addr);
MBeanServerConnection mbsc = cc.getMBeanServerConnection();
test(mbs, mbsc, name);
cc.close();
mbs.unregisterMBean(csName);
} finally {
cs.stop();
}
}
private static void test(
MBeanServer mbs, MBeanServerConnection mbsc, ObjectName name)
throws Exception {
EventClient ec = new EventClient(mbsc);
ec.addNotificationListener(name, queueListener, null, null);
mbs.invoke(name, "send", null, null);
Notification n = notifQ.poll(5, TimeUnit.SECONDS);
if (n == null)
throw new Exception("FAILED: notif not delivered");
if (n.getSequenceNumber() != expectSeqNo) {
throw new Exception(
"FAILED: notif seqno " + n.getSequenceNumber() +
" should be " + expectSeqNo);
}
expectSeqNo++;
ec.removeNotificationListener(name, queueListener);
ec.close();
}
}