blob: f582fed6a0d604b1a6260f6af9109a6c331a3a10 [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.qpid.server.security;
import static org.apache.qpid.server.security.access.ObjectType.EXCHANGE;
import static org.apache.qpid.server.security.access.ObjectType.METHOD;
import static org.apache.qpid.server.security.access.ObjectType.OBJECT;
import static org.apache.qpid.server.security.access.ObjectType.QUEUE;
import static org.apache.qpid.server.security.access.ObjectType.VIRTUALHOST;
import static org.apache.qpid.server.security.access.Operation.ACCESS;
import static org.apache.qpid.server.security.access.Operation.BIND;
import static org.apache.qpid.server.security.access.Operation.CONSUME;
import static org.apache.qpid.server.security.access.Operation.CREATE;
import static org.apache.qpid.server.security.access.Operation.DELETE;
import static org.apache.qpid.server.security.access.Operation.PUBLISH;
import static org.apache.qpid.server.security.access.Operation.PURGE;
import static org.apache.qpid.server.security.access.Operation.UNBIND;
import java.net.SocketAddress;
import java.security.Principal;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.security.auth.Subject;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.log4j.Logger;
import org.apache.qpid.framing.AMQShortString;
import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin;
import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory;
import org.apache.qpid.server.exchange.Exchange;
import org.apache.qpid.server.plugins.PluginManager;
import org.apache.qpid.server.queue.AMQQueue;
import org.apache.qpid.server.security.access.ObjectProperties;
import org.apache.qpid.server.security.access.Operation;
/**
* The security manager contains references to all loaded {@link SecurityPlugin}s and delegates security decisions to them based
* on virtual host name. The plugins can be external <em>OSGi</em> .jar files that export the required classes or just internal
* objects for simpler plugins.
*
* @see SecurityPlugin
*/
public class SecurityManager
{
private static final Logger _logger = Logger.getLogger(SecurityManager.class);
/** Container for the {@link Principal} that is using to this thread. */
private static final ThreadLocal<Subject> _subject = new ThreadLocal<Subject>();
private PluginManager _pluginManager;
private Map<String, SecurityPluginFactory> _pluginFactories = new HashMap<String, SecurityPluginFactory>();
private Map<String, SecurityPlugin> _globalPlugins = new HashMap<String, SecurityPlugin>();
private Map<String, SecurityPlugin> _hostPlugins = new HashMap<String, SecurityPlugin>();
public static class SecurityConfiguration extends ConfigurationPlugin
{
public static final ConfigurationPluginFactory FACTORY = new ConfigurationPluginFactory()
{
public ConfigurationPlugin newInstance(String path, Configuration config) throws ConfigurationException
{
ConfigurationPlugin instance = new SecurityConfiguration();
instance.setConfiguration(path, config);
return instance;
}
public List<String> getParentPaths()
{
return Arrays.asList("security", "virtualhosts.virtualhost.security");
}
};
@Override
public String[] getElementsProcessed()
{
return new String[]{"security"};
}
public void validateConfiguration() throws ConfigurationException
{
if (_configuration.isEmpty())
{
throw new ConfigurationException("security section is incomplete, no elements found.");
}
}
}
public SecurityManager(SecurityManager parent) throws ConfigurationException
{
_pluginManager = parent._pluginManager;
_pluginFactories = parent._pluginFactories;
// our global plugins are the parent's host plugins
_globalPlugins = parent._hostPlugins;
}
public SecurityManager(ConfigurationPlugin configuration, PluginManager manager) throws ConfigurationException
{
this(configuration, manager, null);
}
public SecurityManager(ConfigurationPlugin configuration, PluginManager manager, SecurityPluginFactory plugin) throws ConfigurationException
{
_pluginManager = manager;
if (manager == null) // No plugin manager, no plugins
{
return;
}
_pluginFactories = _pluginManager.getSecurityPlugins();
if (plugin != null)
{
_pluginFactories.put(plugin.getPluginName(), plugin);
}
configureHostPlugins(configuration);
}
public static Subject getThreadSubject()
{
return _subject.get();
}
public static void setThreadSubject(final Subject subject)
{
_subject.set(subject);
}
public void configureHostPlugins(ConfigurationPlugin hostConfig) throws ConfigurationException
{
_hostPlugins = configurePlugins(hostConfig);
}
public void configureGlobalPlugins(ConfigurationPlugin configuration) throws ConfigurationException
{
_globalPlugins = configurePlugins(configuration);
}
public Map<String, SecurityPlugin> configurePlugins(ConfigurationPlugin hostConfig) throws ConfigurationException
{
Map<String, SecurityPlugin> plugins = new HashMap<String, SecurityPlugin>();
SecurityConfiguration securityConfig = hostConfig.getConfiguration(SecurityConfiguration.class.getName());
// If we have no security Configuration then there is nothing to configure.
if (securityConfig != null)
{
for (SecurityPluginFactory<?> factory : _pluginFactories.values())
{
SecurityPlugin plugin = factory.newInstance(securityConfig);
if (plugin != null)
{
plugins.put(factory.getPluginName(), plugin);
}
}
}
return plugins;
}
public void addHostPlugin(SecurityPlugin plugin)
{
_hostPlugins.put(plugin.getClass().getName(), plugin);
}
public static Logger getLogger()
{
return _logger;
}
private abstract class AccessCheck
{
abstract Result allowed(SecurityPlugin plugin);
}
private boolean checkAllPlugins(AccessCheck checker)
{
HashMap<String, SecurityPlugin> remainingPlugins = new HashMap<String, SecurityPlugin>(_globalPlugins);
for (Entry<String, SecurityPlugin> hostEntry : _hostPlugins.entrySet())
{
// Create set of global only plugins
SecurityPlugin globalPlugin = remainingPlugins.get(hostEntry.getKey());
if (globalPlugin != null)
{
remainingPlugins.remove(hostEntry.getKey());
}
Result host = checker.allowed(hostEntry.getValue());
if (host == Result.DENIED)
{
// Something vetoed the access, we're done
return false;
}
// host allow overrides global allow, so only check global on abstain or defer
if (host != Result.ALLOWED)
{
if (globalPlugin == null)
{
if (host == Result.DEFER)
{
host = hostEntry.getValue().getDefault();
}
if (host == Result.DENIED)
{
return false;
}
}
else
{
Result global = checker.allowed(globalPlugin);
if (global == Result.DEFER)
{
global = globalPlugin.getDefault();
}
if (global == Result.ABSTAIN && host == Result.DEFER)
{
global = hostEntry.getValue().getDefault();
}
if (global == Result.DENIED)
{
return false;
}
}
}
}
for (SecurityPlugin plugin : remainingPlugins.values())
{
Result remaining = checker.allowed(plugin);
if (remaining == Result.DEFER)
{
remaining = plugin.getDefault();
}
if (remaining == Result.DENIED)
{
return false;
}
}
// getting here means either allowed or abstained from all plugins
return true;
}
public boolean authoriseBind(final Exchange exch, final AMQQueue queue, final AMQShortString routingKey)
{
return checkAllPlugins(new AccessCheck()
{
Result allowed(SecurityPlugin plugin)
{
return plugin.authorise(BIND, EXCHANGE, new ObjectProperties(exch, queue, routingKey));
}
});
}
// TODO not implemented yet, awaiting consensus
public boolean authoriseObject(final String packageName, final String className)
{
return checkAllPlugins(new AccessCheck()
{
Result allowed(SecurityPlugin plugin)
{
ObjectProperties properties = new ObjectProperties();
properties.put(ObjectProperties.Property.PACKAGE, packageName);
properties.put(ObjectProperties.Property.CLASS, className);
return plugin.authorise(ACCESS, OBJECT, properties);
}
});
}
public boolean authoriseMethod(final Operation operation, final String componentName, final String methodName)
{
return checkAllPlugins(new AccessCheck()
{
Result allowed(SecurityPlugin plugin)
{
ObjectProperties properties = new ObjectProperties();
properties.setName(methodName);
if (componentName != null)
{
// Only set the property if there is a component name
properties.put(ObjectProperties.Property.COMPONENT, componentName);
}
return plugin.authorise(operation, METHOD, properties);
}
});
}
public boolean accessVirtualhost(final String vhostname, final SocketAddress remoteAddress)
{
return checkAllPlugins(new AccessCheck()
{
Result allowed(SecurityPlugin plugin)
{
return plugin.access(VIRTUALHOST, remoteAddress);
}
});
}
public boolean authoriseConsume(final AMQQueue queue)
{
return checkAllPlugins(new AccessCheck()
{
Result allowed(SecurityPlugin plugin)
{
return plugin.authorise(CONSUME, QUEUE, new ObjectProperties(queue));
}
});
}
public boolean authoriseConsume(final boolean exclusive, final boolean noAck, final boolean noLocal, final boolean nowait, final AMQQueue queue)
{
return checkAllPlugins(new AccessCheck()
{
Result allowed(SecurityPlugin plugin)
{
return plugin.authorise(CONSUME, QUEUE, new ObjectProperties(exclusive, noAck, noLocal, nowait, queue));
}
});
}
public boolean authoriseCreateExchange(final Boolean autoDelete, final Boolean durable, final AMQShortString exchangeName,
final Boolean internal, final Boolean nowait, final Boolean passive, final AMQShortString exchangeType)
{
return checkAllPlugins(new AccessCheck()
{
Result allowed(SecurityPlugin plugin)
{
return plugin.authorise(CREATE, EXCHANGE, new ObjectProperties(autoDelete, durable, exchangeName,
internal, nowait, passive, exchangeType));
}
});
}
public boolean authoriseCreateQueue(final Boolean autoDelete, final Boolean durable, final Boolean exclusive,
final Boolean nowait, final Boolean passive, final AMQShortString queueName, final String owner)
{
return checkAllPlugins(new AccessCheck()
{
Result allowed(SecurityPlugin plugin)
{
return plugin.authorise(CREATE, QUEUE, new ObjectProperties(autoDelete, durable, exclusive, nowait, passive, queueName, owner));
}
});
}
public boolean authoriseDelete(final AMQQueue queue)
{
return checkAllPlugins(new AccessCheck()
{
Result allowed(SecurityPlugin plugin)
{
return plugin.authorise(DELETE, QUEUE, new ObjectProperties(queue));
}
});
}
public boolean authoriseDelete(final Exchange exchange)
{
return checkAllPlugins(new AccessCheck()
{
Result allowed(SecurityPlugin plugin)
{
return plugin.authorise(DELETE, EXCHANGE, new ObjectProperties(exchange.getName()));
}
});
}
public boolean authorisePublish(final boolean immediate, final String routingKey, final String exchangeName)
{
return checkAllPlugins(new AccessCheck()
{
Result allowed(SecurityPlugin plugin)
{
return plugin.authorise(PUBLISH, EXCHANGE, new ObjectProperties(exchangeName, routingKey, immediate));
}
});
}
public boolean authorisePurge(final AMQQueue queue)
{
return checkAllPlugins(new AccessCheck()
{
Result allowed(SecurityPlugin plugin)
{
return plugin.authorise(PURGE, QUEUE, new ObjectProperties(queue));
}
});
}
public boolean authoriseUnbind(final Exchange exch, final AMQShortString routingKey, final AMQQueue queue)
{
return checkAllPlugins(new AccessCheck()
{
Result allowed(SecurityPlugin plugin)
{
return plugin.authorise(UNBIND, EXCHANGE, new ObjectProperties(exch, queue, routingKey));
}
});
}
}