| /* |
| * 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; |
| } |
| |
| } |