blob: eb31a45f38b40d311ff65525e2ffd3a61b0c3423 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.geode.management.internal.security;
import java.io.ObjectInputStream;
import java.util.HashSet;
import java.util.Set;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.Descriptor;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.InvalidAttributeValueException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanFeatureInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.NotCompliantMBeanException;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.OperationsException;
import javax.management.Query;
import javax.management.QueryExp;
import javax.management.ReflectionException;
import javax.management.loading.ClassLoaderRepository;
import javax.management.remote.MBeanServerForwarder;
import org.apache.commons.lang3.StringUtils;
import org.apache.geode.annotations.Immutable;
import org.apache.geode.internal.security.SecurityService;
import org.apache.geode.management.internal.ManagementConstants;
import org.apache.geode.security.GemFireSecurityException;
import org.apache.geode.security.ResourcePermission;
import org.apache.geode.security.ResourcePermission.Operation;
import org.apache.geode.security.ResourcePermission.Resource;
import org.apache.geode.security.ResourcePermission.Target;
/**
* This class intercepts all MBean requests for GemFire MBeans and passed it to
* ManagementInterceptor for authorization
*
* @since Geode 1.0
*/
public class MBeanServerWrapper implements MBeanServerForwarder {
private MBeanServer mbs;
private final SecurityService securityService;
public MBeanServerWrapper(SecurityService securityService) {
this.securityService = securityService;
}
private void checkDomain(ObjectName name) {
if (ManagementConstants.OBJECTNAME__DEFAULTDOMAIN.equals(name.getDomain()))
throw new SecurityException(ResourceConstants.ACCESS_DENIED_MESSAGE);
}
@Override
public ObjectInstance createMBean(String className, ObjectName name) throws ReflectionException,
InstanceAlreadyExistsException, MBeanException, NotCompliantMBeanException {
checkDomain(name);
return mbs.createMBean(className, name);
}
@Override
public ObjectInstance createMBean(String className, ObjectName name, ObjectName loaderName)
throws ReflectionException, InstanceAlreadyExistsException, MBeanException,
NotCompliantMBeanException, InstanceNotFoundException {
checkDomain(name);
return mbs.createMBean(className, name, loaderName);
}
@Override
public ObjectInstance createMBean(String className, ObjectName name, Object[] params,
String[] signature) throws ReflectionException, InstanceAlreadyExistsException,
MBeanException, NotCompliantMBeanException {
checkDomain(name);
return mbs.createMBean(className, name, params, signature);
}
@Override
public ObjectInstance createMBean(String className, ObjectName name, ObjectName loaderName,
Object[] params, String[] signature)
throws ReflectionException, InstanceAlreadyExistsException, MBeanException,
NotCompliantMBeanException, InstanceNotFoundException {
checkDomain(name);
return mbs.createMBean(className, name, loaderName, params, signature);
}
@Override
public ObjectInstance registerMBean(Object object, ObjectName name)
throws InstanceAlreadyExistsException, MBeanRegistrationException,
NotCompliantMBeanException {
checkDomain(name);
return mbs.registerMBean(object, name);
}
@Override
public void unregisterMBean(ObjectName name)
throws InstanceNotFoundException, MBeanRegistrationException {
checkDomain(name);
mbs.unregisterMBean(name);
}
@Override
public ObjectInstance getObjectInstance(ObjectName name) throws InstanceNotFoundException {
return mbs.getObjectInstance(name);
}
@Immutable
private static final QueryExp notAccessControlMBean =
Query.not(Query.isInstanceOf(Query.value(AccessControlMXBean.class.getName())));
@Override
public Set<ObjectInstance> queryMBeans(ObjectName name, QueryExp query) {
// We need to filter out the AccessControlMXBean so that the clients wouldn't see it
if (query != null)
return mbs.queryMBeans(name, Query.and(query, notAccessControlMBean));
else
return mbs.queryMBeans(name, notAccessControlMBean);
}
@Override
public Set<ObjectName> queryNames(ObjectName name, QueryExp query) {
if (query != null)
return mbs.queryNames(name, Query.and(query, notAccessControlMBean));
else
return mbs.queryNames(name, notAccessControlMBean);
}
@Override
public boolean isRegistered(ObjectName name) {
return mbs.isRegistered(name);
}
@Override
public Integer getMBeanCount() {
return mbs.getMBeanCount();
}
@Override
public Object getAttribute(ObjectName name, String attribute)
throws MBeanException, InstanceNotFoundException, ReflectionException {
ResourcePermission ctx = getOperationContext(name, attribute, false);
this.securityService.authorize(ctx);
Object result;
try {
result = mbs.getAttribute(name, attribute);
} catch (AttributeNotFoundException nex) {
return null;
}
return result;
}
@Override
public AttributeList getAttributes(ObjectName name, String[] attributes)
throws InstanceNotFoundException, ReflectionException {
AttributeList results;
checkAuthorization(name, attributes);
try {
results = mbs.getAttributes(name, attributes);
} catch (Exception e) {
throw new GemFireSecurityException(
"error getting values of attributes :" + attributes + " from " + name,
e);
}
return results;
}
void checkAuthorization(ObjectName name, String[] attributes)
throws InstanceNotFoundException, ReflectionException {
Set<ResourcePermission> contextSet = new HashSet<>();
for (String attribute : attributes) {
ResourcePermission ctx = getOperationContext(name, attribute, false);
if (ctx != null) {
if (contextSet.add(ctx)) {
this.securityService.authorize(ctx);
}
}
}
}
@Override
public void setAttribute(ObjectName name, Attribute attribute)
throws InstanceNotFoundException, AttributeNotFoundException, InvalidAttributeValueException,
MBeanException, ReflectionException {
ResourcePermission ctx = getOperationContext(name, attribute.getName(), false);
this.securityService.authorize(ctx);
mbs.setAttribute(name, attribute);
}
@Override
public AttributeList setAttributes(ObjectName name, AttributeList attributes)
throws InstanceNotFoundException, ReflectionException {
// call setAttribute instead to use the authorization logic
checkAuthorization(name,
(String[]) attributes.parallelStream().map(attribute -> ((Attribute) attribute).getName())
.toArray());
try {
mbs.setAttributes(name, attributes);
} catch (Exception e) {
throw new GemFireSecurityException("error setting attributes :" + attributes + " of " + name,
e);
}
return attributes;
}
@Override
public Object invoke(ObjectName name, String operationName, Object[] params, String[] signature)
throws InstanceNotFoundException, MBeanException, ReflectionException {
ResourcePermission ctx = getOperationContext(name, operationName, true);
this.securityService.authorize(ctx);
return mbs.invoke(name, operationName, params, signature);
}
// TODO: cache this
private ResourcePermission getOperationContext(ObjectName objectName, String featureName,
boolean isOp) throws InstanceNotFoundException, ReflectionException {
MBeanInfo beanInfo;
try {
beanInfo = mbs.getMBeanInfo(objectName);
} catch (IntrospectionException e) {
throw new GemFireSecurityException("error getting beanInfo of " + objectName, e);
}
// If there is no annotation defined either in the class level or method level, we should
// consider this operation/attribute freely accessible
ResourcePermission result = null;
// find the context in the beanInfo if defined in the class level
result = getOperationContext(beanInfo.getDescriptor(), result);
MBeanFeatureInfo[] featureInfos;
if (isOp) {
featureInfos = beanInfo.getOperations();
} else {
featureInfos = beanInfo.getAttributes();
}
// still look into the attributes/operations to see if it's defined in the method level
for (MBeanFeatureInfo info : featureInfos) {
if (info.getName().equals(featureName)) {
// found the featureInfo of this method on the bean
result = getOperationContext(info.getDescriptor(), result);
break;
}
}
return result;
}
private ResourcePermission getOperationContext(Descriptor descriptor,
ResourcePermission defaultValue) {
String resource = (String) descriptor.getFieldValue("resource");
String operationCode = (String) descriptor.getFieldValue("operation");
String targetCode = (String) descriptor.getFieldValue("target");
if (resource != null && operationCode != null) {
if (StringUtils.isBlank(targetCode)) {
return new ResourcePermission(Resource.valueOf(resource), Operation.valueOf(operationCode));
} else {
return new ResourcePermission(Resource.valueOf(resource), Operation.valueOf(operationCode),
Target.valueOf(targetCode).getName());
}
}
return defaultValue;
}
@Override
public String getDefaultDomain() {
return mbs.getDefaultDomain();
}
@Override
public String[] getDomains() {
return mbs.getDomains();
}
@Override
public void addNotificationListener(ObjectName name, NotificationListener listener,
NotificationFilter filter, Object handback) throws InstanceNotFoundException {
mbs.addNotificationListener(name, listener, filter, handback);
}
@Override
public void addNotificationListener(ObjectName name, ObjectName listener,
NotificationFilter filter, Object handback) throws InstanceNotFoundException {
mbs.addNotificationListener(name, listener, filter, handback);
}
@Override
public void removeNotificationListener(ObjectName name, ObjectName listener)
throws InstanceNotFoundException, ListenerNotFoundException {
mbs.removeNotificationListener(name, listener);
}
@Override
public void removeNotificationListener(ObjectName name, ObjectName listener,
NotificationFilter filter, Object handback)
throws InstanceNotFoundException, ListenerNotFoundException {
mbs.removeNotificationListener(name, listener, filter, handback);
}
@Override
public void removeNotificationListener(ObjectName name, NotificationListener listener)
throws InstanceNotFoundException, ListenerNotFoundException {
mbs.removeNotificationListener(name, listener);
}
@Override
public void removeNotificationListener(ObjectName name, NotificationListener listener,
NotificationFilter filter, Object handback)
throws InstanceNotFoundException, ListenerNotFoundException {
mbs.removeNotificationListener(name, listener, filter, handback);
}
@Override
public MBeanInfo getMBeanInfo(ObjectName name)
throws InstanceNotFoundException, IntrospectionException, ReflectionException {
return mbs.getMBeanInfo(name);
}
@Override
public boolean isInstanceOf(ObjectName name, String className) throws InstanceNotFoundException {
return mbs.isInstanceOf(name, className);
}
@Override
public Object instantiate(String className) throws ReflectionException, MBeanException {
return mbs.instantiate(className);
}
@Override
public Object instantiate(String className, ObjectName loaderName)
throws ReflectionException, MBeanException, InstanceNotFoundException {
return mbs.instantiate(className, loaderName);
}
@Override
public Object instantiate(String className, Object[] params, String[] signature)
throws ReflectionException, MBeanException {
return mbs.instantiate(className, params, signature);
}
@Override
public Object instantiate(String className, ObjectName loaderName, Object[] params,
String[] signature) throws ReflectionException, MBeanException, InstanceNotFoundException {
return mbs.instantiate(className, params, signature);
}
@SuppressWarnings("deprecation")
@Override
public ObjectInputStream deserialize(ObjectName name, byte[] data) throws OperationsException {
return mbs.deserialize(name, data);
}
@Override
public ObjectInputStream deserialize(String className, byte[] data)
throws OperationsException, ReflectionException {
return mbs.deserialize(className, data);
}
@SuppressWarnings("deprecation")
@Override
public ObjectInputStream deserialize(String className, ObjectName loaderName, byte[] data)
throws OperationsException, ReflectionException {
return mbs.deserialize(className, loaderName, data);
}
@Override
public ClassLoader getClassLoaderFor(ObjectName mbeanName) throws InstanceNotFoundException {
return mbs.getClassLoaderFor(mbeanName);
}
@Override
public ClassLoader getClassLoader(ObjectName loaderName) throws InstanceNotFoundException {
return mbs.getClassLoader(loaderName);
}
@Override
public ClassLoaderRepository getClassLoaderRepository() {
return mbs.getClassLoaderRepository();
}
@Override
public MBeanServer getMBeanServer() {
return mbs;
}
@Override
public void setMBeanServer(MBeanServer mbs) {
this.mbs = mbs;
}
}