001 /*
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements. See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership. The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License. You may obtain a copy of the License at
010 *
011 * http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied. See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 *
020 */
021 package org.apache.qpid.server.management;
022
023 import org.apache.qpid.server.security.access.management.UserManagement;
024 import org.apache.log4j.Logger;
025
026 import javax.management.remote.MBeanServerForwarder;
027 import javax.management.remote.JMXPrincipal;
028 import javax.management.MBeanServer;
029 import javax.management.ObjectName;
030 import javax.management.MBeanInfo;
031 import javax.management.MBeanOperationInfo;
032 import javax.management.JMException;
033 import javax.security.auth.Subject;
034 import java.lang.reflect.InvocationHandler;
035 import java.lang.reflect.Proxy;
036 import java.lang.reflect.Method;
037 import java.security.AccessController;
038 import java.security.Principal;
039 import java.security.AccessControlContext;
040 import java.util.Set;
041 import java.util.Properties;
042
043 /**
044 * This class can be used by the JMXConnectorServer as an InvocationHandler for the mbean operations. This implements
045 * the logic for allowing the users to invoke MBean operations and implements the restrictions for readOnly, readWrite
046 * and admin users.
047 */
048 public class MBeanInvocationHandlerImpl implements InvocationHandler
049 {
050 private static final Logger _logger = Logger.getLogger(MBeanInvocationHandlerImpl.class);
051
052 public final static String ADMIN = "admin";
053 public final static String READWRITE = "readwrite";
054 public final static String READONLY = "readonly";
055 private final static String DELEGATE = "JMImplementation:type=MBeanServerDelegate";
056 private MBeanServer mbs;
057 private static Properties _userRoles = new Properties();
058
059 public static MBeanServerForwarder newProxyInstance()
060 {
061 final InvocationHandler handler = new MBeanInvocationHandlerImpl();
062 final Class[] interfaces = new Class[]{MBeanServerForwarder.class};
063
064 Object proxy = Proxy.newProxyInstance(MBeanServerForwarder.class.getClassLoader(), interfaces, handler);
065 return MBeanServerForwarder.class.cast(proxy);
066 }
067
068 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
069 {
070 final String methodName = method.getName();
071
072 if (methodName.equals("getMBeanServer"))
073 {
074 return mbs;
075 }
076
077 if (methodName.equals("setMBeanServer"))
078 {
079 if (args[0] == null)
080 {
081 throw new IllegalArgumentException("Null MBeanServer");
082 }
083 if (mbs != null)
084 {
085 throw new IllegalArgumentException("MBeanServer object already initialized");
086 }
087 mbs = (MBeanServer) args[0];
088 return null;
089 }
090
091 // Retrieve Subject from current AccessControlContext
092 AccessControlContext acc = AccessController.getContext();
093 Subject subject = Subject.getSubject(acc);
094
095 // Allow operations performed locally on behalf of the connector server itself
096 if (subject == null)
097 {
098 return method.invoke(mbs, args);
099 }
100
101 if (args == null || DELEGATE.equals(args[0]))
102 {
103 return method.invoke(mbs, args);
104 }
105
106 // Restrict access to "createMBean" and "unregisterMBean" to any user
107 if (methodName.equals("createMBean") || methodName.equals("unregisterMBean"))
108 {
109 _logger.debug("User trying to create or unregister an MBean");
110 throw new SecurityException("Access denied");
111 }
112
113 // Retrieve JMXPrincipal from Subject
114 Set<JMXPrincipal> principals = subject.getPrincipals(JMXPrincipal.class);
115 if (principals == null || principals.isEmpty())
116 {
117 throw new SecurityException("Access denied");
118 }
119
120 Principal principal = principals.iterator().next();
121 String identity = principal.getName();
122
123 if (isAdminMethod(args))
124 {
125 if (isAdmin(identity))
126 {
127 return method.invoke(mbs, args);
128 }
129 else
130 {
131 throw new SecurityException("Access denied");
132 }
133 }
134
135 // Following users can perform any operation other than "createMBean" and "unregisterMBean"
136 if (isAllowedToModify(identity))
137 {
138 return method.invoke(mbs, args);
139 }
140
141 // These users can only call "getAttribute" on the MBeanServerDelegate MBean
142 // Here we can add other fine grained permissions like specific method for a particular mbean
143 if (isReadOnlyUser(identity) && isReadOnlyMethod(method, args))
144 {
145 return method.invoke(mbs, args);
146 }
147
148 throw new SecurityException("Access denied");
149 }
150
151 private boolean isAdminMethod(Object[] args)
152 {
153 if (args[0] instanceof ObjectName)
154 {
155 ObjectName object = (ObjectName) args[0];
156 return UserManagement.TYPE.equals(object.getKeyProperty("type"));
157 }
158
159 return false;
160 }
161
162 // Initialises the user roles
163 public static void setAccessRights(Properties accessRights)
164 {
165 _userRoles = accessRights;
166 }
167
168 private boolean isAdmin(String userName)
169 {
170 if (ADMIN.equals(_userRoles.getProperty(userName)))
171 {
172 return true;
173 }
174 return false;
175 }
176
177 private boolean isAllowedToModify(String userName)
178 {
179 if (ADMIN.equals(_userRoles.getProperty(userName))
180 || READWRITE.equals(_userRoles.getProperty(userName)))
181 {
182 return true;
183 }
184 return false;
185 }
186
187 private boolean isReadOnlyUser(String userName)
188 {
189 if (READONLY.equals(_userRoles.getProperty(userName)))
190 {
191 return true;
192 }
193 return false;
194 }
195
196 private boolean isReadOnlyMethod(Method method, Object[] args)
197 {
198 String methodName = method.getName();
199 if (methodName.startsWith("query") || methodName.startsWith("get"))
200 {
201 return true;
202 }
203 else if (methodName.startsWith("set"))
204 {
205 return false;
206 }
207
208 if ((args[0] instanceof ObjectName) && (methodName.equals("invoke")))
209 {
210 String mbeanMethod = (args.length > 1) ? (String) args[1] : null;
211 if (mbeanMethod == null)
212 {
213 return false;
214 }
215
216 try
217 {
218 MBeanInfo mbeanInfo = mbs.getMBeanInfo((ObjectName) args[0]);
219 if (mbeanInfo != null)
220 {
221 MBeanOperationInfo[] opInfos = mbeanInfo.getOperations();
222 for (MBeanOperationInfo opInfo : opInfos)
223 {
224 if (opInfo.getName().equals(mbeanMethod) && (opInfo.getImpact() == MBeanOperationInfo.INFO))
225 {
226 return true;
227 }
228 }
229 }
230 }
231 catch (JMException ex)
232 {
233 ex.printStackTrace();
234 }
235 }
236
237 return false;
238 }
239 }
|