diff options
Diffstat (limited to 'qpid/cpp/src/qpid/acl')
-rw-r--r-- | qpid/cpp/src/qpid/acl/Acl.cpp | 450 | ||||
-rw-r--r-- | qpid/cpp/src/qpid/acl/Acl.h | 134 | ||||
-rw-r--r-- | qpid/cpp/src/qpid/acl/AclConnectionCounter.cpp | 320 | ||||
-rw-r--r-- | qpid/cpp/src/qpid/acl/AclConnectionCounter.h | 106 | ||||
-rw-r--r-- | qpid/cpp/src/qpid/acl/AclData.cpp | 890 | ||||
-rw-r--r-- | qpid/cpp/src/qpid/acl/AclData.h | 323 | ||||
-rw-r--r-- | qpid/cpp/src/qpid/acl/AclLexer.cpp | 141 | ||||
-rw-r--r-- | qpid/cpp/src/qpid/acl/AclLexer.h | 200 | ||||
-rw-r--r-- | qpid/cpp/src/qpid/acl/AclPlugin.cpp | 98 | ||||
-rw-r--r-- | qpid/cpp/src/qpid/acl/AclReader.cpp | 827 | ||||
-rw-r--r-- | qpid/cpp/src/qpid/acl/AclReader.h | 150 | ||||
-rw-r--r-- | qpid/cpp/src/qpid/acl/AclResourceCounter.cpp | 174 | ||||
-rw-r--r-- | qpid/cpp/src/qpid/acl/AclResourceCounter.h | 78 | ||||
-rw-r--r-- | qpid/cpp/src/qpid/acl/AclTopicMatch.h | 89 | ||||
-rw-r--r-- | qpid/cpp/src/qpid/acl/AclValidator.cpp | 536 | ||||
-rw-r--r-- | qpid/cpp/src/qpid/acl/AclValidator.h | 106 | ||||
-rw-r--r-- | qpid/cpp/src/qpid/acl/management-schema.xml | 85 |
17 files changed, 4707 insertions, 0 deletions
diff --git a/qpid/cpp/src/qpid/acl/Acl.cpp b/qpid/cpp/src/qpid/acl/Acl.cpp new file mode 100644 index 0000000000..bd9482ef41 --- /dev/null +++ b/qpid/cpp/src/qpid/acl/Acl.cpp @@ -0,0 +1,450 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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. + * + */ + +#include "qpid/acl/Acl.h" +#include "qpid/acl/AclConnectionCounter.h" +#include "qpid/acl/AclResourceCounter.h" +#include "qpid/acl/AclData.h" +#include "qpid/acl/AclValidator.h" +#include "qpid/sys/Mutex.h" + +#include "qpid/broker/Broker.h" +#include "qpid/broker/Connection.h" +#include "qpid/Plugin.h" +#include "qpid/Options.h" +#include "qpid/log/Logger.h" +#include "qpid/types/Variant.h" +#include "qmf/org/apache/qpid/acl/ArgsAclLookup.h" +#include "qmf/org/apache/qpid/acl/ArgsAclLookupPublish.h" +#include "qmf/org/apache/qpid/acl/Package.h" +#include "qmf/org/apache/qpid/acl/EventAllow.h" +#include "qmf/org/apache/qpid/acl/EventConnectionDeny.h" +#include "qmf/org/apache/qpid/acl/EventQueueQuotaDeny.h" +#include "qmf/org/apache/qpid/acl/EventDeny.h" +#include "qmf/org/apache/qpid/acl/EventFileLoaded.h" +#include "qmf/org/apache/qpid/acl/EventFileLoadFailed.h" + +#include <map> + +#include <boost/shared_ptr.hpp> + +using namespace std; +using namespace qpid::acl; +using qpid::broker::Broker; +using namespace qpid::sys; +using qpid::management::ManagementAgent; +using qpid::management::ManagementObject; +using qpid::management::Manageable; +using qpid::management::Args; +namespace _qmf = qmf::org::apache::qpid::acl; + +Acl::Acl (AclValues& av, Broker& b): aclValues(av), broker(&b), transferAcl(false), + connectionCounter(new ConnectionCounter(*this, aclValues.aclMaxConnectPerUser, aclValues.aclMaxConnectPerIp, aclValues.aclMaxConnectTotal)), + resourceCounter(new ResourceCounter(*this, aclValues.aclMaxQueuesPerUser)),userRules(false) +{ + + if (aclValues.aclMaxConnectPerUser > AclData::getConnectMaxSpec()) + throw Exception("--connection-limit-per-user switch cannot be larger than " + AclData::getMaxConnectSpecStr()); + if (aclValues.aclMaxConnectPerIp > AclData::getConnectMaxSpec()) + throw Exception("--connection-limit-per-ip switch cannot be larger than " + AclData::getMaxConnectSpecStr()); + if (aclValues.aclMaxConnectTotal > AclData::getConnectMaxSpec()) + throw Exception("--max-connections switch cannot be larger than " + AclData::getMaxConnectSpecStr()); + if (aclValues.aclMaxQueuesPerUser > AclData::getConnectMaxSpec()) + throw Exception("--max-queues-per-user switch cannot be larger than " + AclData::getMaxQueueSpecStr()); + + agent = broker->getManagementAgent(); + + if (agent != 0) { + _qmf::Package packageInit(agent); + mgmtObject = _qmf::Acl::shared_ptr(new _qmf::Acl (agent, this, broker)); + agent->addObject (mgmtObject); + mgmtObject->set_maxConnections(aclValues.aclMaxConnectTotal); + mgmtObject->set_maxConnectionsPerIp(aclValues.aclMaxConnectPerIp); + mgmtObject->set_maxConnectionsPerUser(aclValues.aclMaxConnectPerUser); + mgmtObject->set_maxQueuesPerUser(aclValues.aclMaxQueuesPerUser); + } + + if (!aclValues.aclFile.empty()) { + std::string errorString; + if (!readAclFile(errorString)){ + if (mgmtObject!=0) mgmtObject->set_enforcingAcl(0); + throw Exception("Could not read ACL file " + errorString); + } + } else { + loadEmptyAclRuleset(); + QPID_LOG(debug, "ACL loaded empty rule set"); + } + broker->getConnectionObservers().add(connectionCounter); + QPID_LOG(info, "ACL Plugin loaded"); + if (mgmtObject!=0) mgmtObject->set_enforcingAcl(1); +} + + +void Acl::reportConnectLimit(const std::string user, const std::string addr) +{ + if (mgmtObject!=0) + mgmtObject->inc_connectionDenyCount(); + + if (agent != 0) { + agent->raiseEvent(_qmf::EventConnectionDeny(user, addr)); + } +} + + +void Acl::reportQueueLimit(const std::string user, const std::string queueName) +{ + if (mgmtObject!=0) + mgmtObject->inc_queueQuotaDenyCount(); + + if (agent != 0) { + agent->raiseEvent(_qmf::EventQueueQuotaDeny(user, queueName)); + } +} + + +bool Acl::authorise( + const std::string& id, + const Action& action, + const ObjectType& objType, + const std::string& name, + std::map<Property, std::string>* params) +{ + boost::shared_ptr<AclData> dataLocal; + { + Mutex::ScopedLock locker(dataLock); + dataLocal = data; //rcu copy + } + + // add real ACL check here... + AclResult aclreslt = dataLocal->lookup(id,action,objType,name,params); + + + return result(aclreslt, id, action, objType, name); +} + +bool Acl::authorise( + const std::string& id, + const Action& action, + const ObjectType& objType, + const std::string& ExchangeName, + const std::string& RoutingKey) +{ + boost::shared_ptr<AclData> dataLocal; + { + Mutex::ScopedLock locker(dataLock); + dataLocal = data; //rcu copy + } + + // only use dataLocal here... + AclResult aclreslt = dataLocal->lookup(id,action,objType,ExchangeName,RoutingKey); + + return result(aclreslt, id, action, objType, ExchangeName); +} + + +bool Acl::approveConnection(const qpid::broker::Connection& conn) +{ + const std::string& userName(conn.getUserId()); + uint16_t connectionLimit(0); + + boost::shared_ptr<AclData> dataLocal; + { + Mutex::ScopedLock locker(dataLock); + dataLocal = data; //rcu copy + } + + (void) dataLocal->getConnQuotaForUser(userName, &connectionLimit); + + return connectionCounter->approveConnection( + conn, + userName, + dataLocal->enforcingConnectionQuotas(), + connectionLimit, + dataLocal); +} + +bool Acl::approveCreateQueue(const std::string& userId, const std::string& queueName) +{ +// return resourceCounter->approveCreateQueue(userId, queueName); + uint16_t queueLimit(0); + + boost::shared_ptr<AclData> dataLocal; + { + Mutex::ScopedLock locker(dataLock); + dataLocal = data; //rcu copy + } + + (void) dataLocal->getQueueQuotaForUser(userId, &queueLimit); + + return resourceCounter->approveCreateQueue(userId, queueName, dataLocal->enforcingQueueQuotas(), queueLimit); +} + + +void Acl::recordDestroyQueue(const std::string& queueName) +{ + resourceCounter->recordDestroyQueue(queueName); +} + + +bool Acl::result( + const AclResult& aclreslt, + const std::string& id, + const Action& action, + const ObjectType& objType, + const std::string& name) +{ + bool result(false); + + switch (aclreslt) + { + case ALLOWLOG: + QPID_LOG(info, "ACL Allow id:" << id + << " action:" << AclHelper::getActionStr(action) + << " ObjectType:" << AclHelper::getObjectTypeStr(objType) + << " Name:" << name ); + if (agent != 0) { + agent->raiseEvent(_qmf::EventAllow(id, AclHelper::getActionStr(action), + AclHelper::getObjectTypeStr(objType), + name, types::Variant::Map())); + } + // FALLTHROUGH + case ALLOW: + result = true; + break; + + case DENYLOG: + QPID_LOG(info, "ACL Deny id:" << id + << " action:" << AclHelper::getActionStr(action) + << " ObjectType:" << AclHelper::getObjectTypeStr(objType) + << " Name:" << name); + if (agent != 0) { + agent->raiseEvent(_qmf::EventDeny(id, AclHelper::getActionStr(action), + AclHelper::getObjectTypeStr(objType), + name, types::Variant::Map())); + } + // FALLTHROUGH + case DENY: + if (mgmtObject!=0) + mgmtObject->inc_aclDenyCount(); + result = false; + break; + + default: + assert (false); + } + + return result; +} + +bool Acl::readAclFile(std::string& errorText) +{ + // only set transferAcl = true if a rule implies the use of ACL on transfer, else keep false for performance reasons. + return readAclFile(aclValues.aclFile, errorText); +} + +bool Acl::readAclFile(std::string& aclFile, std::string& errorText) { + boost::shared_ptr<AclData> d(new AclData); + AclReader ar(aclValues.aclMaxConnectPerUser, aclValues.aclMaxQueuesPerUser); + if (ar.read(aclFile, d)){ + if (agent != 0) { + agent->raiseEvent(_qmf::EventFileLoadFailed("", ar.getError())); + } + errorText = ar.getError(); + QPID_LOG(error,ar.getError()); + return false; + } + + AclValidator validator; + validator.validate(d); + + { + Mutex::ScopedLock locker(dataLock); + data = d; + } + transferAcl = data->transferAcl; // any transfer ACL + userRules = true; // rules in force came from an ACL file + + if (data->transferAcl){ + QPID_LOG(debug,"ACL: Transfer ACL is Enabled!"); + } + + if (data->enforcingConnectionQuotas()){ + QPID_LOG(debug, "ACL: Connection quotas are Enabled."); + } + + if (data->enforcingQueueQuotas()){ + QPID_LOG(debug, "ACL: Queue quotas are Enabled."); + } + + QPID_LOG(debug, "ACL: Default connection mode : " + << AclHelper::getAclResultStr(d->connectionMode())); + + data->aclSource = aclFile; + if (mgmtObject!=0){ + mgmtObject->set_transferAcl(transferAcl?1:0); + mgmtObject->set_policyFile(aclFile); + mgmtObject->set_lastAclLoad(Duration::FromEpoch()); + if (agent != 0) { + agent->raiseEvent(_qmf::EventFileLoaded("")); + } + } + return true; +} + +// +// loadEmptyAclRuleset() +// +// No ACL file is specified but ACL should run. +// Create a ruleset as if only "ACL ALLOW ALL ALL" was in a file +// +void Acl::loadEmptyAclRuleset() { + boost::shared_ptr<AclData> d(new AclData); + d->decisionMode = ALLOW; + d->aclSource = ""; + d->connectionDecisionMode = ALLOW; + { + Mutex::ScopedLock locker(dataLock); + data = d; + } + if (mgmtObject!=0){ + mgmtObject->set_transferAcl(transferAcl?1:0); + mgmtObject->set_policyFile(""); + mgmtObject->set_lastAclLoad(Duration::FromEpoch()); + if (agent != 0) { + agent->raiseEvent(_qmf::EventFileLoaded("")); + } + } +} + +// +// management lookup function performs general query on acl engine +// +Manageable::status_t Acl::lookup(qpid::management::Args& args, std::string& text) +{ + _qmf::ArgsAclLookup& ioArgs = (_qmf::ArgsAclLookup&) args; + Manageable::status_t result(STATUS_USER); + + try { + ObjectType objType = AclHelper::getObjectType(ioArgs.i_object); + Action action = AclHelper::getAction( ioArgs.i_action); + std::map<Property, std::string> propertyMap; + for (::qpid::types::Variant::Map::const_iterator + iMapIter = ioArgs.i_propertyMap.begin(); + iMapIter != ioArgs.i_propertyMap.end(); + iMapIter++) + { + Property property = AclHelper::getProperty(iMapIter->first); + propertyMap.insert(make_pair(property, iMapIter->second)); + } + + boost::shared_ptr<AclData> dataLocal; + { + Mutex::ScopedLock locker(dataLock); + dataLocal = data; //rcu copy + } + AclResult aclResult; + // CREATE CONNECTION does not use lookup() + if (action == ACT_CREATE && objType == OBJ_CONNECTION) { + std::string host = propertyMap[acl::PROP_HOST]; + std::string logString; + aclResult = dataLocal->isAllowedConnection( + ioArgs.i_userId, + host, + logString); + } else { + aclResult = dataLocal->lookup( + ioArgs.i_userId, + action, + objType, + ioArgs.i_objectName, + &propertyMap); + } + ioArgs.o_result = AclHelper::getAclResultStr(aclResult); + result = STATUS_OK; + + } catch (const std::exception& e) { + std::ostringstream oss; + oss << "AclLookup invalid name : " << e.what(); + ioArgs.o_result = oss.str(); + text = oss.str(); + } + + return result; +} + + +// +// management lookupPublish function performs fastpath +// PUBLISH EXCHANGE query on acl engine +// +Manageable::status_t Acl::lookupPublish(qpid::management::Args& args, std::string& /*text*/) +{ + _qmf::ArgsAclLookupPublish& ioArgs = (_qmf::ArgsAclLookupPublish&) args; + boost::shared_ptr<AclData> dataLocal; + { + Mutex::ScopedLock locker(dataLock); + dataLocal = data; //rcu copy + } + AclResult aclResult = dataLocal->lookup( + ioArgs.i_userId, + ACT_PUBLISH, + OBJ_EXCHANGE, + ioArgs.i_exchangeName, + ioArgs.i_routingKey); + + ioArgs.o_result = AclHelper::getAclResultStr(aclResult); + + return STATUS_OK; +} + + +Acl::~Acl(){ + broker->getConnectionObservers().remove(connectionCounter); +} + +ManagementObject::shared_ptr Acl::GetManagementObject(void) const +{ + return mgmtObject; +} + +Manageable::status_t Acl::ManagementMethod (uint32_t methodId, Args& args, string& text) +{ + Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD; + QPID_LOG (debug, "ACL: Queue::ManagementMethod [id=" << methodId << "]"); + + switch (methodId) + { + case _qmf::Acl::METHOD_RELOADACLFILE : + readAclFile(text); + if (text.empty()) + status = Manageable::STATUS_OK; + else + status = Manageable::STATUS_USER; + break; + + case _qmf::Acl::METHOD_LOOKUP : + status = lookup(args, text); + break; + + case _qmf::Acl::METHOD_LOOKUPPUBLISH : + status = lookupPublish(args, text); + break; + } + + return status; +} diff --git a/qpid/cpp/src/qpid/acl/Acl.h b/qpid/cpp/src/qpid/acl/Acl.h new file mode 100644 index 0000000000..df2fb66c82 --- /dev/null +++ b/qpid/cpp/src/qpid/acl/Acl.h @@ -0,0 +1,134 @@ +#ifndef QPID_ACL_ACL_H +#define QPID_ACL_ACL_H + + +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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. + * + */ + + + +#include "qpid/acl/AclReader.h" +#include "qpid/AclHost.h" +#include "qpid/RefCounted.h" +#include "qpid/broker/AclModule.h" +#include "qpid/management/Manageable.h" +#include "qpid/management/ManagementAgent.h" +#include "qmf/org/apache/qpid/acl/Acl.h" +#include "qpid/sys/Mutex.h" + +#include <boost/shared_ptr.hpp> +#include <map> +#include <string> + + +namespace qpid { +namespace broker { +class Broker; +class Connection; +} + +namespace acl { +class ConnectionCounter; +class ResourceCounter; + +struct AclValues { + std::string aclFile; + uint16_t aclMaxConnectPerUser; + uint16_t aclMaxConnectPerIp; + uint16_t aclMaxConnectTotal; + uint16_t aclMaxQueuesPerUser; +}; + + +class Acl : public broker::AclModule, public RefCounted, public management::Manageable +{ + +private: + acl::AclValues aclValues; + broker::Broker* broker; + bool transferAcl; + boost::shared_ptr<AclData> data; + qmf::org::apache::qpid::acl::Acl::shared_ptr mgmtObject; + qpid::management::ManagementAgent* agent; + mutable qpid::sys::Mutex dataLock; + boost::shared_ptr<ConnectionCounter> connectionCounter; + boost::shared_ptr<ResourceCounter> resourceCounter; + bool userRules; + +public: + Acl (AclValues& av, broker::Broker& b); + + /** reportConnectLimit + * issue management counts and alerts for denied connections + */ + void reportConnectLimit(const std::string user, const std::string addr); + void reportQueueLimit(const std::string user, const std::string queueName); + + inline virtual bool doTransferAcl() { + return transferAcl; + }; + + inline virtual uint16_t getMaxConnectTotal() { + return aclValues.aclMaxConnectTotal; + }; + + inline virtual bool userAclRules() { + return userRules; + }; + +// create specilied authorise methods for cases that need faster matching as needed. + virtual bool authorise( + const std::string& id, + const Action& action, + const ObjectType& objType, + const std::string& name, + std::map<Property, std::string>* params=0); + + virtual bool authorise( + const std::string& id, + const Action& action, + const ObjectType& objType, + const std::string& ExchangeName, + const std::string& RoutingKey); + + // Resource quota tracking + virtual bool approveConnection(const broker::Connection& connection); + virtual bool approveCreateQueue(const std::string& userId, const std::string& queueName); + virtual void recordDestroyQueue(const std::string& queueName); + + virtual ~Acl(); +private: + bool result( + const AclResult& aclreslt, + const std::string& id, + const Action& action, + const ObjectType& objType, + const std::string& name); + bool readAclFile(std::string& errorText); + bool readAclFile(std::string& aclFile, std::string& errorText); + void loadEmptyAclRuleset(); + Manageable::status_t lookup (management::Args& args, std::string& text); + Manageable::status_t lookupPublish(management::Args& args, std::string& text); + virtual qpid::management::ManagementObject::shared_ptr GetManagementObject(void) const; + virtual management::Manageable::status_t ManagementMethod (uint32_t methodId, management::Args& args, std::string& text); + +}; + +}} // namespace qpid::acl + +#endif // QPID_ACL_ACL_H diff --git a/qpid/cpp/src/qpid/acl/AclConnectionCounter.cpp b/qpid/cpp/src/qpid/acl/AclConnectionCounter.cpp new file mode 100644 index 0000000000..ca3da50088 --- /dev/null +++ b/qpid/cpp/src/qpid/acl/AclConnectionCounter.cpp @@ -0,0 +1,320 @@ +/* + * + * 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. + * + */ + +#include "AclConnectionCounter.h" +#include "Acl.h" +#include "qpid/broker/Connection.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Mutex.h" +#include "qpid/sys/SocketAddress.h" +#include <assert.h> +#include <sstream> + +using namespace qpid::sys; + +namespace qpid { +namespace acl { + +// +// This module instantiates a broker::ConnectionObserver and limits client +// connections by counting connections per user name, per client IP address +// and per total connection count. +// + + +// +// +// +ConnectionCounter::ConnectionCounter(Acl& a, uint16_t nl, uint16_t hl, uint16_t tl) : + acl(a), nameLimit(nl), hostLimit(hl), totalLimit(tl), totalCurrentConnections(0) {} + +ConnectionCounter::~ConnectionCounter() {} + + +// +// limitApproveLH +// +// Connection creation approver. Return true only if user is under limit. +// Called with lock held. +// +bool ConnectionCounter::limitApproveLH( + connectCountsMap_t& theMap, + const std::string& theName, + uint16_t theLimit, + bool emitLog) { + + bool result(true); + if (theLimit > 0) { + uint16_t count; + connectCountsMap_t::iterator eRef = theMap.find(theName); + if (eRef != theMap.end()) { + count = (uint16_t)(*eRef).second; + result = count <= theLimit; + } else { + // Not found + count = 0; + } + if (emitLog) { + QPID_LOG(trace, "ACL ConnectionApprover IP=" << theName + << " limit=" << theLimit + << " curValue=" << count + << " result=" << (result ? "allow" : "deny")); + } + } + return result; +} + + +// +// countConnectionLH +// +// Increment the name's count in map and return an optional comparison +// against a connection limit. +// Called with dataLock already taken. +// +bool ConnectionCounter::countConnectionLH( + connectCountsMap_t& theMap, + const std::string& theName, + uint16_t theLimit, + bool emitLog, + bool enforceLimit) { + + bool result(true); + uint16_t count(0); + connectCountsMap_t::iterator eRef = theMap.find(theName); + if (eRef != theMap.end()) { + count = (uint16_t)(*eRef).second + 1; + (*eRef).second = count; + } else { + theMap[theName] = count = 1; + } + if (enforceLimit) { + result = count <= theLimit; + } + if (emitLog) { + QPID_LOG(trace, "ACL ConnectionApprover user=" << theName + << " limit=" << theLimit + << " curValue=" << count + << " result=" << (result ? "allow" : "deny")); + } + return result; +} + + +// +// releaseLH +// +// Decrement the name's count in map. +// called with dataLock already taken +// +void ConnectionCounter::releaseLH( + connectCountsMap_t& theMap, const std::string& theName) { + + connectCountsMap_t::iterator eRef = theMap.find(theName); + if (eRef != theMap.end()) { + uint16_t count = (uint16_t) (*eRef).second; + assert (count > 0); + if (1 == count) { + theMap.erase (eRef); + } else { + (*eRef).second = count - 1; + } + } else { + // User had no connections. + // Connections denied by ACL never get users added + //QPID_LOG(notice, "ACL ConnectionCounter Connection for '" << theName + // << "' not found in connection count pool"); + } +} + + +// +// connection - called during Connection's constructor +// +void ConnectionCounter::connection(broker::Connection& connection) { + QPID_LOG(trace, "ACL ConnectionCounter new connection: " << connection.getMgmtId()); + + const std::string& hostName(getClientHost(connection.getMgmtId())); + + Mutex::ScopedLock locker(dataLock); + + // Total connections goes up + totalCurrentConnections += 1; + + // Record the fact that this connection exists + connectProgressMap[connection.getMgmtId()] = C_CREATED; + + // Count the connection from this host. + (void) countConnectionLH(connectByHostMap, hostName, hostLimit, false, false); +} + + +// +// closed - called during Connection's destructor +// +void ConnectionCounter::closed(broker::Connection& connection) { + QPID_LOG(trace, "ACL ConnectionCounter closed: " << connection.getMgmtId() + << ", userId:" << connection.getUserId()); + + Mutex::ScopedLock locker(dataLock); + + connectCountsMap_t::iterator eRef = connectProgressMap.find(connection.getMgmtId()); + if (eRef != connectProgressMap.end()) { + if ((*eRef).second == C_OPENED){ + // Normal case: connection was created and opened. + // Decrement user in-use counts + releaseLH(connectByNameMap, + connection.getUserId()); + } else { + // Connection was created but not opened. + // Don't decrement user count. + } + + // Decrement host in-use count. + releaseLH(connectByHostMap, + getClientHost(connection.getMgmtId())); + + // destroy connection progress indicator + connectProgressMap.erase(eRef); + + } else { + // connection not found in progress map + QPID_LOG(notice, "ACL ConnectionCounter closed info for '" << connection.getMgmtId() + << "' not found in connection state pool"); + } + + // total connections + totalCurrentConnections -= 1; +} + + +// +// approveConnection +// check total connections, connections from IP, connections by user and +// disallow if over any limit +// +bool ConnectionCounter::approveConnection( + const broker::Connection& connection, + const std::string& userName, + bool enforcingConnectionQuotas, + uint16_t connectionUserQuota, + boost::shared_ptr<AclData> localdata) +{ + const std::string& hostName(getClientHost(connection.getMgmtId())); + + Mutex::ScopedLock locker(dataLock); + + // Bump state from CREATED to OPENED + (void) countConnectionLH(connectProgressMap, connection.getMgmtId(), + C_OPENED, false, false); + + // Run global black/white list check + sys::SocketAddress sa(hostName, ""); + bool okByHostList(true); + std::string hostLimitText; + if (sa.isIp()) { + AclResult result = localdata->isAllowedConnection(userName, hostName, hostLimitText); + okByHostList = AclHelper::resultAllows(result); + if (okByHostList) { + QPID_LOG(trace, "ACL: ConnectionApprover host list " << hostLimitText); + } + } + + // Approve total connections + bool okTotal = true; + if (totalLimit > 0) { + okTotal = totalCurrentConnections <= totalLimit; + QPID_LOG(trace, "ACL ConnectionApprover totalLimit=" << totalLimit + << " curValue=" << totalCurrentConnections + << " result=" << (okTotal ? "allow" : "deny")); + } + + // Approve by IP host connections + bool okByIP = limitApproveLH(connectByHostMap, hostName, hostLimit, true); + + // Count and Approve the connection by the user + bool okByUser = countConnectionLH(connectByNameMap, userName, + connectionUserQuota, true, + enforcingConnectionQuotas); + + // Emit separate log for each disapproval + if (!okByHostList) { + QPID_LOG(error, "ACL: ConnectionApprover host list " << hostLimitText + << " Connection refused."); + } + if (!okTotal) { + QPID_LOG(error, "Client max total connection count limit of " << totalLimit + << " exceeded by '" + << connection.getMgmtId() << "', user: '" + << userName << "'. Connection refused"); + } + if (!okByIP) { + QPID_LOG(error, "Client max per-host connection count limit of " + << hostLimit << " exceeded by '" + << connection.getMgmtId() << "', user: '" + << userName << "'. Connection refused."); + } + if (!okByUser) { + QPID_LOG(error, "Client max per-user connection count limit of " + << connectionUserQuota << " exceeded by '" + << connection.getMgmtId() << "', user: '" + << userName << "'. Connection refused."); + } + + // Count/Event once for each disapproval + bool result = okByHostList && okTotal && okByIP && okByUser; + if (!result) { + acl.reportConnectLimit(userName, hostName); + } + + return result; +} + +// +// getClientIp - given a connection's mgmtId return the client host part. +// +// TODO: Ideally this would be a method of the connection itself. +// TODO: Verify it works with rdma connection names. +// +std::string ConnectionCounter::getClientHost(const std::string mgmtId) +{ + size_t hyphen = mgmtId.find('-'); + if (std::string::npos != hyphen) { + size_t colon = mgmtId.find_last_of(':'); + if (std::string::npos != colon) { + // trailing colon found + std::string tmp = mgmtId.substr(hyphen+1, colon - hyphen - 1); + // undecorate ipv6 + if (tmp.length() >= 3 && tmp.find("[") == 0 && tmp.rfind("]") == tmp.length()-1) + tmp = tmp.substr(1, tmp.length()-2); + return tmp; + + } else { + // colon not found - use everything after hyphen + return mgmtId.substr(hyphen+1); + } + } + + // no hyphen found - use whole string + return mgmtId; +} + +}} // namespace qpid::ha diff --git a/qpid/cpp/src/qpid/acl/AclConnectionCounter.h b/qpid/cpp/src/qpid/acl/AclConnectionCounter.h new file mode 100644 index 0000000000..3683b573ff --- /dev/null +++ b/qpid/cpp/src/qpid/acl/AclConnectionCounter.h @@ -0,0 +1,106 @@ +#ifndef QPID_ACL_CONNECTIONCOUNTER_H +#define QPID_ACL_CONNECTIONCOUNTER_H + +/* + * + * 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. + * + */ + +#include "qpid/broker/ConnectionObserver.h" +#include "qpid/sys/Mutex.h" +#include "qpid/acl/AclData.h" + +#include <map> + +namespace qpid { + +namespace broker { +class Connection; +} + +namespace acl { +class Acl; + + /** + * Terminate client connections when a user tries to create 'too many'. + * Terminate hostIp connections when an IP host tries to create 'too many'. + */ +class ConnectionCounter : public broker::ConnectionObserver +{ +private: + typedef std::map<std::string, uint32_t> connectCountsMap_t; + enum CONNECTION_PROGRESS { C_CREATED=1, C_OPENED=2 }; + + Acl& acl; + uint16_t nameLimit; + uint16_t hostLimit; + uint16_t totalLimit; + uint16_t totalCurrentConnections; + qpid::sys::Mutex dataLock; + + /** Records per-connection state */ + connectCountsMap_t connectProgressMap; + + /** Records per-username counts */ + connectCountsMap_t connectByNameMap; + + /** Records per-host counts */ + connectCountsMap_t connectByHostMap; + + /** Given a connection's management ID, return the client host name */ + std::string getClientHost(const std::string mgmtId); + + /** Return approval for proposed connection */ + bool limitApproveLH(connectCountsMap_t& theMap, + const std::string& theName, + uint16_t theLimit, + bool emitLog); + + /** Record a connection. + * @return indication if user/host is over its limit */ + bool countConnectionLH(connectCountsMap_t& theMap, + const std::string& theName, + uint16_t theLimit, + bool emitLog, + bool enforceLimit); + + /** Release a connection */ + void releaseLH(connectCountsMap_t& theMap, + const std::string& theName); + +public: + ConnectionCounter(Acl& acl, uint16_t nl, uint16_t hl, uint16_t tl); + ~ConnectionCounter(); + + // ConnectionObserver interface + void connection(broker::Connection& connection); + void closed(broker::Connection& connection); + + // Connection counting + bool approveConnection(const broker::Connection& conn, + const std::string& userName, + bool enforcingConnectionQuotas, + uint16_t connectionLimit, + boost::shared_ptr<AclData> localdata + ); +}; + +}} // namespace qpid::ha + +#endif /*!QPID_ACL_CONNECTIONCOUNTER_H*/ diff --git a/qpid/cpp/src/qpid/acl/AclData.cpp b/qpid/cpp/src/qpid/acl/AclData.cpp new file mode 100644 index 0000000000..a629e44d60 --- /dev/null +++ b/qpid/cpp/src/qpid/acl/AclData.cpp @@ -0,0 +1,890 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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. + * + */ + +#include "qpid/acl/AclData.h" +#include "qpid/acl/AclValidator.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/IntegerTypes.h" +#include <boost/lexical_cast.hpp> +#include <boost/shared_ptr.hpp> +#include <sstream> +#include <iomanip> + +namespace qpid { +namespace acl { + +// +// Instantiate the keyword strings +// +const std::string AclData::ACL_KEYWORD_USER_SUBST = "${user}"; +const std::string AclData::ACL_KEYWORD_DOMAIN_SUBST = "${domain}"; +const std::string AclData::ACL_KEYWORD_USERDOMAIN_SUBST = "${userdomain}"; +const std::string AclData::ACL_KEYWORD_ALL = "all"; +const std::string AclData::ACL_KEYWORD_ACL = "acl"; +const std::string AclData::ACL_KEYWORD_GROUP = "group"; +const std::string AclData::ACL_KEYWORD_QUOTA = "quota"; +const std::string AclData::ACL_KEYWORD_QUOTA_CONNECTIONS = "connections"; +const std::string AclData::ACL_KEYWORD_QUOTA_QUEUES = "queues"; +const char AclData::ACL_SYMBOL_WILDCARD = '*'; +const std::string AclData::ACL_KEYWORD_WILDCARD = "*"; +const char AclData::ACL_SYMBOL_LINE_CONTINUATION = '\\'; +const std::string AclData::ACL_KEYWORD_DEFAULT_EXCHANGE = "amq.default"; + +// +// constructor +// +AclData::AclData(): + decisionMode(qpid::acl::DENY), + transferAcl(false), + aclSource("UNKNOWN"), + connectionDecisionMode(qpid::acl::ALLOW), + connQuotaRuleSettings(new quotaRuleSet), + queueQuotaRuleSettings(new quotaRuleSet), + connBWHostsGlobalRules(new bwHostRuleSet), + connBWHostsUserRules(new bwHostUserRuleMap) +{ + for (unsigned int cnt=0; cnt< qpid::acl::ACTIONSIZE; cnt++) { + actionList[cnt]=0; + } +} + + +// +// clear +// +void AclData::clear () +{ + for (unsigned int cnt=0; cnt< qpid::acl::ACTIONSIZE; cnt++) { + if (actionList[cnt]) { + for (unsigned int cnt1=0; cnt1< qpid::acl::OBJECTSIZE; cnt1++) { + delete actionList[cnt][cnt1]; + } + } + delete[] actionList[cnt]; + } + transferAcl = false; + connectionDecisionMode = qpid::acl::ALLOW; + connQuotaRuleSettings->clear(); + queueQuotaRuleSettings->clear(); + connBWHostsGlobalRules->clear(); + connBWHostsUserRules->clear(); +} + +void AclData::printDecisionRules(int userFieldWidth) { + AclValidator validator; + QPID_LOG(trace, "ACL: Decision rule cross reference"); + for (int act=0; act<acl::ACTIONSIZE; act++) { + acl::Action action = acl::Action(act); + for (int obj=0; obj<acl::OBJECTSIZE; obj++) { + acl::ObjectType object = acl::ObjectType(obj); + if (actionList[act] != NULL && actionList[act][obj] != NULL) { + for (actObjItr aoitr = actionList[act][obj]->begin(); + aoitr != actionList[act][obj]->end(); + aoitr++) { + std::string user = (*aoitr).first; + ruleSetItr rsitr = (*aoitr).second.end(); + for (size_t rCnt=0; rCnt < (*aoitr).second.size(); rCnt++) { + rsitr--; + std::vector<int> candidates; + validator.findPossibleLookupMatch( + action, object, rsitr->props, candidates); + std::stringstream ss; + std::string sep(""); + for (std::vector<int>::const_iterator + itr = candidates.begin(); itr != candidates.end(); itr++) { + ss << sep << *itr; + sep = ","; + } + QPID_LOG(trace, "ACL: User: " + << std::setfill(' ') << std::setw(userFieldWidth +1) << std::left + << user << " " + << std::setfill(' ') << std::setw(acl::ACTION_STR_WIDTH +1) << std::left + << AclHelper::getActionStr(action) + << std::setfill(' ') << std::setw(acl::OBJECTTYPE_STR_WIDTH) << std::left + << AclHelper::getObjectTypeStr(object) + << " Rule: " + << rsitr->toString() << " may match Lookups : (" + << ss.str() << ")"); + } + } + } else { + // no rules for action/object + } + } + } +} + +// +// matchProp +// +// Compare a rule's property name with a lookup name, +// The rule's name may contain a trailing '*' to specify a wildcard match. +// +bool AclData::matchProp(const std::string& ruleStr, + const std::string& lookupStr) +{ + // allow wildcard on the end of rule strings... + if (ruleStr.data()[ruleStr.size()-1]==ACL_SYMBOL_WILDCARD) { + return ruleStr.compare(0, + ruleStr.size()-1, + lookupStr, + 0, + ruleStr.size()-1 ) == 0; + } else { + return ruleStr.compare(lookupStr) == 0; + } +} + + +// +// lookupMatchRule +// +// Check a single rule and if it's a match return the decision +// +bool AclData::lookupMatchRule( + const ruleSetItr& rsItr, + const std::string& id, + const std::string& name, + const std::map<Property, std::string>* params, + AclResult& aclresult) +{ + QPID_LOG(debug, "ACL: checking rule " << rsItr->toString()); + + bool match = true; + bool limitChecked = true; + + // Iterate this rule's properties. A 'match' is true when + // all of the rule's properties are found to be satisfied + // in the lookup param list. The lookup may specify things + // (they usually do) that are not in the rule properties but + // these things don't interfere with the rule match. + + for (specPropertyMapItr rulePropMapItr = rsItr->props.begin(); + (rulePropMapItr != rsItr->props.end()) && match; + rulePropMapItr++) { + // The rule property map's NAME property is given in + // the calling args and not in the param map. + if (rulePropMapItr->first == acl::SPECPROP_NAME) + { + // substitute user name into object name + bool result; + if (rsItr->ruleHasUserSub[PROP_NAME]) { + std::string sName(rulePropMapItr->second); + substituteUserId(sName, id); + result = matchProp(sName, name); + } else { + result = matchProp(rulePropMapItr->second, name); + } + + if (result) { + QPID_LOG(debug, "ACL: lookup name '" << name + << "' matched with rule name '" + << rulePropMapItr->second << "'"); + } else { + match = false; + QPID_LOG(debug, "ACL: lookup name '" << name + << "' didn't match with rule name '" + << rulePropMapItr->second << "'"); + } + } else { + if (params) { + // The rule's property map non-NAME properties + // found in the lookup's params list. + // In some cases the param's index is not the same + // as rule's index. + propertyMapItr lookupParamItr; + switch (rulePropMapItr->first) { + case acl::SPECPROP_MAXPAGESLOWERLIMIT: + case acl::SPECPROP_MAXPAGESUPPERLIMIT: + lookupParamItr = params->find(PROP_MAXPAGES); + break; + + case acl::SPECPROP_MAXPAGEFACTORLOWERLIMIT: + case acl::SPECPROP_MAXPAGEFACTORUPPERLIMIT: + lookupParamItr = params->find(PROP_MAXPAGEFACTOR); + break; + + case acl::SPECPROP_MAXQUEUECOUNTUPPERLIMIT: + case acl::SPECPROP_MAXQUEUECOUNTLOWERLIMIT: + lookupParamItr = params->find(PROP_MAXQUEUECOUNT); + break; + + case acl::SPECPROP_MAXQUEUESIZEUPPERLIMIT: + case acl::SPECPROP_MAXQUEUESIZELOWERLIMIT: + lookupParamItr = params->find(PROP_MAXQUEUESIZE); + break; + + case acl::SPECPROP_MAXFILECOUNTUPPERLIMIT: + case acl::SPECPROP_MAXFILECOUNTLOWERLIMIT: + lookupParamItr = params->find(PROP_MAXFILECOUNT); + break; + + case acl::SPECPROP_MAXFILESIZEUPPERLIMIT: + case acl::SPECPROP_MAXFILESIZELOWERLIMIT: + lookupParamItr = params->find(PROP_MAXFILESIZE); + break; + + default: + lookupParamItr = params->find((Property)rulePropMapItr->first); + break; + }; + + if (lookupParamItr == params->end()) { + // Now the rule has a specified property + // that does not exist in the caller's + // lookup params list. + // This rule does not match. + match = false; + QPID_LOG(debug, "ACL: lookup parameter map doesn't contain the rule property '" + << AclHelper::getPropertyStr(rulePropMapItr->first) << "'"); + } else { + // Now account for the business of rules + // whose property indexes are mismatched. + switch (rulePropMapItr->first) { + case acl::SPECPROP_MAXQUEUECOUNTUPPERLIMIT: + case acl::SPECPROP_MAXQUEUESIZEUPPERLIMIT: + case acl::SPECPROP_MAXFILECOUNTUPPERLIMIT: + case acl::SPECPROP_MAXFILESIZEUPPERLIMIT: + case acl::SPECPROP_MAXPAGESUPPERLIMIT: + case acl::SPECPROP_MAXPAGEFACTORUPPERLIMIT: + limitChecked &= + compareInt( + rulePropMapItr->first, + boost::lexical_cast<std::string>(rulePropMapItr->second), + boost::lexical_cast<std::string>(lookupParamItr->second), + true); + break; + + case acl::SPECPROP_MAXQUEUECOUNTLOWERLIMIT: + case acl::SPECPROP_MAXQUEUESIZELOWERLIMIT: + case acl::SPECPROP_MAXFILECOUNTLOWERLIMIT: + case acl::SPECPROP_MAXFILESIZELOWERLIMIT: + case acl::SPECPROP_MAXPAGESLOWERLIMIT: + case acl::SPECPROP_MAXPAGEFACTORLOWERLIMIT: + limitChecked &= + compareInt( + rulePropMapItr->first, + boost::lexical_cast<std::string>(rulePropMapItr->second), + boost::lexical_cast<std::string>(lookupParamItr->second), + false); + break; + + default: + bool result; + if ((SPECPROP_ALTERNATE == rulePropMapItr->first && rsItr->ruleHasUserSub[PROP_ALTERNATE]) || + (SPECPROP_QUEUENAME == rulePropMapItr->first && rsItr->ruleHasUserSub[PROP_QUEUENAME])) { + // These properties are allowed to have username substitution + std::string sName(rulePropMapItr->second); + substituteUserId(sName, id); + result = matchProp(sName, lookupParamItr->second); + } else if (SPECPROP_ROUTINGKEY == rulePropMapItr->first) { + // Routing key is allowed to have username substitution + // and it gets topic exchange matching + if (rsItr->ruleHasUserSub[PROP_ROUTINGKEY]) { + std::string sKey(lookupParamItr->second); + substituteKeywords(sKey, id); + result = rsItr->matchRoutingKey(sKey); + } else { + result = rsItr->matchRoutingKey(lookupParamItr->second); + } + } else { + // Rules without substitution + result = matchProp(rulePropMapItr->second, lookupParamItr->second); + } + + if (result) { + QPID_LOG(debug, "ACL: the pair(" + << AclHelper::getPropertyStr(lookupParamItr->first) + << "," << lookupParamItr->second + << ") given in lookup matched the pair(" + << AclHelper::getPropertyStr(rulePropMapItr->first) << "," + << rulePropMapItr->second + << ") given in the rule"); + } else { + match = false; + QPID_LOG(debug, "ACL: the pair(" + << AclHelper::getPropertyStr(lookupParamItr->first) + << "," << lookupParamItr->second + << ") given in lookup doesn't match the pair(" + << AclHelper::getPropertyStr(rulePropMapItr->first) + << "," << rulePropMapItr->second + << ") given in the rule"); + } + break; + }; + } + } else { + // params don't exist. + } + } + } + if (match) { + aclresult = rsItr->ruleMode; + if (!limitChecked) { + // Now a lookup matched all rule properties but one + // of the numeric limit checks has failed. + // Demote allow rules to corresponding deny rules. + switch (aclresult) { + case acl::ALLOW: + aclresult = acl::DENY; + break; + case acl::ALLOWLOG: + aclresult = acl::DENYLOG; + break; + default: + break; + }; + } + QPID_LOG(debug,"ACL: Successful match, the decision is:" + << AclHelper::getAclResultStr(aclresult)); + } else { + // This rule did not match the requested lookup and + // does not contribute to an ACL decision. + } + return match; +} + +// +// lookup - general ACL lookup +// +// The ACL main business logic function of matching rules and declaring +// an allow or deny result. +// +AclResult AclData::lookup( + const std::string& id, + const Action& action, + const ObjectType& objType, + const std::string& name, + std::map<Property, std::string>* params) +{ + QPID_LOG(debug, "ACL: Lookup for id:" << id + << " action:" << AclHelper::getActionStr((Action) action) + << " objectType:" << AclHelper::getObjectTypeStr((ObjectType) objType) + << " name:" << name + << " with params " << AclHelper::propertyMapToString(params)); + + // A typical log looks like: + // ACL: Lookup for id:bob@QPID action:create objectType:queue name:q2 + // with params { durable=false passive=false autodelete=false + // exclusive=false alternate= policytype= maxqueuesize=0 + // maxqueuecount=0 } + + // Default result is blanket decision mode for the entire ACL list. + AclResult aclresult = decisionMode; + + // Test for lists of rules at the intersection of the Action & Object + if (actionList[action] && actionList[action][objType]) { + // Find the list of rules for this actorId + AclData::actObjItr itrRule = actionList[action][objType]->find(id); + + // If individual actorId not found then find a rule set for '*'. + if (itrRule == actionList[action][objType]->end()) { + itrRule = actionList[action][objType]->find(ACL_KEYWORD_WILDCARD); + } + if (itrRule != actionList[action][objType]->end()) { + // A list of rules exists for this actor/action/object tuple. + // Iterate the rule set to search for a matching rule. + ruleSetItr rsItr = itrRule->second.end(); + for (int cnt = itrRule->second.size(); cnt != 0; cnt--) { + rsItr--; + if (lookupMatchRule(rsItr, id, name, params, aclresult)) { + return aclresult; + } + } + } else { + // The Action-Object list has entries but not for this actorId + // nor for *. + } + } else { + // The Action-Object list has no entries. + } + + QPID_LOG(debug,"ACL: No successful match, defaulting to the decision mode " + << AclHelper::getAclResultStr(aclresult)); + return aclresult; +} + + +// +// lookupMatchPublishExchangeRule +// +// check a single publish exchange rule +// +bool AclData::lookupMatchPublishExchangeRule( + const ruleSetItr& rsItr, + const std::string& id, + const std::string& name, + const std::string& routingKey, + AclResult& aclresult) +{ + QPID_LOG(debug, "ACL: checking rule " << rsItr->toString()); + + // Search on exchange name and routing key only if specfied in rule. + bool match =true; + if (rsItr->pubExchNameInRule) { + // substitute user name into object name + bool result; + + if (rsItr->ruleHasUserSub[PROP_NAME]) { + std::string sName(rsItr->pubExchName); + substituteUserId(sName, id); + result = matchProp(sName, name); + } + else if (rsItr->pubExchNameMatchesBlank) { + result = name.empty(); + } else { + result = matchProp(rsItr->pubExchName, name); + } + + if (result) { + QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << " lookup exchange name '" + << name << "' matched with rule name '" + << rsItr->pubExchName << "'"); + } else { + match= false; + QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << " lookup exchange name '" + << name << "' did not match with rule name '" + << rsItr->pubExchName << "'"); + } + } + + if (match && rsItr->pubRoutingKeyInRule) { + if ((routingKey.find(ACL_KEYWORD_USER_SUBST, 0) != std::string::npos) || + (routingKey.find(ACL_KEYWORD_DOMAIN_SUBST, 0) != std::string::npos) || + (routingKey.find(ACL_KEYWORD_USERDOMAIN_SUBST, 0) != std::string::npos)) { + // The user is not allowed to present a routing key with the substitution key in it + QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << + " User-specified routing key has substitution wildcard:" << routingKey + << ". Rule match prohibited."); + match = false; + } else { + bool result; + if (rsItr->ruleHasUserSub[PROP_ROUTINGKEY]) { + std::string sKey(routingKey); + substituteKeywords(sKey, id); + result = rsItr->matchRoutingKey(sKey); + } else { + result = rsItr->matchRoutingKey(routingKey); + } + + if (result) { + QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << " lookup key name '" + << routingKey << "' matched with rule routing key '" + << rsItr->pubRoutingKey << "'"); + } else { + QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << " lookup key name '" + << routingKey << "' did not match with rule routing key '" + << rsItr->pubRoutingKey << "'"); + match = false; + } + } + } + + if (match) { + aclresult = rsItr->ruleMode; + QPID_LOG(debug,"ACL: Rule: " << rsItr->rawRuleNum << " Successful match, the decision is:" + << AclHelper::getAclResultStr(aclresult)); + } + return match; +} + +// +// lookup - special PUBLISH EXCHANGE lookup +// +// The ACL main business logic function of matching rules and declaring +// an allow or deny result. This lookup is the fastpath per-message +// lookup to verify if a user is allowed to publish to an exchange with +// a given key. +// +AclResult AclData::lookup( + const std::string& id, + const Action& action, + const ObjectType& objType, + const std::string& /*Exchange*/ name, + const std::string& routingKey) +{ + + QPID_LOG(debug, "ACL: Lookup for id:" << id + << " action:" << AclHelper::getActionStr((Action) action) + << " objectType:" << AclHelper::getObjectTypeStr((ObjectType) objType) + << " exchange name:" << name + << " with routing key " << routingKey); + + AclResult aclresult = decisionMode; + + if (actionList[action] && actionList[action][objType]){ + AclData::actObjItr itrRule = actionList[action][objType]->find(id); + + if (itrRule == actionList[action][objType]->end()) { + itrRule = actionList[action][objType]->find(ACL_KEYWORD_WILDCARD); + } + if (itrRule != actionList[action][objType]->end() ) { + // Found a rule list for this user-action-object set. + // Search the rule list for a matching rule. + ruleSetItr rsItr = itrRule->second.end(); + for (int cnt = itrRule->second.size(); cnt != 0; cnt--) { + rsItr--; + + if (lookupMatchPublishExchangeRule(rsItr, id, name, routingKey, aclresult)) { + return aclresult; + } + } + } + } + QPID_LOG(debug,"ACL: No successful match, defaulting to the decision mode " + << AclHelper::getAclResultStr(aclresult)); + return aclresult; + +} + + + +// +// +// +void AclData::setConnQuotaRuleSettings ( + boost::shared_ptr<quotaRuleSet> quotaPtr) +{ + connQuotaRuleSettings = quotaPtr; +} + + +// +// getConnQuotaForUser +// +// Return the true or false value of connQuotaRulesExist, +// indicating whether any kind of lookup was done or not. +// +// When lookups are performed return the result value of +// 1. The user's setting else +// 2. The 'all' user setting else +// 3. Zero +// When lookups are not performed then return a result value of Zero. +// +bool AclData::getConnQuotaForUser(const std::string& theUserName, + uint16_t* theResult) const { + if (this->enforcingConnectionQuotas()) { + // look for this user explicitly + quotaRuleSetItr nameItr = (*connQuotaRuleSettings).find(theUserName); + if (nameItr != (*connQuotaRuleSettings).end()) { + QPID_LOG(trace, "ACL: Connection quota for user " << theUserName + << " explicitly set to : " << (*nameItr).second); + *theResult = (*nameItr).second; + } else { + // Look for the 'all' user + nameItr = (*connQuotaRuleSettings).find(ACL_KEYWORD_ALL); + if (nameItr != (*connQuotaRuleSettings).end()) { + QPID_LOG(trace, "ACL: Connection quota for user " << theUserName + << " chosen through value for 'all' : " << (*nameItr).second); + *theResult = (*nameItr).second; + } else { + // Neither userName nor "all" found. + QPID_LOG(trace, "ACL: Connection quota for user " << theUserName + << " absent in quota settings. Return value : 0"); + *theResult = 0; + } + } + } else { + // Rules do not exist + QPID_LOG(trace, "ACL: Connection quota for user " << theUserName + << " unavailable; quota settings are not specified. Return value : 0"); + *theResult = 0; + } + return this->enforcingConnectionQuotas(); +} + +// +// +// +void AclData::setQueueQuotaRuleSettings ( + boost::shared_ptr<quotaRuleSet> quotaPtr) +{ + queueQuotaRuleSettings = quotaPtr; +} + + +// +// getQueueQuotaForUser +// +// Return the true or false value of queueQuotaRulesExist, +// indicating whether any kind of lookup was done or not. +// +// When lookups are performed return the result value of +// 1. The user's setting else +// 2. The 'all' user setting else +// 3. Zero +// When lookups are not performed then return a result value of Zero. +// +bool AclData::getQueueQuotaForUser(const std::string& theUserName, + uint16_t* theResult) const { + if (this->enforcingQueueQuotas()) { + // look for this user explicitly + quotaRuleSetItr nameItr = (*queueQuotaRuleSettings).find(theUserName); + if (nameItr != (*queueQuotaRuleSettings).end()) { + QPID_LOG(trace, "ACL: Queue quota for user " << theUserName + << " explicitly set to : " << (*nameItr).second); + *theResult = (*nameItr).second; + } else { + // Look for the 'all' user + nameItr = (*queueQuotaRuleSettings).find(ACL_KEYWORD_ALL); + if (nameItr != (*queueQuotaRuleSettings).end()) { + QPID_LOG(trace, "ACL: Queue quota for user " << theUserName + << " chosen through value for 'all' : " << (*nameItr).second); + *theResult = (*nameItr).second; + } else { + // Neither userName nor "all" found. + QPID_LOG(trace, "ACL: Queue quota for user " << theUserName + << " absent in quota settings. Return value : 0"); + *theResult = 0; + } + } + } else { + // Rules do not exist + QPID_LOG(trace, "ACL: Queue quota for user " << theUserName + << " unavailable; quota settings are not specified. Return value : 0"); + *theResult = 0; + } + return this->enforcingQueueQuotas(); +} + +void AclData::setConnGlobalRules (boost::shared_ptr<bwHostRuleSet> cgr) { + connBWHostsGlobalRules = cgr; +} + +void AclData::setConnUserRules (boost::shared_ptr<bwHostUserRuleMap> hurm) { + connBWHostsUserRules = hurm; +} + +AclResult AclData::isAllowedConnection(const std::string& userName, + const std::string& hostName, + std::string& logText) { + bool decisionMade(false); + AclResult result(ALLOW); + for (bwHostRuleSetItr it=connBWHostsGlobalRules->begin(); + it!=connBWHostsGlobalRules->end(); it++) { + if (it->getAclHost().match(hostName)) { + // This host matches a global spec and controls the + // allow/deny decision for this connection. + result = it->getAclResult(); + logText = QPID_MSG("global rule " << it->toString() + << (AclHelper::resultAllows(result) ? " allows" : " denies") + << " connection for host " << hostName << ", user " + << userName); + decisionMade = true; + break; + } else { + // This rule in the global spec doesn't match and + // does not control the allow/deny decision. + } + } + + // Run user black/white list check + if (!decisionMade) { + bwHostUserRuleMapItr itrRule = connBWHostsUserRules->find(userName); + if (itrRule != connBWHostsUserRules->end()) { + for (bwHostRuleSetItr it=(*itrRule).second.begin(); + it!=(*itrRule).second.end(); it++) { + if (it->getAclHost().match(hostName)) { + // This host matches a user spec and controls the + // allow/deny decision for this connection. + result = it->getAclResult(); + logText = QPID_MSG("global rule " << it->toString() + << (AclHelper::resultAllows(result) ? " allows" : " denies") + << " connection for host " << hostName << ", user " + << userName); + decisionMade = true; + break; + } else { + // This rule in the user's spec doesn't match and + // does not control the allow/deny decision. + } + } + } + } + + // Apply global connection mode + if (!decisionMade) { + result = connectionDecisionMode; + logText = QPID_MSG("default connection policy " + << (AclHelper::resultAllows(result) ? "allows" : "denies") + << " connection for host " << hostName << ", user " + << userName); + } + return result; +} + +// +// +// +AclData::~AclData() +{ + clear(); +} + + +// +// Limit check an int limit +// +bool AclData::compareInt(const qpid::acl::SpecProperty theProperty, + const std::string theAclValue, + const std::string theLookupValue, + bool theMaxFlag) +{ + uint64_t aclRuleValue (0); + uint64_t lookupValue (0); + + QPID_LOG(debug, "ACL: " + << (theMaxFlag ? "Upper" : "Lower") << "-limit comparison for property " + << AclHelper::getPropertyStr(theProperty) + << ". Success if lookup(" << theLookupValue + << ") " + << (theMaxFlag ? "<=" : ">=") << " rule(" << theAclValue << ")"); + + try { + aclRuleValue = boost::lexical_cast<uint64_t>(theAclValue); + } + catch(const boost::bad_lexical_cast&) { + assert (false); + return false; + } + + if (aclRuleValue == 0) { + QPID_LOG(debug, "ACL: Comparison is always true when ACL rule value is zero"); + return true; + } + + try { + lookupValue = boost::lexical_cast<uint64_t>(theLookupValue); + } + catch(const boost::bad_lexical_cast&) { + QPID_LOG(error,"ACL: Illegal value given in lookup for property '" + << AclHelper::getPropertyStr(theProperty) + << "' : " << theLookupValue); + return false; + } + + bool result = + (theMaxFlag ? lookupValue > aclRuleValue : lookupValue < aclRuleValue); + if ( result ) { + QPID_LOG(debug, "ACL: Limit exceeded for property '" + << AclHelper::getPropertyStr(theProperty) << "'"); + return false; + } + + return true; +} + +const std::string DOMAIN_SEPARATOR("@"); +const std::string PERIOD("."); +const std::string UNDERSCORE("_"); +// +// substituteString +// Given a name string from an Acl rule, substitute the replacement into it +// wherever the placeholder directs. +// +void AclData::substituteString(std::string& targetString, + const std::string& placeholder, + const std::string& replacement) +{ + assert (!placeholder.empty()); + if (placeholder.empty()) { + return; + } + size_t start_pos(0); + while((start_pos = targetString.find(placeholder, start_pos)) != std::string::npos) { + targetString.replace(start_pos, placeholder.length(), replacement); + start_pos += replacement.length(); + } +} + + +// +// normalizeUserId +// Given a name string return it in a form usable as topic keys: +// change "@" and "." to "_". +// +std::string AclData::normalizeUserId(const std::string& userId) +{ + std::string normalId(userId); + substituteString(normalId, DOMAIN_SEPARATOR, UNDERSCORE); + substituteString(normalId, PERIOD, UNDERSCORE); + return normalId; +} + + +// +// substituteUserId +// Given an Acl rule and an authenticated userId +// do the keyword substitutions on the rule. +// +void AclData::substituteUserId(std::string& ruleString, + const std::string& userId) +{ + size_t locDomSeparator(0); + std::string user(""); + std::string domain(""); + std::string userdomain = normalizeUserId(userId); + + locDomSeparator = userId.find(DOMAIN_SEPARATOR); + if (std::string::npos == locDomSeparator) { + // "@" not found. There's just a user name + user = normalizeUserId(userId); + } else { + // "@" found, split the names. Domain may be blank. + user = normalizeUserId(userId.substr(0,locDomSeparator)); + domain = normalizeUserId(userId.substr(locDomSeparator+1)); + } + + substituteString(ruleString, ACL_KEYWORD_USER_SUBST, user); + substituteString(ruleString, ACL_KEYWORD_DOMAIN_SUBST, domain); + substituteString(ruleString, ACL_KEYWORD_USERDOMAIN_SUBST, userdomain); +} + + +// +// substituteKeywords +// Given an Acl rule and an authenticated userId +// do reverse keyword substitutions on the rule. +// That is, replace the normalized name in the rule string with +// the keyword that represents it. This stragegy is used for +// topic key lookups where the keyword string proper is in the +// topic key search tree. +// +void AclData::substituteKeywords(std::string& ruleString, + const std::string& userId) +{ + size_t locDomSeparator(0); + std::string user(""); + std::string domain(""); + std::string userdomain = normalizeUserId(userId); + + locDomSeparator = userId.find(DOMAIN_SEPARATOR); + if (std::string::npos == locDomSeparator) { + // "@" not found. There's just a user name + user = normalizeUserId(userId); + } else { + // "@" found, split the names + user = normalizeUserId(userId.substr(0,locDomSeparator)); + domain = normalizeUserId(userId.substr(locDomSeparator+1)); + } + std::string oRule(ruleString); + substituteString(ruleString, userdomain, ACL_KEYWORD_USERDOMAIN_SUBST); + substituteString(ruleString, user, ACL_KEYWORD_USER_SUBST); + substituteString(ruleString, domain, ACL_KEYWORD_DOMAIN_SUBST); +} +}} diff --git a/qpid/cpp/src/qpid/acl/AclData.h b/qpid/cpp/src/qpid/acl/AclData.h new file mode 100644 index 0000000000..105a5d9c67 --- /dev/null +++ b/qpid/cpp/src/qpid/acl/AclData.h @@ -0,0 +1,323 @@ +#ifndef QPID_ACL_ACLDATA_H +#define QPID_ACL_ACLDATA_H + + +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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. + * + */ + +#include "qpid/broker/AclModule.h" +#include "qpid/AclHost.h" +#include "AclTopicMatch.h" +#include "qpid/log/Statement.h" +#include "boost/shared_ptr.hpp" +#include <vector> +#include <sstream> + +namespace qpid { +namespace acl { + +/** A rule for tracking black/white host connection settings. + * When a connection is attempted, the remote host is verified + * against lists of these rules. When the remote host is in + * the range specified by this aclHost then the AclResult is + * applied as allow/deny. + */ +class AclBWHostRule { +public: + AclBWHostRule(AclResult r, std::string h) : + aclResult(r), aclHost(h) {} + + std::string toString () const { + std::ostringstream ruleStr; + ruleStr << "[ruleMode = " << AclHelper::getAclResultStr(aclResult) + << " {" << aclHost.str() << "}"; + return ruleStr.str(); + } + const AclHost& getAclHost() const { return aclHost; } + const AclResult& getAclResult() const { return aclResult; } + +private: + AclResult aclResult; + AclHost aclHost; +}; + + +class AclData { + + +public: + + typedef std::map<qpid::acl::Property, std::string> propertyMap; + typedef propertyMap::const_iterator propertyMapItr; + + typedef std::map<qpid::acl::SpecProperty, std::string> specPropertyMap; + typedef specPropertyMap::const_iterator specPropertyMapItr; + + // + // Rule + // + // Created by AclReader and stored in a ruleSet vector for subsequent + // run-time lookup matching and allow/deny decisions. + // RuleSet vectors are indexed by Action-Object-actorId so these + // attributes are not part of a rule. + // A single ACL file entry may create many rule entries in + // many ruleset vectors. + // + struct Rule { + typedef broker::TopicExchange::TopicExchangeTester topicTester; + + int rawRuleNum; // rule number in ACL file + qpid::acl::AclResult ruleMode; // combined allow/deny log/nolog + specPropertyMap props; // properties to be matched + // pubXxx for publish exchange fastpath + bool pubRoutingKeyInRule; + std::string pubRoutingKey; + boost::shared_ptr<topicTester> pTTest; + bool pubExchNameInRule; + bool pubExchNameMatchesBlank; + std::string pubExchName; + std::vector<bool> ruleHasUserSub; + std::string lookupSource; + std::string lookupHelp; + + Rule (int ruleNum, qpid::acl::AclResult res, specPropertyMap& p) : + rawRuleNum(ruleNum), + ruleMode(res), + props(p), + pubRoutingKeyInRule(false), + pubRoutingKey(), + pTTest(boost::shared_ptr<topicTester>(new topicTester())), + pubExchNameInRule(false), + pubExchNameMatchesBlank(false), + pubExchName(), + ruleHasUserSub(PROPERTYSIZE, false) + {} + + // Variation of Rule for tracking PropertyDefs + // for AclValidation. + Rule (int ruleNum, qpid::acl::AclResult res, specPropertyMap& p, + const std::string& ls, const std::string& lh + ) : + rawRuleNum(ruleNum), + ruleMode(res), + props(p), + pubRoutingKeyInRule(false), + pubRoutingKey(), + pubExchNameInRule(false), + pubExchNameMatchesBlank(false), + pubExchName(), + ruleHasUserSub(PROPERTYSIZE, false), + lookupSource(ls), + lookupHelp(lh) + {} + + + std::string toString () const { + std::ostringstream ruleStr; + ruleStr << "[rule " << rawRuleNum + << " ruleMode = " << AclHelper::getAclResultStr(ruleMode) + << " props{"; + for (specPropertyMapItr pMItr = props.begin(); + pMItr != props.end(); + pMItr++) { + ruleStr << " " + << AclHelper::getPropertyStr((SpecProperty) pMItr-> first) + << "=" << pMItr->second; + } + ruleStr << " }]"; + return ruleStr.str(); + } + + void addTopicTest(const std::string& pattern) { + pTTest->addBindingKey(broker::TopicExchange::normalize(pattern)); + } + + // Topic Exchange tester + // return true if any bindings match 'pattern' + bool matchRoutingKey(const std::string& pattern) const + { + topicTester::BindingVec bv; + return pTTest->findMatches(pattern, bv); + } + }; + + typedef std::vector<Rule> ruleSet; + typedef ruleSet::const_iterator ruleSetItr; + typedef std::map<std::string, ruleSet > actionObject; // user + typedef actionObject::iterator actObjItr; + typedef actionObject* aclAction; + typedef std::map<std::string, uint16_t> quotaRuleSet; // <username, N> + typedef quotaRuleSet::const_iterator quotaRuleSetItr; + typedef std::vector<AclBWHostRule> bwHostRuleSet; // allow/deny hosts-vector + typedef bwHostRuleSet::const_iterator bwHostRuleSetItr; + typedef std::map<std::string, bwHostRuleSet> bwHostUserRuleMap; //<username, hosts-vector> + typedef bwHostUserRuleMap::const_iterator bwHostUserRuleMapItr; + + // Action*[] -> Object*[] -> map<user, set<Rule> > + aclAction* actionList[qpid::acl::ACTIONSIZE]; + qpid::acl::AclResult decisionMode; // allow/deny[-log] if no matching rule found + bool transferAcl; + std::string aclSource; + qpid::acl::AclResult connectionDecisionMode; + + AclResult lookup( + const std::string& id, // actor id + const Action& action, + const ObjectType& objType, + const std::string& name, // object name + std::map<Property, std::string>* params=0); + + AclResult lookup( + const std::string& id, // actor id + const Action& action, + const ObjectType& objType, + const std::string& ExchangeName, + const std::string& RoutingKey); + + boost::shared_ptr<const bwHostRuleSet> getGlobalConnectionRules() { + return connBWHostsGlobalRules; + } + + boost::shared_ptr<const bwHostUserRuleMap> getUserConnectionRules() { + return connBWHostsUserRules; + } + + bool matchProp(const std::string & src, const std::string& src1); + void clear (); + void printDecisionRules(int userFieldWidth); + + static const std::string ACL_KEYWORD_USER_SUBST; + static const std::string ACL_KEYWORD_DOMAIN_SUBST; + static const std::string ACL_KEYWORD_USERDOMAIN_SUBST; + static const std::string ACL_KEYWORD_ALL; + static const std::string ACL_KEYWORD_ACL; + static const std::string ACL_KEYWORD_GROUP; + static const std::string ACL_KEYWORD_QUOTA; + static const std::string ACL_KEYWORD_QUOTA_CONNECTIONS; + static const std::string ACL_KEYWORD_QUOTA_QUEUES; + static const char ACL_SYMBOL_WILDCARD; + static const std::string ACL_KEYWORD_WILDCARD; + static const char ACL_SYMBOL_LINE_CONTINUATION; + static const std::string ACL_KEYWORD_DEFAULT_EXCHANGE; + + void substituteString(std::string& targetString, + const std::string& placeholder, + const std::string& replacement); + std::string normalizeUserId(const std::string& userId); + void substituteUserId(std::string& ruleString, + const std::string& userId); + void substituteKeywords(std::string& ruleString, + const std::string& userId); + + // Per user connection quotas extracted from acl rule file + // Set by reader + void setConnQuotaRuleSettings (boost::shared_ptr<quotaRuleSet>); + // Get by connection approvers + bool enforcingConnectionQuotas() const { return connQuotaRuleSettings->size() > 0; } + bool getConnQuotaForUser(const std::string&, uint16_t*) const; + + // Per user queue quotas extracted from acl rule file + // Set by reader + void setQueueQuotaRuleSettings (boost::shared_ptr<quotaRuleSet>); + // Get by queue approvers + bool enforcingQueueQuotas() const { return queueQuotaRuleSettings->size() > 0; } + bool getQueueQuotaForUser(const std::string&, uint16_t*) const; + + // Global connection Black/White list rules + void setConnGlobalRules (boost::shared_ptr<bwHostRuleSet>); + + // Per-user connection Black/White list rules map + void setConnUserRules (boost::shared_ptr<bwHostUserRuleMap>); + + /** getConnectMaxSpec + * Connection quotas are held in uint16_t variables. + * This function specifies the largest value that a user is allowed + * to declare for a connection quota. The upper limit serves two + * purposes: 1. It leaves room for magic numbers that may be declared + * by keyword names in Acl files and not have those numbers conflict + * with innocent user declared values, and 2. It makes the unsigned + * math very close to _MAX work reliably with no risk of accidental + * wrapping back to zero. + */ + static uint16_t getConnectMaxSpec() { + return 65530; + } + static std::string getMaxConnectSpecStr() { + return "65530"; + } + + static uint16_t getQueueMaxSpec() { + return 65530; + } + static std::string getMaxQueueSpecStr() { + return "65530"; + } + + /** + * isAllowedConnection + * Return true if this user is allowed to connect to this host. + * Return log text describing both success and failure. + */ + AclResult isAllowedConnection(const std::string& userName, + const std::string& hostName, + std::string& logText); + + AclResult connectionMode() const { + return connectionDecisionMode; + } + + AclData(); + virtual ~AclData(); + +private: + + inline bool lookupMatchRule( + const ruleSetItr& rsItr, + const std::string& id, + const std::string& name, + const std::map<Property, std::string>* params, + AclResult& aclresult); + + inline bool lookupMatchPublishExchangeRule( + const ruleSetItr& rsItr, + const std::string& id, + const std::string& name, + const std::string& routingKey, + AclResult& aclresult); + + bool compareInt(const qpid::acl::SpecProperty theProperty, + const std::string theAclValue, + const std::string theLookupValue, + bool theMaxFlag); + + // Per-user connection quota + boost::shared_ptr<quotaRuleSet> connQuotaRuleSettings; + + // Per-user queue quota + boost::shared_ptr<quotaRuleSet> queueQuotaRuleSettings; + + // Global host connection black/white rule set + boost::shared_ptr<bwHostRuleSet> connBWHostsGlobalRules; + + // Per-user host connection black/white rule set map + boost::shared_ptr<bwHostUserRuleMap> connBWHostsUserRules; +}; + +}} // namespace qpid::acl + +#endif // QPID_ACL_ACLDATA_H diff --git a/qpid/cpp/src/qpid/acl/AclLexer.cpp b/qpid/cpp/src/qpid/acl/AclLexer.cpp new file mode 100644 index 0000000000..4006e5271f --- /dev/null +++ b/qpid/cpp/src/qpid/acl/AclLexer.cpp @@ -0,0 +1,141 @@ +/* + * + * Copyright (c) 2014 The Apache Software Foundation + * + * Licensed 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. + * + */ + +#include "qpid/acl/AclLexer.h" +#include "qpid/RefCounted.h" +#include "qpid/Exception.h" +#include <boost/shared_ptr.hpp> +#include <boost/concept_check.hpp> +#include <iostream> +#include <map> +#include <set> +#include <string> +#include <sstream> + +namespace qpid { +namespace acl { + +// ObjectType +const std::string objectNames[OBJECTSIZE] = { + "broker", "connection", "exchange", "link", "method", "query", "queue" }; + +ObjectType AclHelper::getObjectType(const std::string& str) { + for (int i=0; i< OBJECTSIZE; ++i) { + if (str.compare(objectNames[i]) == 0) + return ObjectType(i); + } + throw qpid::Exception("Acl illegal object name: " + str); +} + +const std::string& AclHelper::getObjectTypeStr(const ObjectType o) { + return objectNames[o]; +} + +// Action +const std::string actionNames[ACTIONSIZE] = { + "access", "bind", "consume", "create", "delete", + "move", "publish", "purge", "redirect", "reroute", + "unbind", "update" }; + +Action AclHelper::getAction(const std::string& str) { + for (int i=0; i< ACTIONSIZE; ++i) { + if (str.compare(actionNames[i]) == 0) + return Action(i); + } + throw qpid::Exception("Acl illegal action name: " + str); +} + +const std::string& AclHelper::getActionStr(const Action a) { + return actionNames[a]; +} + +// Property +// These are shared between broker and acl using code enums. +const std::string propertyNames[PROPERTYSIZE] = { + "name", "durable", "owner", "routingkey", "autodelete", "exclusive", "type", + "alternate", "queuename", "exchangename", "schemapackage", + "schemaclass", "policytype", "paging", "host", + + "maxpages", "maxpagefactor", + "maxqueuesize", "maxqueuecount", "maxfilesize", "maxfilecount"}; + +Property AclHelper::getProperty(const std::string& str) { + for (int i=0; i< PROPERTYSIZE; ++i) { + if (str.compare(propertyNames[i]) == 0) + return Property(i); + } + throw qpid::Exception("Acl illegal property name: " + str); +} + +const std::string& AclHelper::getPropertyStr(const Property p) { + return propertyNames[p]; +} + +// SpecProperty +// These are shared between user acl files and acl using text. +const std::string specPropertyNames[SPECPROPSIZE] = { + "name", "durable", "owner", "routingkey", "autodelete", "exclusive", "type", + "alternate", "queuename", "exchangename", "schemapackage", + "schemaclass", "policytype", "paging", "host", + + "queuemaxsizelowerlimit", "queuemaxsizeupperlimit", + "queuemaxcountlowerlimit", "queuemaxcountupperlimit", + "filemaxsizelowerlimit", "filemaxsizeupperlimit", + "filemaxcountlowerlimit", "filemaxcountupperlimit", + "pageslowerlimit", "pagesupperlimit", + "pagefactorlowerlimit", "pagefactorupperlimit" }; + +SpecProperty AclHelper::getSpecProperty(const std::string& str) { + for (int i=0; i< SPECPROPSIZE; ++i) { + if (str.compare(specPropertyNames[i]) == 0) + return SpecProperty(i); + } + // Allow old names in ACL file as aliases for newly-named properties + if (str.compare("maxqueuesize") == 0) + return SPECPROP_MAXQUEUESIZEUPPERLIMIT; + if (str.compare("maxqueuecount") == 0) + return SPECPROP_MAXQUEUECOUNTUPPERLIMIT; + throw qpid::Exception("Acl illegal spec property name: " + str); +} + +const std::string& AclHelper::getPropertyStr(const SpecProperty p) { + return specPropertyNames[p]; +} + +// AclResult +const std::string resultNames[RESULTSIZE] = { + "allow", "allow-log", "deny", "deny-log" }; + +AclResult AclHelper::getAclResult(const std::string& str) { + for (int i=0; i< RESULTSIZE; ++i) { + if (str.compare(resultNames[i]) == 0) + return AclResult(i); + } + throw qpid::Exception("Acl illegal result name: " + str); +} + +const std::string& AclHelper::getAclResultStr(const AclResult r) { + return resultNames[r]; +} + +bool AclHelper::resultAllows(const AclResult r) { + bool answer = r == ALLOW || r == ALLOWLOG; + return answer; +} + +}} // namespace qpid::acl diff --git a/qpid/cpp/src/qpid/acl/AclLexer.h b/qpid/cpp/src/qpid/acl/AclLexer.h new file mode 100644 index 0000000000..d3df411afd --- /dev/null +++ b/qpid/cpp/src/qpid/acl/AclLexer.h @@ -0,0 +1,200 @@ +#ifndef QPID_ACL_ACLLEXER_H +#define QPID_ACL_ACLLEXER_H + +/* + * + * Copyright (c) 2014 The Apache Software Foundation + * + * Licensed 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. + * + */ + + +#include "qpid/Exception.h" +#include "qpid/broker/BrokerImportExport.h" +#include <boost/shared_ptr.hpp> +#include <iostream> +#include <map> +#include <set> +#include <string> +#include <sstream> + +namespace qpid { + +namespace acl { + + // Interface enumerations. + // These enumerations define enum lists and implied text strings + // to match. They are used in two areas: + // 1. In the ACL specifications in the ACL file, file parsing, and + // internal rule storage. + // 2. In the authorize interface in the rest of the broker where + // code requests the ACL module to authorize an action. + + // ObjectType shared between ACL spec and ACL authorise interface + enum ObjectType { + OBJ_BROKER, + OBJ_CONNECTION, + OBJ_EXCHANGE, + OBJ_LINK, + OBJ_METHOD, + OBJ_QUERY, + OBJ_QUEUE, + OBJECTSIZE }; // OBJECTSIZE must be last in list + + const int OBJECTTYPE_STR_WIDTH = 10; + + // Action shared between ACL spec and ACL authorise interface + enum Action { + ACT_ACCESS, + ACT_BIND, + ACT_CONSUME, + ACT_CREATE, + ACT_DELETE, + ACT_MOVE, + ACT_PUBLISH, + ACT_PURGE, + ACT_REDIRECT, + ACT_REROUTE, + ACT_UNBIND, + ACT_UPDATE, + ACTIONSIZE }; // ACTIONSIZE must be last in list + + const int ACTION_STR_WIDTH = 8; + + // Property used in ACL authorize interface + enum Property { + PROP_NAME, + PROP_DURABLE, + PROP_OWNER, + PROP_ROUTINGKEY, + PROP_AUTODELETE, + PROP_EXCLUSIVE, + PROP_TYPE, + PROP_ALTERNATE, + PROP_QUEUENAME, + PROP_EXCHANGENAME, + PROP_SCHEMAPACKAGE, + PROP_SCHEMACLASS, + PROP_POLICYTYPE, + PROP_PAGING, + PROP_HOST, + PROP_MAXPAGES, + PROP_MAXPAGEFACTOR, + PROP_MAXQUEUESIZE, + PROP_MAXQUEUECOUNT, + PROP_MAXFILESIZE, + PROP_MAXFILECOUNT, + PROPERTYSIZE // PROPERTYSIZE must be last in list + }; + + // Property used in ACL spec file + // Note for properties common to file processing/rule storage and to + // broker rule lookups the identical enum values are used. + enum SpecProperty { + SPECPROP_NAME = PROP_NAME, + SPECPROP_DURABLE = PROP_DURABLE, + SPECPROP_OWNER = PROP_OWNER, + SPECPROP_ROUTINGKEY = PROP_ROUTINGKEY, + SPECPROP_AUTODELETE = PROP_AUTODELETE, + SPECPROP_EXCLUSIVE = PROP_EXCLUSIVE, + SPECPROP_TYPE = PROP_TYPE, + SPECPROP_ALTERNATE = PROP_ALTERNATE, + SPECPROP_QUEUENAME = PROP_QUEUENAME, + SPECPROP_EXCHANGENAME = PROP_EXCHANGENAME, + SPECPROP_SCHEMAPACKAGE = PROP_SCHEMAPACKAGE, + SPECPROP_SCHEMACLASS = PROP_SCHEMACLASS, + SPECPROP_POLICYTYPE = PROP_POLICYTYPE, + SPECPROP_PAGING = PROP_PAGING, + SPECPROP_HOST = PROP_HOST, + + SPECPROP_MAXQUEUESIZELOWERLIMIT, + SPECPROP_MAXQUEUESIZEUPPERLIMIT, + SPECPROP_MAXQUEUECOUNTLOWERLIMIT, + SPECPROP_MAXQUEUECOUNTUPPERLIMIT, + SPECPROP_MAXFILESIZELOWERLIMIT, + SPECPROP_MAXFILESIZEUPPERLIMIT, + SPECPROP_MAXFILECOUNTLOWERLIMIT, + SPECPROP_MAXFILECOUNTUPPERLIMIT, + SPECPROP_MAXPAGESLOWERLIMIT, + SPECPROP_MAXPAGESUPPERLIMIT, + SPECPROP_MAXPAGEFACTORLOWERLIMIT, + SPECPROP_MAXPAGEFACTORUPPERLIMIT, + SPECPROPSIZE // SPECPROPSIZE must be last + }; + +// AclResult shared between ACL spec and ACL authorise interface + enum AclResult { + ALLOW, + ALLOWLOG, + DENY, + DENYLOG, + RESULTSIZE + }; + + + QPID_BROKER_CLASS_EXTERN class AclHelper { + private: + AclHelper(){} + public: + static QPID_BROKER_EXTERN ObjectType getObjectType(const std::string& str); + static QPID_BROKER_EXTERN const std::string& getObjectTypeStr(const ObjectType o); + static QPID_BROKER_EXTERN Action getAction(const std::string& str); + static QPID_BROKER_EXTERN const std::string& getActionStr(const Action a); + static QPID_BROKER_EXTERN Property getProperty(const std::string& str); + static QPID_BROKER_EXTERN const std::string& getPropertyStr(const Property p); + static QPID_BROKER_EXTERN SpecProperty getSpecProperty(const std::string& str); + static QPID_BROKER_EXTERN const std::string& getPropertyStr(const SpecProperty p); + static QPID_BROKER_EXTERN AclResult getAclResult(const std::string& str); + static QPID_BROKER_EXTERN const std::string& getAclResultStr(const AclResult r); + static QPID_BROKER_EXTERN bool resultAllows(const AclResult r); + + typedef std::set<Property> propSet; + typedef boost::shared_ptr<propSet> propSetPtr; + typedef std::pair<Action, propSetPtr> actionPair; + typedef std::map<Action, propSetPtr> actionMap; + typedef boost::shared_ptr<actionMap> actionMapPtr; + typedef std::pair<ObjectType, actionMapPtr> objectPair; + typedef std::map<Property, std::string> propMap; + typedef propMap::const_iterator propMapItr; + typedef std::map<SpecProperty, std::string> specPropMap; + typedef specPropMap::const_iterator specPropMapItr; + + // + // properyMapToString + // + template <typename T> + static std::string propertyMapToString( + const std::map<T, std::string>* params) + { + std::ostringstream ss; + ss << "{"; + if (params) + { + for (typename std::map<T, std::string>::const_iterator + pMItr = params->begin(); pMItr != params->end(); pMItr++) + { + ss << " " << getPropertyStr((T) pMItr-> first) + << "=" << pMItr->second; + } + } + ss << " }"; + return ss.str(); + } + + }; + + +}} // namespace qpid::acl + +#endif // QPID_ACL_ACLLEXER_H diff --git a/qpid/cpp/src/qpid/acl/AclPlugin.cpp b/qpid/cpp/src/qpid/acl/AclPlugin.cpp new file mode 100644 index 0000000000..77580ba531 --- /dev/null +++ b/qpid/cpp/src/qpid/acl/AclPlugin.cpp @@ -0,0 +1,98 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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. + * + */ + +#include <sstream> +#include "qpid/acl/Acl.h" +#include "qpid/broker/Broker.h" +#include "qpid/Plugin.h" +#include "qpid/Options.h" +#include "qpid/sys/Path.h" +#include "qpid/log/Statement.h" + +#include <boost/shared_ptr.hpp> +#include <boost/utility/in_place_factory.hpp> + +namespace qpid { +namespace acl { + +using namespace std; + +/** Note separating options from values to work around boost version differences. + * Old boost takes a reference to options objects, but new boost makes a copy. + * New boost allows a shared_ptr but that's not compatible with old boost. + */ +struct AclOptions : public Options { + AclValues& values; + + AclOptions(AclValues& v) : Options("ACL Options"), values(v) { + values.aclMaxConnectTotal = 500; + addOptions() + ("acl-file", optValue(values.aclFile, "FILE"), "The policy file to load from, loaded from data dir") + ("connection-limit-per-user", optValue(values.aclMaxConnectPerUser, "N"), "The maximum number of connections allowed per user. 0 implies no limit.") + ("max-connections" , optValue(values.aclMaxConnectTotal, "N"), "The maximum combined number of connections allowed. 0 implies no limit.") + ("connection-limit-per-ip" , optValue(values.aclMaxConnectPerIp, "N"), "The maximum number of connections allowed per host IP address. 0 implies no limit.") + ("max-queues-per-user", optValue(values.aclMaxQueuesPerUser, "N"), "The maximum number of queues allowed per user. 0 implies no limit.") + ; + } +}; + +struct AclPlugin : public Plugin { + + AclValues values; + AclOptions options; + boost::intrusive_ptr<Acl> acl; + + AclPlugin() : options(values) {} + + Options* getOptions() { return &options; } + + void init(broker::Broker& b) { + if (acl) throw Exception("ACL plugin cannot be initialized twice in one process."); + + if (!values.aclFile.empty()){ + sys::Path aclFile(values.aclFile); + sys::Path dataDir(b.getDataDir().getPath()); + if (!aclFile.isAbsolute() && !dataDir.empty()) + values.aclFile = (dataDir + aclFile).str(); + } + acl = new Acl(values, b); + b.setAcl(acl.get()); + b.addFinalizer(boost::bind(&AclPlugin::shutdown, this)); + } + + template <class T> bool init(Plugin::Target& target) { + T* t = dynamic_cast<T*>(&target); + if (t) init(*t); + return t; + } + + void earlyInitialize(Plugin::Target&) {} + + void initialize(Plugin::Target& target) { + init<broker::Broker>(target); + } + + void shutdown() { acl = 0; } +}; + +static AclPlugin instance; // Static initialization. + +// For test purposes. +boost::intrusive_ptr<Acl> getGlobalAcl() { return instance.acl; } + +}} // namespace qpid::acl diff --git a/qpid/cpp/src/qpid/acl/AclReader.cpp b/qpid/cpp/src/qpid/acl/AclReader.cpp new file mode 100644 index 0000000000..e8223c3570 --- /dev/null +++ b/qpid/cpp/src/qpid/acl/AclReader.cpp @@ -0,0 +1,827 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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. + * + */ + +#include "qpid/acl/AclReader.h" +#include "qpid/acl/AclData.h" + +#include <cctype> +#include <cstring> +#include <fstream> +#include <sstream> +#include "qpid/log/Statement.h" +#include "qpid/Exception.h" +#include <boost/lexical_cast.hpp> +#include <algorithm> + +#include <iomanip> // degug +#include <iostream> // debug + +#define ACL_FORMAT_ERR_LOG_PREFIX "ACL format error: " << fileName << ":" << lineNumber << ": " + +namespace qpid { +namespace acl { + + AclReader::aclRule::aclRule(const AclResult r, const std::string n, const groupMap& groups) : res(r), actionAll(true), objStatus(NONE) { + processName(n, groups); + } + AclReader::aclRule::aclRule(const AclResult r, const std::string n, const groupMap& groups, const Action a) : res(r), actionAll(false), action(a), objStatus(NONE) { + processName(n, groups); + } + + void AclReader::aclRule::setObjectType(const ObjectType o) { + objStatus = VALUE; + object = o; + } + + void AclReader::aclRule::setObjectTypeAll() { + objStatus = ALL; + } + + bool AclReader::aclRule::addProperty(const SpecProperty p, const std::string v) { + return props.insert(propNvPair(p, v)).second; + } + + // Debug aid + std::string AclReader::aclRule::toString() { + std::ostringstream oss; + oss << AclHelper::getAclResultStr(res) << " ["; + for (nsCitr itr = names.begin(); itr != names.end(); itr++) { + if (itr != names.begin()) oss << ", "; + oss << *itr; + } + oss << "]"; + if (actionAll) { + oss << " *"; + } else { + oss << " " << AclHelper::getActionStr(action); + } + if (objStatus == ALL) { + oss << " *"; + } else if (objStatus == VALUE) { + oss << " " << AclHelper::getObjectTypeStr(object); + } + for (pmCitr i=props.begin(); i!=props.end(); i++) { + oss << " " << AclHelper::getPropertyStr(i->first) << "=" << i->second; + } + return oss.str(); + } + + void AclReader::loadDecisionData(boost::shared_ptr<AclData> d) { + d->clear(); + QPID_LOG(debug, "ACL: Load Rules"); + bool foundmode = false; + bool foundConnectionMode = false; + + rlCitr i = rules.end(); + for (int cnt = rules.size(); cnt; cnt--) { + i--; + QPID_LOG(debug, "ACL: Processing " << std::setfill(' ') << std::setw(2) + << cnt << " " << (*i)->toString()); + + if (!(*i)->actionAll && (*i)->objStatus == aclRule::VALUE && + !validator.validateAllowedProperties( + (*i)->action, (*i)->object, (*i)->props, false)) { + // specific object/action has bad property + // this rule gets ignored + continue; + } else { + // action=all or object=none/all means the rule gets propagated + // possibly to many places. + // Invalid rule combinations are not propagated. + } + + if (!foundmode && (*i)->actionAll && (*i)->names.size() == 1 + && (*((*i)->names.begin())).compare(AclData::ACL_KEYWORD_WILDCARD) == 0) { + d->decisionMode = (*i)->res; + QPID_LOG(debug, "ACL: FoundMode " + << AclHelper::getAclResultStr(d->decisionMode)); + foundmode = true; + } else if ((*i)->action == acl::ACT_CREATE && (*i)->object == acl::OBJ_CONNECTION) { + // Intercept CREATE CONNECTION rules process them into separate lists to + // be consumed in the connection approval code path. + propMap::const_iterator pName = (*i)->props.find(SPECPROP_NAME); + if (pName != (*i)->props.end()) { + throw Exception(QPID_MSG("ACL: CREATE CONNECTION rule " << cnt << " must not have a 'name' property")); + } + propMap::const_iterator pHost = (*i)->props.find(SPECPROP_HOST); + if (pHost == (*i)->props.end()) { + throw Exception(QPID_MSG("ACL: CREATE CONNECTION rule " << cnt << " has no 'host' property")); + } + // create the connection rule + bool allUsers = (*(*i)->names.begin()).compare(AclData::ACL_KEYWORD_WILDCARD) == 0; + bool allHosts = pHost->second.compare(AclData::ACL_KEYWORD_ALL) == 0; + AclBWHostRule bwRule((*i)->res, (allHosts ? "" : pHost->second)); + + // apply the rule globally or to user list + if (allUsers) { + if (allHosts) { + // allow one specification of allUsers,allHosts + if (foundConnectionMode) { + throw Exception(QPID_MSG("ACL: only one CREATE CONNECTION rule for user=all and host=all allowed")); + } + foundConnectionMode = true; + d->connectionDecisionMode = (*i)->res; + QPID_LOG(trace, "ACL: Found connection mode: " << AclHelper::getAclResultStr( (*i)->res )); + } else { + // Rules for allUsers but not allHosts go into the global list + globalHostRules->insert( globalHostRules->begin(), bwRule ); + } + } else { + // other rules go into binned rule sets for each user + for (nsCitr itr = (*i)->names.begin(); + itr != (*i)->names.end(); + itr++) { + (*userHostRules)[(*itr)].insert( (*userHostRules)[(*itr)].begin(), bwRule); + } + } + } else { + AclData::Rule rule(cnt, (*i)->res, (*i)->props); + // Record which properties have the user substitution string + for (pmCitr pItr=rule.props.begin(); pItr!=rule.props.end(); pItr++) { + if ((pItr->second.find(AclData::ACL_KEYWORD_USER_SUBST, 0) != std::string::npos) || + (pItr->second.find(AclData::ACL_KEYWORD_DOMAIN_SUBST, 0) != std::string::npos) || + (pItr->second.find(AclData::ACL_KEYWORD_USERDOMAIN_SUBST, 0) != std::string::npos)) { + rule.ruleHasUserSub[pItr->first] = true; + } + } + + // Find possible routingkey property and cache its pattern + for (pmCitr pItr=rule.props.begin(); pItr!=rule.props.end(); pItr++) { + if (acl::SPECPROP_ROUTINGKEY == pItr->first) + { + rule.pubRoutingKeyInRule = true; + rule.pubRoutingKey = (std::string)pItr->second; + rule.addTopicTest(rule.pubRoutingKey); + } + } + + // Action -> Object -> map<user -> set<Rule> > + std::ostringstream actionstr; + for (int acnt = ((*i)->actionAll ? 0 : (*i)->action); + acnt < acl::ACTIONSIZE; + (*i)->actionAll ? acnt++ : acnt = acl::ACTIONSIZE) { + + if (acnt == acl::ACT_PUBLISH) + { + d->transferAcl = true; // we have transfer ACL + // For Publish the only object should be Exchange + // and the only property should be routingkey. + // Go through the rule properties and find the name and the key. + // If found then place them specially for the lookup engine. + for (pmCitr pItr=(*i)->props.begin(); pItr!=(*i)->props.end(); pItr++) { + if (acl::SPECPROP_NAME == pItr->first) + { + rule.pubExchNameInRule = true; + rule.pubExchName = pItr->second; + rule.pubExchNameMatchesBlank = rule.pubExchName.compare(AclData::ACL_KEYWORD_DEFAULT_EXCHANGE) == 0; + } + } + } + actionstr << AclHelper::getActionStr((Action) acnt) << ","; + + //find the Action, create if not exist + if (d->actionList[acnt] == NULL) { + d->actionList[acnt] = + new AclData::aclAction[qpid::acl::OBJECTSIZE]; + for (int j = 0; j < qpid::acl::OBJECTSIZE; j++) + d->actionList[acnt][j] = NULL; + } + + for (int ocnt = ((*i)->objStatus != aclRule::VALUE ? 0 + : (*i)->object); + ocnt < acl::OBJECTSIZE; + (*i)->objStatus != aclRule::VALUE ? ocnt++ : ocnt = acl::OBJECTSIZE) { + + //find the Object, create if not exist + if (d->actionList[acnt][ocnt] == NULL) + d->actionList[acnt][ocnt] = + new AclData::actionObject; + + // add users and Rule to object set + bool allNames = false; + // check to see if names.begin is '*' + if ((*(*i)->names.begin()).compare(AclData::ACL_KEYWORD_WILDCARD) == 0) + allNames = true; + + for (nsCitr itr = (allNames ? names.begin() : (*i)->names.begin()); + itr != (allNames ? names.end() : (*i)->names.end()); + itr++) { + if (validator.validateAllowedProperties(acl::Action(acnt), + acl::ObjectType(ocnt), + (*i)->props, + false)) { + AclData::actObjItr itrRule = + d->actionList[acnt][ocnt]->find(*itr); + + if (itrRule == d->actionList[acnt][ocnt]->end()) { + AclData::ruleSet rSet; + rSet.push_back(rule); + d->actionList[acnt][ocnt]->insert + (make_pair(std::string(*itr), rSet)); + } else { + itrRule->second.push_back(rule); + } + } else { + // Skip propagating this rule as it will never match. + } + } + } + } + + std::ostringstream objstr; + for (int ocnt = ((*i)->objStatus != aclRule::VALUE ? 0 : (*i)->object); + ocnt < acl::OBJECTSIZE; + (*i)->objStatus != aclRule::VALUE ? ocnt++ : ocnt = acl::OBJECTSIZE) { + objstr << AclHelper::getObjectTypeStr((ObjectType) ocnt) << ","; + } + + bool allNames = ((*(*i)->names.begin()).compare(AclData::ACL_KEYWORD_WILDCARD) == 0); + std::ostringstream userstr; + for (nsCitr itr = (allNames ? names.begin() : (*i)->names.begin()); + itr != (allNames ? names.end() : (*i)->names.end()); + itr++) { + userstr << *itr << ","; + } + + QPID_LOG(debug, "ACL: Adding actions {" << + actionstr.str().substr(0,actionstr.str().length()-1) + << "} to objects {" << + objstr.str().substr(0,objstr.str().length()-1) + << "} with props " << + AclHelper::propertyMapToString(&rule.props) + << " for users {" << + userstr.str().substr(0,userstr.str().length()-1) + << "}"); + } + } + + // connection quota + d->setConnQuotaRuleSettings(connQuota); + // queue quota + d->setQueueQuotaRuleSettings(queueQuota); + // global B/W connection rules + d->setConnGlobalRules(globalHostRules); + // user B/W connection rules + d->setConnUserRules(userHostRules); + } + + + void AclReader::aclRule::processName(const std::string& name, const groupMap& groups) { + if (name.compare(AclData::ACL_KEYWORD_ALL) == 0) { + names.insert(AclData::ACL_KEYWORD_WILDCARD); + } else { + gmCitr itr = groups.find(name); + if (itr == groups.end()) { + names.insert(name); + } else { + names.insert(itr->second->begin(), itr->second->end()); + } + } + } + + AclReader::AclReader(uint16_t theCliMaxConnPerUser, uint16_t theCliMaxQueuesPerUser) : + lineNumber(0), contFlag(false), + cliMaxConnPerUser (theCliMaxConnPerUser), + connQuotaRulesExist(false), + connQuota(new AclData::quotaRuleSet), + cliMaxQueuesPerUser (theCliMaxQueuesPerUser), + queueQuotaRulesExist(false), + queueQuota(new AclData::quotaRuleSet), + globalHostRules(new AclData::bwHostRuleSet), + userHostRules(new AclData::bwHostUserRuleMap) { + names.insert(AclData::ACL_KEYWORD_WILDCARD); + } + + AclReader::~AclReader() {} + + std::string AclReader::getError() { + return errorStream.str(); + } + + int AclReader::read(const std::string& fn, boost::shared_ptr<AclData> d) { + fileName = fn; + lineNumber = 0; + char buff[1024]; + std::ifstream ifs(fn.c_str(), std::ios_base::in); + if (!ifs.good()) { + errorStream << "Unable to open ACL file \"" << fn << "\": eof=" << (ifs.eof()?"T":"F") << "; fail=" << (ifs.fail()?"T":"F") << "; bad=" << (ifs.bad()?"T":"F"); + return -1; + } + // Propagate nonzero per-user max connection setting from CLI + if (cliMaxConnPerUser > 0) { + connQuotaRulesExist = true; + (*connQuota)[AclData::ACL_KEYWORD_ALL] = cliMaxConnPerUser; + } + // Propagate nonzero per-user max queue setting from CLI + if (cliMaxQueuesPerUser > 0) { + queueQuotaRulesExist = true; + (*queueQuota)[AclData::ACL_KEYWORD_ALL] = cliMaxQueuesPerUser; + } + // Loop to process the Acl file + try { + bool err = false; + while (ifs.good()) { + ifs.getline(buff, 1024); + lineNumber++; + if (std::strlen(buff) > 0 && buff[0] != '#') // Ignore blank lines and comments + err |= !processLine(buff); + } + if (!ifs.eof()) + { + errorStream << "Unable to read ACL file \"" << fn << "\": eof=" << (ifs.eof()?"T":"F") << "; fail=" << (ifs.fail()?"T":"F") << "; bad=" << (ifs.bad()?"T":"F"); + ifs.close(); + return -2; + } + ifs.close(); + if (err) return -3; + QPID_LOG(notice, "ACL: Read file \"" << fn << "\""); + } catch (const std::exception& e) { + errorStream << "Unable to read ACL file \"" << fn << "\": " << e.what(); + ifs.close(); + return -4; + } catch (...) { + errorStream << "Unable to read ACL file \"" << fn << "\": Unknown exception"; + ifs.close(); + return -5; + } + printNames(); + printRules(); + printQuotas(AclData::ACL_KEYWORD_QUOTA_CONNECTIONS, connQuota); + printQuotas(AclData::ACL_KEYWORD_QUOTA_QUEUES, queueQuota); + try { + loadDecisionData(d); + } catch (const std::exception& e) { + errorStream << "Error loading decision data : " << e.what(); + return -6; + } + printGlobalConnectRules(); + printUserConnectRules(); + validator.tracePropertyDefs(); + d->printDecisionRules( printNamesFieldWidth() ); + + return 0; + } + + bool AclReader::processLine(char* line) { + bool ret = false; + std::vector<std::string> toks; + + // Check for continuation + char* contCharPtr = std::strrchr(line, AclData::ACL_SYMBOL_LINE_CONTINUATION); + bool cont = contCharPtr != 0; + if (cont) *contCharPtr = 0; + + int numToks = tokenize(line, toks); + + if (cont && numToks == 0){ + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line \"" << lineNumber << "\" contains an illegal extension."; + return false; + } + + if (numToks && (toks[0].compare(AclData::ACL_KEYWORD_GROUP) == 0 || contFlag)) { + ret = processGroupLine(toks, cont); + } else if (numToks && toks[0].compare(AclData::ACL_KEYWORD_ACL) == 0) { + ret = processAclLine(toks); + } else if (numToks && toks[0].compare(AclData::ACL_KEYWORD_QUOTA) == 0) { + ret = processQuotaLine(toks); + } else { + // Check for whitespace only line, ignore these + bool ws = true; + for (unsigned i=0; i<std::strlen(line) && ws; i++) { + if (!std::isspace(line[i])) ws = false; + } + if (ws) { + ret = true; + } else { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Non-continuation line must start with \"" + << AclData::ACL_KEYWORD_GROUP << "\", \"" + << AclData::ACL_KEYWORD_ACL << "\". or \"" + << AclData::ACL_KEYWORD_QUOTA << "\"."; + ret = false; + } + } + contFlag = cont; + return ret; + } + + int AclReader::tokenize(char* line, std::vector<std::string>& toks) { + const char* tokChars = " \t\n\f\v\r"; + int cnt = 0; + char* cp = std::strtok(line, tokChars); + while (cp != 0) { + toks.push_back(std::string(cp)); + cnt++; + cp = std::strtok(0, tokChars); + } + return cnt; + } + + + // Process 'quota' rule lines + // Return true if the line is successfully processed without errors + bool AclReader::processQuotaLine(tokList& toks) { + const unsigned toksSize = toks.size(); + const unsigned minimumSize = 3; + if (toksSize < minimumSize) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Insufficient tokens for quota definition."; + return false; + } + + if (toks[1].compare(AclData::ACL_KEYWORD_QUOTA_CONNECTIONS) == 0) { + if (processQuotaLine(toks, AclData::ACL_KEYWORD_QUOTA_CONNECTIONS, AclData::getConnectMaxSpec(), connQuota)) { + // We have processed a connection quota rule + connQuotaRulesExist = true; + return true; + } + } else if (toks[1].compare(AclData::ACL_KEYWORD_QUOTA_QUEUES) == 0) { + if (processQuotaLine(toks, AclData::ACL_KEYWORD_QUOTA_QUEUES, AclData::getConnectMaxSpec(), queueQuota)) { + // We have processed a queue quota rule + queueQuotaRulesExist = true; + return true; + } + } else { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Quota type \"" << toks[1] << "\" unrecognized."; + return false; + } + return false; + } + + + // Process quota rule lines + // Return true if the line is successfully processed without errors + bool AclReader::processQuotaLine(tokList& toks, const std::string theNoun, uint16_t maxSpec, aclQuotaRuleSet theRules) { + const unsigned toksSize = toks.size(); + + uint16_t nEntities(0); + try { + nEntities = boost::lexical_cast<uint16_t>(toks[2]); + } catch(const boost::bad_lexical_cast&) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", " << theNoun << " quota value \"" << toks[2] + << "\" cannot be converted to a 16-bit unsigned integer."; + return false; + } + + // limit check the setting + if (nEntities > maxSpec) + { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", " << theNoun << " quota value \"" << toks[2] + << "\" exceeds maximum configuration setting of " + << maxSpec; + return false; + } + + // Apply the ount to all names in rule + for (unsigned idx = 3; idx < toksSize; idx++) { + if (groups.find(toks[idx]) == groups.end()) { + // This is the name of an individual, not a group + (*theRules)[toks[idx]] = nEntities; + } else { + if (!processQuotaGroup(toks[idx], nEntities, theRules)) + return false; + } + } + + return true; + } + + + // Process quota group expansion + // Return true if the quota is applied to all members of the group + bool AclReader::processQuotaGroup(const std::string& theGroup, uint16_t theQuota, aclQuotaRuleSet theRules) { + gmCitr citr = groups.find(theGroup); + + if (citr == groups.end()) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Failed to expand group \"" << theGroup << "\"."; + return false; + } + + for (nsCitr gni=citr->second->begin(); gni!=citr->second->end(); gni++) { + if (groups.find(*gni) == groups.end()) { + (*theRules)[*gni] = theQuota; + } else { + if (!processQuotaGroup(*gni, theQuota, theRules)) + return false; + } + } + return true; + } + + + void AclReader::printQuotas(const std::string theNoun, aclQuotaRuleSet theRules) const { + QPID_LOG(debug, "ACL: " << theNoun << " quota: " << (*theRules).size() << " rules found:"); + int cnt = 1; + for (AclData::quotaRuleSetItr itr=(*theRules).begin(); + itr != (*theRules).end(); + ++itr,++cnt) { + QPID_LOG(debug, "ACL: quota " << cnt << " : " << (*itr).second + << " " << theNoun << " for " << (*itr).first) + } + } + + + // Return true if the line is successfully processed without errors + // If cont is true, then groupName must be set to the continuation group name + bool AclReader::processGroupLine(tokList& toks, const bool cont) { + const unsigned toksSize = toks.size(); + + if (contFlag) { + gmCitr citr = groups.find(groupName); + for (unsigned i = 0; i < toksSize; i++) { + if (isValidGroupName(toks[i])) { + if (toks[i] == groupName) { + QPID_LOG(debug, "ACL: Line: " << lineNumber + << ", Ignoring recursive sub-group \"" << toks[i] << "\"."); + continue; + } else if (groups.find(toks[i]) == groups.end()) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Sub-group \"" << toks[i] << "\" not defined yet."; + return false; + } + } else if (!isValidUserName(toks[i])) return false; + addName(toks[i], citr->second); + } + } else { + const unsigned minimumSize = (cont ? 2 : 3); + if (toksSize < minimumSize) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Insufficient tokens for group definition."; + return false; + } + if (!isValidGroupName(toks[1])) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Group name \"" << toks[1] << "\" contains illegal characters."; + return false; + } + gmCitr citr = addGroup(toks[1]); + if (citr == groups.end()) return false; + for (unsigned i = 2; i < toksSize; i++) { + if (isValidGroupName(toks[i])) { + if (toks[i] == groupName) { + QPID_LOG(debug, "ACL: Line: " << lineNumber + << ", Ignoring recursive sub-group \"" << toks[i] << "\"."); + continue; + } else if (groups.find(toks[i]) == groups.end()) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Sub-group \"" << toks[i] << "\" not defined yet."; + return false; + } + } else if (!isValidUserName(toks[i])) return false; + addName(toks[i], citr->second); + } + } + return true; + } + + // Return true if sucessfully added group + AclReader::gmCitr AclReader::addGroup(const std::string& newGroupName) { + gmCitr citr = groups.find(newGroupName); + if (citr != groups.end()) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Duplicate group name \"" << newGroupName << "\"."; + return groups.end(); + } + groupPair p(newGroupName, nameSetPtr(new nameSet)); + gmRes res = groups.insert(p); + assert(res.second); + groupName = newGroupName; + return res.first; + } + + void AclReader::addName(const std::string& name, nameSetPtr groupNameSet) { + gmCitr citr = groups.find(name); + if (citr != groups.end()) { + // This is a previously defined group: add all the names in that group to this group + groupNameSet->insert(citr->second->begin(), citr->second->end()); + } else { + // Not a known group name + groupNameSet->insert(name); + addName(name); + } + } + + void AclReader::addName(const std::string& name) { + names.insert(name); + } + + /** + * Emit debug logs exposing the name lists + */ + void AclReader::printNames() const { + QPID_LOG(debug, "ACL: Group list: " << groups.size() << " groups found:" ); + std::string tmp("ACL: "); + for (gmCitr i=groups.begin(); i!= groups.end(); i++) { + tmp += " \""; + tmp += i->first; + tmp += "\":"; + for (nsCitr j=i->second->begin(); j!=i->second->end(); j++) { + tmp += " "; + tmp += *j; + } + QPID_LOG(debug, tmp); + tmp = "ACL: "; + } + QPID_LOG(debug, "ACL: name list: " << names.size() << " names found:" ); + tmp = "ACL: "; + for (nsCitr k=names.begin(); k!=names.end(); k++) { + tmp += " "; + tmp += *k; + } + QPID_LOG(debug, tmp); + } + + /** + * compute the width of longest user name + */ + int AclReader::printNamesFieldWidth() const { + std::string::size_type max = 0; + for (nsCitr k=names.begin(); k!=names.end(); k++) { + max = std::max(max, (*k).length()); + } + return max; + } + + bool AclReader::processAclLine(tokList& toks) { + const unsigned toksSize = toks.size(); + if (toksSize < 4) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Insufficient tokens for acl definition."; + return false; + } + + AclResult res; + try { + res = AclHelper::getAclResult(toks[1]); + } catch (...) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Unknown ACL permission \"" << toks[1] << "\"."; + return false; + } + + bool actionAllFlag = toks[3].compare(AclData::ACL_KEYWORD_ALL) == 0; + bool userAllFlag = toks[2].compare(AclData::ACL_KEYWORD_ALL) == 0; + Action action; + if (actionAllFlag) { + + if (userAllFlag && toksSize > 4) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Tokens found after action \"all\"."; + return false; + } + action = ACT_CONSUME; // dummy; compiler must initialize action for this code path + } else { + try { + action = AclHelper::getAction(toks[3]); + } catch (...) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Unknown action \"" << toks[3] << "\"."; + return false; + } + } + + // Create rule obj; then add object (if any) and properties (if any) + aclRulePtr rule; + if (actionAllFlag) { + rule.reset(new aclRule(res, toks[2], groups)); + } else { + rule.reset(new aclRule(res, toks[2], groups, action)); + } + + if (toksSize >= 5) { // object name-value pair + if (toks[4].compare(AclData::ACL_KEYWORD_ALL) == 0) { + rule->setObjectTypeAll(); + } else { + try { + rule->setObjectType(AclHelper::getObjectType(toks[4])); + } catch (...) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Unknown object \"" << toks[4] << "\"."; + return false; + } + } + } + + if (toksSize >= 6) { // property name-value pair(s) + for (unsigned i=5; i<toksSize; i++) { + nvPair propNvp = splitNameValuePair(toks[i]); + if (propNvp.second.size() == 0) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + <<", Badly formed property name-value pair \"" + << propNvp.first << "\". (Must be name=value)"; + return false; + } + SpecProperty prop; + try { + prop = AclHelper::getSpecProperty(propNvp.first); + } catch (...) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Unknown property \"" << propNvp.first << "\"."; + return false; + } + rule->addProperty(prop, propNvp.second); + } + } + // Check if name (toks[2]) is group; if not, add as name of individual + if (toks[2].compare(AclData::ACL_KEYWORD_ALL) != 0) { + if (groups.find(toks[2]) == groups.end()) { + addName(toks[2]); + } + } + + rules.push_back(rule); + + return true; + } + + // Debug aid + void AclReader::printRules() const { + QPID_LOG(debug, "ACL: Rule list: " << rules.size() << " ACL rules found:"); + int cnt = 1; + for (rlCitr i=rules.begin(); i<rules.end(); i++,cnt++) { + QPID_LOG(debug, "ACL: " << std::setfill(' ') << std::setw(2) << cnt << " " << (*i)->toString()); + if (!(*i)->actionAll && (*i)->objStatus == aclRule::VALUE) { + (void)validator.validateAllowedProperties((*i)->action, (*i)->object, (*i)->props, true); + } + } + } + + void AclReader::printConnectionRules(const std::string name, const AclData::bwHostRuleSet& rules) const { + QPID_LOG(debug, "ACL: " << name << " Connection Rule list : " << rules.size() << " rules found :"); + int cnt = 1; + for (AclData::bwHostRuleSetItr i=rules.begin(); i<rules.end(); i++,cnt++) { + QPID_LOG(debug, "ACL: " << std::setfill(' ') << std::setw(2) << cnt << " " << i->toString()); + } + } + + void AclReader::printGlobalConnectRules() const { + printConnectionRules("global", *globalHostRules); + } + + void AclReader::printUserConnectRules() const { + QPID_LOG(debug, "ACL: User Connection Rule lists : " << userHostRules->size() << " user lists found :"); + int cnt = 1; + for (AclData::bwHostUserRuleMapItr i=userHostRules->begin(); i!=userHostRules->end(); i++,cnt++) { + printConnectionRules(std::string((*i).first), (*i).second); + } + } + + // Static function + // Return true if the name is well-formed (ie contains legal characters) + bool AclReader::isValidGroupName(const std::string& name) { + for (unsigned i=0; i<name.size(); i++) { + const char ch = name.at(i); + if (!std::isalnum(ch) && ch != '-' && ch != '_') return false; + } + return true; + } + + // Static function + // Split name-value pair around '=' char of the form "name=value" + AclReader::nvPair AclReader::splitNameValuePair(const std::string& nvpString) { + std::size_t pos = nvpString.find("="); + if (pos == std::string::npos || pos == nvpString.size() - 1) { + return nvPair(nvpString, ""); + } + return nvPair(nvpString.substr(0, pos), nvpString.substr(pos+1)); + } + + // Returns true if a username has the name@realm format + bool AclReader::isValidUserName(const std::string& name){ + size_t pos = name.find('@'); + if ( pos == std::string::npos || pos == name.length() -1){ + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Username '" << name << "' must contain a realm"; + return false; + } + for (unsigned i=0; i<name.size(); i++) { + const char ch = name.at(i); + if (!std::isalnum(ch) && ch != '-' && ch != '_' && ch != '@' && ch != '.' && ch != '/'){ + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Username \"" << name << "\" contains illegal characters."; + return false; + } + } + return true; + } + +}} // namespace qpid::acl diff --git a/qpid/cpp/src/qpid/acl/AclReader.h b/qpid/cpp/src/qpid/acl/AclReader.h new file mode 100644 index 0000000000..24237a82ce --- /dev/null +++ b/qpid/cpp/src/qpid/acl/AclReader.h @@ -0,0 +1,150 @@ +#ifndef QPID_ACL_ACLREADER_H +#define QPID_ACL_ACLREADER_H + + +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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. + * + */ + +#include <boost/shared_ptr.hpp> +#include <map> +#include <set> +#include <string> +#include <vector> +#include <sstream> +#include <memory> +#include "qpid/acl/AclData.h" +#include "qpid/acl/Acl.h" +#include "qpid/broker/AclModule.h" +#include "qpid/acl/AclValidator.h" + +namespace qpid { +namespace acl { + +class AclReader { + typedef std::set<std::string> nameSet; + typedef nameSet::const_iterator nsCitr; + typedef boost::shared_ptr<nameSet> nameSetPtr; + + typedef std::pair<std::string, nameSetPtr> groupPair; + typedef std::map<std::string, nameSetPtr> groupMap; + typedef groupMap::const_iterator gmCitr; + typedef std::pair<gmCitr, bool> gmRes; + + typedef std::pair<SpecProperty, std::string> propNvPair; + typedef std::map<SpecProperty, std::string> propMap; + typedef propMap::const_iterator pmCitr; + + // + // aclRule + // + // A temporary rule created during ACL file processing. + // + class aclRule { + public: + enum objectStatus {NONE, VALUE, ALL}; + + AclResult res; + nameSet names; + bool actionAll; // True if action is set to keyword "all" + Action action; // Ignored if action is set to keyword "all" + objectStatus objStatus; + ObjectType object; // Ignored for all status values except VALUE + propMap props; + public: + aclRule(const AclResult r, const std::string n, const groupMap& groups); // action = "all" + aclRule(const AclResult r, const std::string n, const groupMap& groups, const Action a); + void setObjectType(const ObjectType o); + void setObjectTypeAll(); + bool addProperty(const SpecProperty p, const std::string v); + std::string toString(); // debug aid + private: + void processName(const std::string& name, const groupMap& groups); + }; + typedef boost::shared_ptr<AclData::quotaRuleSet> aclQuotaRuleSet; + typedef boost::shared_ptr<aclRule> aclRulePtr; + typedef std::vector<aclRulePtr> ruleList; + typedef ruleList::const_iterator rlCitr; + + typedef std::vector<std::string> tokList; + typedef tokList::const_iterator tlCitr; + + typedef std::set<std::string> keywordSet; + typedef keywordSet::const_iterator ksCitr; + typedef std::pair<std::string, std::string> nvPair; // Name-Value pair + + typedef boost::shared_ptr<std::vector<acl::AclBWHostRule> > aclGlobalHostRuleSet; + typedef boost::shared_ptr<std::map<std::string, std::vector<acl::AclBWHostRule> > > aclUserHostRuleSet; + + std::string fileName; + int lineNumber; + bool contFlag; + std::string groupName; + nameSet names; + groupMap groups; + ruleList rules; + AclValidator validator; + std::ostringstream errorStream; + + public: + AclReader(uint16_t cliMaxConnPerUser, uint16_t cliMaxQueuesPerUser); + virtual ~AclReader(); + int read(const std::string& fn, boost::shared_ptr<AclData> d); // return=0 for success + std::string getError(); + + private: + bool processLine(char* line); + void loadDecisionData(boost::shared_ptr<AclData> d); + int tokenize(char* line, tokList& toks); + + bool processGroupLine(tokList& toks, const bool cont); + gmCitr addGroup(const std::string& groupName); + void addName(const std::string& name, nameSetPtr groupNameSet); + void addName(const std::string& name); + void printNames() const; // debug aid + int printNamesFieldWidth() const; + + bool processAclLine(tokList& toks); + void printRules() const; // debug aid + void printConnectionRules(const std::string name, const AclData::bwHostRuleSet& rules) const; + void printGlobalConnectRules() const; + void printUserConnectRules() const; + bool isValidUserName(const std::string& name); + + bool processQuotaLine(tokList& toks); + bool processQuotaLine(tokList& toks, const std::string theNoun, uint16_t maxSpec, aclQuotaRuleSet theRules); + bool processQuotaGroup(const std::string&, uint16_t, aclQuotaRuleSet theRules); + void printQuotas(const std::string theNoun, aclQuotaRuleSet theRules) const; + + static bool isValidGroupName(const std::string& name); + static nvPair splitNameValuePair(const std::string& nvpString); + + const uint16_t cliMaxConnPerUser; + bool connQuotaRulesExist; + aclQuotaRuleSet connQuota; + + const uint16_t cliMaxQueuesPerUser; + bool queueQuotaRulesExist; + aclQuotaRuleSet queueQuota; + + aclGlobalHostRuleSet globalHostRules; + aclUserHostRuleSet userHostRules; +}; + +}} // namespace qpid::acl + +#endif // QPID_ACL_ACLREADER_H diff --git a/qpid/cpp/src/qpid/acl/AclResourceCounter.cpp b/qpid/cpp/src/qpid/acl/AclResourceCounter.cpp new file mode 100644 index 0000000000..2527af6375 --- /dev/null +++ b/qpid/cpp/src/qpid/acl/AclResourceCounter.cpp @@ -0,0 +1,174 @@ +/* + * + * 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. + * + */ + +#include "AclResourceCounter.h" +#include "Acl.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Mutex.h" +#include <assert.h> +#include <sstream> + +using namespace qpid::sys; + +namespace qpid { +namespace acl { + +// +// This module approves various resource creation requests: +// Queues +// + + +// +// +// +ResourceCounter::ResourceCounter(Acl& a, uint16_t ql) : + acl(a), queueLimit(ql) {} + +ResourceCounter::~ResourceCounter() {} + + +// +// limitApproveLH +// +// Resource creation approver. +// If user is under limit increment count and return true. +// Called with lock held. +// +bool ResourceCounter::limitApproveLH( + countsMap_t& theMap, + const std::string& theName, + uint16_t theLimit, + bool emitLog, + bool enforceLimit) { + + bool result(true); + uint16_t count; + countsMap_t::iterator eRef = theMap.find(theName); + if (eRef != theMap.end()) { + count = (uint16_t)(*eRef).second; + result = (enforceLimit ? count < theLimit : true); + if (result) { + count += 1; + (*eRef).second = count; + } + } else { + // user not found in map + if (enforceLimit) { + if (theLimit > 0) { + theMap[theName] = count = 1; + } else { + count = 0; + result = false; + } + } + else { + // not enforcing the limit + theMap[theName] = count = 1; + } + } + if (emitLog) { + QPID_LOG(trace, "ACL QueueApprover user=" << theName + << " limit=" << theLimit + << " curValue=" << count + << " result=" << (result ? "allow" : "deny")); + } + return result; +} + + +// +// releaseLH +// +// Decrement the name's count in map. +// called with dataLock already taken +// +void ResourceCounter::releaseLH(countsMap_t& theMap, const std::string& theName) { + + countsMap_t::iterator eRef = theMap.find(theName); + if (eRef != theMap.end()) { + uint16_t count = (uint16_t) (*eRef).second; + assert (count > 0); + if (1 == count) { + theMap.erase (eRef); + } else { + (*eRef).second = count - 1; + } + } else { + // User had no connections. + QPID_LOG(notice, "ACL resource counter: Queue owner for queue '" << theName + << "' not found in resource count pool"); + } +} + + +// +// approveCreateQueue +// Count an attempted queue creation by this user. +// Disapprove if over limit. +// +bool ResourceCounter::approveCreateQueue(const std::string& userId, + const std::string& queueName, + bool enforcingQueueQuotas, + uint16_t queueUserQuota ) +{ + Mutex::ScopedLock locker(dataLock); + + bool okByQ = limitApproveLH(queuePerUserMap, userId, queueUserQuota, true, enforcingQueueQuotas); + + if (okByQ) { + // Queue is owned by this userId + queueOwnerMap[queueName] = userId; + + QPID_LOG(trace, "ACL create queue approved for user '" << userId + << "' queue '" << queueName << "'"); + } else { + + QPID_LOG(error, "Client max queue count limit of " << queueUserQuota + << " exceeded by '" << userId << "' creating queue '" + << queueName << "'. Queue creation denied."); + + acl.reportQueueLimit(userId, queueName); + } + return okByQ; +} + + +// +// recordDestroyQueue +// Return a destroyed queue to a user's quota +// +void ResourceCounter::recordDestroyQueue(const std::string& queueName) +{ + Mutex::ScopedLock locker(dataLock); + + queueOwnerMap_t::iterator eRef = queueOwnerMap.find(queueName); + if (eRef != queueOwnerMap.end()) { + releaseLH(queuePerUserMap, (*eRef).second); + + queueOwnerMap.erase(eRef); + } else { + QPID_LOG(notice, "ACL resource counter: Queue '" << queueName + << "' not found in queue owner map"); + } +} + +}} // namespace qpid::acl diff --git a/qpid/cpp/src/qpid/acl/AclResourceCounter.h b/qpid/cpp/src/qpid/acl/AclResourceCounter.h new file mode 100644 index 0000000000..8809f73b18 --- /dev/null +++ b/qpid/cpp/src/qpid/acl/AclResourceCounter.h @@ -0,0 +1,78 @@ +#ifndef QPID_ACL_RESOURCECOUNTER_H +#define QPID_ACL_RESOURCECOUNTER_H + +/* + * + * 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. + * + */ + +#include "qpid/sys/Mutex.h" + +#include <map> + +namespace qpid { + +namespace acl { +class Acl; + + /** + * Approve or disapprove resource creation requests + */ +class ResourceCounter +{ +private: + typedef std::map<std::string, uint32_t> countsMap_t; + typedef std::map<std::string, std::string> queueOwnerMap_t; + + Acl& acl; + uint16_t queueLimit; + qpid::sys::Mutex dataLock; + + /** Records queueName-queueUserId */ + queueOwnerMap_t queueOwnerMap; + + /** Records queue-by-owner counts */ + countsMap_t queuePerUserMap; + + /** Return approval for proposed resource creation */ + bool limitApproveLH(countsMap_t& theMap, + const std::string& theName, + uint16_t theLimit, + bool emitLog, + bool enforceLimit); + + /** Release a connection */ + void releaseLH(countsMap_t& theMap, + const std::string& theName); + +public: + ResourceCounter(Acl& acl, uint16_t ql); + ~ResourceCounter(); + + // Queue counting + bool approveCreateQueue(const std::string& userId, + const std::string& queueName, + bool enforcingQueueQuotas, + uint16_t queueUserQuota ); + void recordDestroyQueue(const std::string& queueName); +}; + +}} // namespace qpid::acl + +#endif /*!QPID_ACL_RESOURCECOUNTER_H*/ diff --git a/qpid/cpp/src/qpid/acl/AclTopicMatch.h b/qpid/cpp/src/qpid/acl/AclTopicMatch.h new file mode 100644 index 0000000000..654d1d63d4 --- /dev/null +++ b/qpid/cpp/src/qpid/acl/AclTopicMatch.h @@ -0,0 +1,89 @@ +#ifndef QPID_ACL_TOPIC_MATCH_H +#define QPID_ACL_TOPIC_MATCH_H + +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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. + * + */ + +#include "qpid/broker/TopicKeyNode.h" +#include "qpid/broker/TopicExchange.h" +#include "qpid/log/Statement.h" +#include "boost/shared_ptr.hpp" +#include <vector> +#include <sstream> + +namespace qpid { +namespace broker { + +// Class for executing topic exchange routing key matching rules in +// Acl code. Allows or denies users publishing to an exchange. +class TopicExchange::TopicExchangeTester { + +class boundNode; + +public: + typedef std::vector<bool> BindingVec; + typedef TopicKeyNode<boundNode> TestBindingNode; + +private: + // Target class to be bound into topic key tree + class boundNode { + public: + BindingVec bindingVector; + }; + + // Acl binding trees contain only one node each. + // When the iterator sees it then the node matches the caller's spec. + class TestFinder : public TestBindingNode::TreeIterator { + public: + TestFinder(BindingVec& m) : bv(m), found(false) {}; + ~TestFinder() {}; + bool visit(TestBindingNode& /*node*/) { + assert(!found); + found = true; + return true; + } + BindingVec& bv; + bool found; + }; + +public: + TopicExchangeTester() {}; + ~TopicExchangeTester() {}; + bool addBindingKey(const std::string& bKey) { + std::string routingPattern = normalize(bKey); + boundNode *mbn = bindingTree.add(routingPattern); + if (mbn) { + // push a dummy binding to mark this node as "non-leaf" + mbn->bindingVector.push_back(true); + return true; + } + return false; + } + + bool findMatches(const std::string& rKey, BindingVec& matches) { + TestFinder testFinder(matches); + bindingTree.iterateMatch( rKey, testFinder ); + return testFinder.found; + } + +private: + TestBindingNode bindingTree; +}; +}} // namespace qpid::broker + +#endif // QPID_ACL_TOPIC_MATCH_H diff --git a/qpid/cpp/src/qpid/acl/AclValidator.cpp b/qpid/cpp/src/qpid/acl/AclValidator.cpp new file mode 100644 index 0000000000..f905b4aca5 --- /dev/null +++ b/qpid/cpp/src/qpid/acl/AclValidator.cpp @@ -0,0 +1,536 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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. + * + */ + +#include "qpid/acl/AclValidator.h" +#include "qpid/acl/AclData.h" +#include "qpid/acl/AclLexer.h" +#include "qpid/Exception.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/IntegerTypes.h" +#include "qpid/StringUtils.h" +#include <boost/lexical_cast.hpp> +#include <boost/bind.hpp> +#include <numeric> +#include <sstream> +#include <iomanip> + +namespace qpid { +namespace acl { + + AclValidator::IntPropertyType::IntPropertyType(int64_t i,int64_t j) : min(i), max(j){ + } + + bool AclValidator::IntPropertyType::validate(const std::string& val) { + int64_t v; + try + { + v = boost::lexical_cast<int64_t>(val); + }catch(const boost::bad_lexical_cast&){ + return 0; + } + + if (v < min || v >= max){ + return 0; + }else{ + return 1; + } + } + + std::string AclValidator::IntPropertyType::allowedValues() { + return "values should be between " + + boost::lexical_cast<std::string>(min) + " and " + + boost::lexical_cast<std::string>(max); + } + + AclValidator::EnumPropertyType::EnumPropertyType(std::vector<std::string>& allowed): values(allowed){ + } + + bool AclValidator::EnumPropertyType::validate(const std::string& val) { + for (std::vector<std::string>::iterator itr = values.begin(); itr != values.end(); ++itr ){ + if (val.compare(*itr) == 0){ + return 1; + } + } + + return 0; + } + + std::string AclValidator::EnumPropertyType::allowedValues() { + std::ostringstream oss; + oss << "possible values are one of { "; + for (std::vector<std::string>::iterator itr = values.begin(); itr != values.end(); itr++ ){ + oss << "'" << *itr << "' "; + } + oss << "}"; + return oss.str(); + } + + AclValidator::AclValidator() : propertyIndex(1) { + validators.insert(Validator(acl::SPECPROP_MAXQUEUESIZELOWERLIMIT, + boost::shared_ptr<PropertyType>( + new IntPropertyType(0,std::numeric_limits<int64_t>::max())))); + + validators.insert(Validator(acl::SPECPROP_MAXQUEUESIZEUPPERLIMIT, + boost::shared_ptr<PropertyType>( + new IntPropertyType(0,std::numeric_limits<int64_t>::max())))); + + validators.insert(Validator(acl::SPECPROP_MAXQUEUECOUNTLOWERLIMIT, + boost::shared_ptr<PropertyType>( + new IntPropertyType(0,std::numeric_limits<int64_t>::max())))); + + validators.insert(Validator(acl::SPECPROP_MAXQUEUECOUNTUPPERLIMIT, + boost::shared_ptr<PropertyType>( + new IntPropertyType(0,std::numeric_limits<int64_t>::max())))); + + validators.insert(Validator(acl::SPECPROP_MAXFILESIZELOWERLIMIT, + boost::shared_ptr<PropertyType>( + new IntPropertyType(0,std::numeric_limits<int64_t>::max())))); + + validators.insert(Validator(acl::SPECPROP_MAXFILESIZEUPPERLIMIT, + boost::shared_ptr<PropertyType>( + new IntPropertyType(0,std::numeric_limits<int64_t>::max())))); + + validators.insert(Validator(acl::SPECPROP_MAXFILECOUNTLOWERLIMIT, + boost::shared_ptr<PropertyType>( + new IntPropertyType(0,std::numeric_limits<int64_t>::max())))); + + validators.insert(Validator(acl::SPECPROP_MAXFILECOUNTUPPERLIMIT, + boost::shared_ptr<PropertyType>( + new IntPropertyType(0,std::numeric_limits<int64_t>::max())))); + + validators.insert(Validator(acl::SPECPROP_MAXPAGESLOWERLIMIT, + boost::shared_ptr<PropertyType>( + new IntPropertyType(0,std::numeric_limits<int64_t>::max())))); + + validators.insert(Validator(acl::SPECPROP_MAXPAGESUPPERLIMIT, + boost::shared_ptr<PropertyType>( + new IntPropertyType(0,std::numeric_limits<int64_t>::max())))); + + validators.insert(Validator(acl::SPECPROP_MAXPAGEFACTORLOWERLIMIT, + boost::shared_ptr<PropertyType>( + new IntPropertyType(0,std::numeric_limits<int64_t>::max())))); + + validators.insert(Validator(acl::SPECPROP_MAXPAGEFACTORUPPERLIMIT, + boost::shared_ptr<PropertyType>( + new IntPropertyType(0,std::numeric_limits<int64_t>::max())))); + + std::string policyTypes[] = {"ring", "self-destruct", "reject"}; + std::vector<std::string> v(policyTypes, policyTypes + sizeof(policyTypes) / sizeof(std::string)); + validators.insert(Validator(acl::SPECPROP_POLICYTYPE, + boost::shared_ptr<PropertyType>( + new EnumPropertyType(v)))); + + // Insert allowed action/object/property sets (generated manually 20140712) +#define RP registerProperties + RP( "Broker::getTimestampConfig", + "User querying message timestamp setting ", + ACT_ACCESS, OBJ_BROKER); + RP( "ExchangeHandlerImpl::query", + "AMQP 0-10 protocol received 'query' ", + ACT_ACCESS, OBJ_EXCHANGE, "name"); + RP( "ExchangeHandlerImpl::bound", + "AMQP 0-10 query binding ", + ACT_ACCESS, OBJ_EXCHANGE, "name queuename routingkey"); + RP( "ExchangeHandlerImpl::declare", + "AMQP 0-10 exchange declare ", + ACT_ACCESS, OBJ_EXCHANGE, "name type alternate durable autodelete"); + RP( "Authorise::access", + "AMQP 1.0 exchange access ", + ACT_ACCESS, OBJ_EXCHANGE, "name type durable"); + RP( "Authorise::access", + "AMQP 1.0 node resolution ", + ACT_ACCESS, OBJ_EXCHANGE, "name"); + RP( "ManagementAgent::handleMethodRequest", + "Management method request ", + ACT_ACCESS, OBJ_METHOD, "name schemapackage schemaclass"); + RP( "ManagementAgent::authorizeAgentMessage", + "Management agent method request ", + ACT_ACCESS, OBJ_METHOD, "name schemapackage schemaclass"); + RP( "ManagementAgent::handleGetQuery", + "Management agent query ", + ACT_ACCESS, OBJ_QUERY, "name schemaclass"); + RP( "Broker::queryQueue", + "QMF 'query queue' method ", + ACT_ACCESS, OBJ_QUEUE, "name"); + RP( "QueueHandlerImpl::query", + "AMQP 0-10 query ", + ACT_ACCESS, OBJ_QUEUE, "name"); + RP( "QueueHandlerImpl::declare", + "AMQP 0-10 queue declare ", + ACT_ACCESS, OBJ_QUEUE, "name alternate durable exclusive autodelete policytype maxqueuecount maxqueuesize"); + RP( "Authorise::access", + "AMQP 1.0 queue access ", + ACT_ACCESS, OBJ_QUEUE, "name alternate durable exclusive autodelete policytype maxqueuecount maxqueuesize"); + RP( "Authorise::access", + "AMQP 1.0 node resolution ", + ACT_ACCESS, OBJ_QUEUE, "name"); + RP( "Broker::bind", + "AMQP 0-10 or QMF bind request ", + ACT_BIND, OBJ_EXCHANGE, "name queuename routingkey"); + RP( "Authorise::outgoing", + "AMQP 1.0 new outgoing link from exchange", + ACT_BIND, OBJ_EXCHANGE, "name queuename routingkey"); + RP( "MessageHandlerImpl::subscribe", + "AMQP 0-10 subscribe request ", + ACT_CONSUME, OBJ_QUEUE, "name"); + RP( "Authorise::outgoing", + "AMQP 1.0 new outgoing link from queue ", + ACT_CONSUME, OBJ_QUEUE, "name"); + RP( "ConnectionHandler", + "TCP/IP connection creation ", + ACT_CREATE, OBJ_CONNECTION, "host"); + RP( "Broker::createExchange", + "Create exchange ", + ACT_CREATE, OBJ_EXCHANGE, "name type alternate durable autodelete"); + RP( "ConnectionHandler::Handler::open", + "Interbroker link creation ", + ACT_CREATE, OBJ_LINK); + RP( "Authorise::interlink", + "Interbroker link creation ", + ACT_CREATE, OBJ_LINK); + RP( "Broker::createQueue", + "Create queue ", + ACT_CREATE, OBJ_QUEUE, "name alternate durable exclusive autodelete policytype paging maxpages maxpagefactor maxqueuecount maxqueuesize maxfilecount maxfilesize"); + RP( "Broker::deleteExchange", + "Delete exchange ", + ACT_DELETE, OBJ_EXCHANGE, "name type alternate durable"); + RP( "Broker::deleteQueue", + "Delete queue ", + ACT_DELETE, OBJ_QUEUE, "name alternate durable exclusive autodelete policytype"); + RP( "Broker::queueMoveMessages", + "Management 'move queue' request ", + ACT_MOVE, OBJ_QUEUE, "name queuename"); + RP( "SemanticState::route", + "AMQP 0-10 received message processing ", + ACT_PUBLISH, OBJ_EXCHANGE, "name routingkey"); + RP( "Authorise::incoming", + "AMQP 1.0 establish sender link to queue ", + ACT_PUBLISH, OBJ_EXCHANGE, "routingkey"); + RP( "Authorise::route", + "AMQP 1.0 received message processing ", + ACT_PUBLISH, OBJ_EXCHANGE, "name routingkey"); + RP( "Queue::ManagementMethod", + "Management 'purge queue' request ", + ACT_PURGE, OBJ_QUEUE, "name"); + RP( "QueueHandlerImpl::purge", + "Management 'purge queue' request ", + ACT_PURGE, OBJ_QUEUE, "name"); + RP( "Broker::queueRedirect", + "Management 'redirect queue' request ", + ACT_REDIRECT,OBJ_QUEUE, "name queuename"); + RP( "Queue::ManagementMethod", + "Management 'reroute queue' request ", + ACT_REROUTE, OBJ_QUEUE, "name exchangename"); + RP( "Broker::unbind", + "Management 'unbind exchange' request ", + ACT_UNBIND, OBJ_EXCHANGE, "name queuename routingkey"); + RP( "Broker::setTimestampConfig", + "User modifying message timestamp setting", + ACT_UPDATE, OBJ_BROKER); + } + + AclValidator::~AclValidator(){ + } + + /* Iterate through the data model and validate the parameters. */ + void AclValidator::validate(boost::shared_ptr<AclData> d) { + + for (unsigned int cnt=0; cnt< qpid::acl::ACTIONSIZE; cnt++){ + + if (d->actionList[cnt]){ + + for (unsigned int cnt1=0; cnt1< qpid::acl::OBJECTSIZE; cnt1++){ + + if (d->actionList[cnt][cnt1]){ + + std::for_each(d->actionList[cnt][cnt1]->begin(), + d->actionList[cnt][cnt1]->end(), + boost::bind(&AclValidator::validateRuleSet, this, _1)); + } + } + } + } + } + + void AclValidator::validateRuleSet(std::pair<const std::string, qpid::acl::AclData::ruleSet>& rules){ + std::for_each(rules.second.begin(), + rules.second.end(), + boost::bind(&AclValidator::validateRule, this, _1)); + } + + void AclValidator::validateRule(qpid::acl::AclData::Rule& rule){ + std::for_each(rule.props.begin(), + rule.props.end(), + boost::bind(&AclValidator::validateProperty, this, _1)); + } + + void AclValidator::validateProperty(std::pair<const qpid::acl::SpecProperty, std::string>& prop){ + ValidatorItr itr = validators.find(prop.first); + if (itr != validators.end()){ + QPID_LOG(debug,"ACL: Found validator for property '" << acl::AclHelper::getPropertyStr(itr->first) + << "'. " << itr->second->allowedValues()); + + if (!itr->second->validate(prop.second)){ + QPID_LOG(debug, "ACL: Property failed validation. '" << prop.second << "' is not a valid value for '" + << AclHelper::getPropertyStr(prop.first) << "'"); + + throw Exception( prop.second + " is not a valid value for '" + + AclHelper::getPropertyStr(prop.first) + "', " + + itr->second->allowedValues()); + } + } + } + + /** + * validateAllowedProperties + * verify that at least one lookup definition can satisfy this + * action/object/props tuple. + * Return false and conditionally emit a warning log entry if the + * incoming definition can not be matched. + */ + bool AclValidator::validateAllowedProperties(qpid::acl::Action action, + qpid::acl::ObjectType object, + const AclData::specPropertyMap& props, + bool emitLog) const { + // No rules defined means no match + if (!allowedSpecProperties[action][object].get()) { + if (emitLog) { + QPID_LOG(warning, "ACL rule ignored: Broker never checks for rules with action: '" + << AclHelper::getActionStr(action) << "' and object: '" + << AclHelper::getObjectTypeStr(object) << "'"); + } + return false; + } + // two empty property sets is a match + if (allowedSpecProperties[action][object]->size() == 0) { + if ((props.size() == 0) || + (props.size() == 1 && props.find(acl::SPECPROP_NAME) != props.end())) { + return true; + } + } + // Scan vector of rules looking for one that matches all properties + bool validRuleFound = false; + for (std::vector<AclData::Rule>::const_iterator + ruleItr = allowedSpecProperties[action][object]->begin(); + ruleItr != allowedSpecProperties[action][object]->end() && !validRuleFound; + ruleItr++) { + // Scan one rule + validRuleFound = true; + for(AclData::specPropertyMapItr itr = props.begin(); + itr != props.end(); + itr++) { + if ((*itr).first != acl::SPECPROP_NAME && + ruleItr->props.find((*itr).first) == + ruleItr->props.end()) { + // Test property not found in this rule + validRuleFound = false; + break; + } + } + } + if (!validRuleFound) { + if (emitLog) { + QPID_LOG(warning, "ACL rule ignored: Broker checks for rules with action: '" + << AclHelper::getActionStr(action) << "' and object: '" + << AclHelper::getObjectTypeStr(object) + << "' but will never match with property set: " + << AclHelper::propertyMapToString(&props)); + } + return false; + } + return true; + } + + /** + * Return a list of indexes of definitions that this lookup might match + */ + void AclValidator::findPossibleLookupMatch(qpid::acl::Action action, + qpid::acl::ObjectType object, + const AclData::specPropertyMap& props, + std::vector<int>& result) const { + if (!allowedSpecProperties[action][object].get()) { + return; + } else { + // Scan vector of rules returning the indexes of all that match + bool validRuleFound; + for (std::vector<AclData::Rule>::const_iterator + ruleItr = allowedSpecProperties[action][object]->begin(); + ruleItr != allowedSpecProperties[action][object]->end(); + ruleItr++) { + // Scan one rule + validRuleFound = true; + for(AclData::specPropertyMapItr + itr = props.begin(); itr != props.end(); itr++) { + if ((*itr).first != acl::SPECPROP_NAME && + ruleItr->props.find((*itr).first) == + ruleItr->props.end()) { + // Test property not found in this rule + validRuleFound = false; + break; + } + } + if (validRuleFound) { + result.push_back(ruleItr->rawRuleNum); + } + } + } + return; + } + + /** + * Emit trace log of original property definitions + */ + void AclValidator::tracePropertyDefs() { + QPID_LOG(trace, "ACL: Definitions of action, object, (allowed properties) lookups"); + for (int iA=0; iA<acl::ACTIONSIZE; iA++) { + for (int iO=0; iO<acl::OBJECTSIZE; iO++) { + if (allowedSpecProperties[iA][iO].get()) { + for (std::vector<AclData::Rule>::const_iterator + ruleItr = allowedSpecProperties[iA][iO]->begin(); + ruleItr != allowedSpecProperties[iA][iO]->end(); + ruleItr++) { + std::string pstr; + for (AclData::specPropertyMapItr pMItr = ruleItr->props.begin(); + pMItr != ruleItr->props.end(); + pMItr++) { + pstr += AclHelper::getPropertyStr((SpecProperty) pMItr-> first); + pstr += ","; + } + QPID_LOG(trace, "ACL: Lookup " + << std::setfill(' ') << std::setw(2) + << ruleItr->rawRuleNum << ": " + << ruleItr->lookupHelp << " " + << std::setfill(' ') << std::setw(acl::ACTION_STR_WIDTH +1) << std::left + << AclHelper::getActionStr(acl::Action(iA)) + << std::setfill(' ') << std::setw(acl::OBJECTTYPE_STR_WIDTH) << std::left + << AclHelper::getObjectTypeStr(acl::ObjectType(iO)) + << " (" << pstr.substr(0, pstr.length()-1) << ")"); + } + } + } + } + } + + /** + * Construct a record of all the calls that the broker will + * make to acl::authorize and the properties for each call. + * From that create the list of all the spec properties that + * users are then allowed to specify in acl rule files. + */ + void AclValidator::registerProperties( + const std::string& source, + const std::string& description, + Action action, + ObjectType object, + const std::string& properties) { + if (!allowedProperties[action][object].get()) { + boost::shared_ptr<std::set<Property> > t1(new std::set<Property>()); + allowedProperties[action][object] = t1; + boost::shared_ptr<std::vector<AclData::Rule> > t2(new std::vector<AclData::Rule>()); + allowedSpecProperties[action][object] = t2; + } + std::vector<std::string> props = split(properties, " "); + AclData::specPropertyMap spm; + for (size_t i=0; i<props.size(); i++) { + Property prop = AclHelper::getProperty(props[i]); + allowedProperties[action][object]->insert(prop); + // Given that the broker will be calling with this property, + // determine what user rule settings are allowed. + switch (prop) { + // Cases where broker and Acl file share property name and meaning + case PROP_NAME: + spm[SPECPROP_NAME]=""; + break; + case PROP_DURABLE: + spm[SPECPROP_DURABLE]=""; + break; + case PROP_OWNER: + spm[SPECPROP_OWNER]=""; + break; + case PROP_ROUTINGKEY: + spm[SPECPROP_ROUTINGKEY]=""; + break; + case PROP_AUTODELETE: + spm[SPECPROP_AUTODELETE]=""; + break; + case PROP_EXCLUSIVE: + spm[SPECPROP_EXCLUSIVE]=""; + break; + case PROP_TYPE: + spm[SPECPROP_TYPE]=""; + break; + case PROP_ALTERNATE: + spm[SPECPROP_ALTERNATE]=""; + break; + case PROP_QUEUENAME: + spm[SPECPROP_QUEUENAME]=""; + break; + case PROP_EXCHANGENAME: + spm[SPECPROP_EXCHANGENAME]=""; + break; + case PROP_SCHEMAPACKAGE: + spm[SPECPROP_SCHEMAPACKAGE]=""; + break; + case PROP_SCHEMACLASS: + spm[SPECPROP_SCHEMACLASS]=""; + break; + case PROP_POLICYTYPE: + spm[SPECPROP_POLICYTYPE]=""; + break; + case PROP_PAGING: + spm[SPECPROP_PAGING]=""; + break; + case PROP_HOST: + spm[SPECPROP_HOST]=""; + break; + // Cases where broker supplies a property but Acl has upper/lower limit for it + case PROP_MAXPAGES: + spm[SPECPROP_MAXPAGESLOWERLIMIT]=""; + spm[SPECPROP_MAXPAGESUPPERLIMIT]=""; + break; + case PROP_MAXPAGEFACTOR: + spm[SPECPROP_MAXPAGEFACTORLOWERLIMIT]=""; + spm[SPECPROP_MAXPAGEFACTORUPPERLIMIT]=""; + break; + case PROP_MAXQUEUESIZE: + spm[SPECPROP_MAXQUEUESIZELOWERLIMIT]=""; + spm[SPECPROP_MAXQUEUESIZEUPPERLIMIT]=""; + break; + case PROP_MAXQUEUECOUNT: + spm[SPECPROP_MAXQUEUECOUNTLOWERLIMIT]=""; + spm[SPECPROP_MAXQUEUECOUNTUPPERLIMIT]=""; + break; + case PROP_MAXFILESIZE: + spm[SPECPROP_MAXFILESIZELOWERLIMIT]=""; + spm[SPECPROP_MAXFILESIZEUPPERLIMIT]=""; + break; + case PROP_MAXFILECOUNT: + spm[SPECPROP_MAXFILECOUNTLOWERLIMIT]=""; + spm[SPECPROP_MAXFILECOUNTUPPERLIMIT]=""; + break; + default: + throw Exception( "acl::RegisterProperties no case for property: " + + AclHelper::getPropertyStr(prop) ); + } + } + AclData::Rule someProps(propertyIndex, acl::ALLOW, spm, source, description); + propertyIndex++; + allowedSpecProperties[action][object]->push_back(someProps); + } + +}} diff --git a/qpid/cpp/src/qpid/acl/AclValidator.h b/qpid/cpp/src/qpid/acl/AclValidator.h new file mode 100644 index 0000000000..8f555797c2 --- /dev/null +++ b/qpid/cpp/src/qpid/acl/AclValidator.h @@ -0,0 +1,106 @@ +#ifndef QPID_ACL_ACLVALIDATOR_H +#define QPID_ACL_ACLVALIDATOR_H + + +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed 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. + * + */ + +#include "qpid/broker/AclModule.h" +#include "qpid/acl/AclData.h" +#include "qpid/sys/IntegerTypes.h" +#include <boost/shared_ptr.hpp> +#include <boost/concept_check.hpp> +#include <vector> +#include <sstream> + +namespace qpid { +namespace acl { + +class AclValidator { + + /* Base Property */ + class PropertyType{ + + public: + virtual ~PropertyType(){}; + virtual bool validate(const std::string& val)=0; + virtual std::string allowedValues()=0; + }; + + class IntPropertyType : public PropertyType{ + int64_t min; + int64_t max; + + public: + IntPropertyType(int64_t min,int64_t max); + virtual ~IntPropertyType (){}; + virtual bool validate(const std::string& val); + virtual std::string allowedValues(); + }; + + class EnumPropertyType : public PropertyType{ + std::vector<std::string> values; + + public: + EnumPropertyType(std::vector<std::string>& allowed); + virtual ~EnumPropertyType (){}; + virtual bool validate(const std::string& val); + virtual std::string allowedValues(); + }; + + typedef std::pair<acl::SpecProperty,boost::shared_ptr<PropertyType> > Validator; + typedef std::map<acl::SpecProperty,boost::shared_ptr<PropertyType> > ValidatorMap; + typedef ValidatorMap::iterator ValidatorItr; + typedef boost::shared_ptr<std::set<Property> > AllowedProperties [ACTIONSIZE][OBJECTSIZE]; + typedef boost::shared_ptr<std::vector<AclData::Rule> > AllowedSpecProperties[ACTIONSIZE][OBJECTSIZE]; + + ValidatorMap validators; + AllowedProperties allowedProperties; + AllowedSpecProperties allowedSpecProperties; + +public: + + void validateRuleSet(std::pair<const std::string, qpid::acl::AclData::ruleSet>& rules); + void validateRule(qpid::acl::AclData::Rule& rule); + void validateProperty(std::pair<const qpid::acl::SpecProperty, std::string>& prop); + void validate(boost::shared_ptr<AclData> d); + bool validateAllowedProperties(qpid::acl::Action action, + qpid::acl::ObjectType object, + const AclData::specPropertyMap& props, + bool emitLog) const; + void findPossibleLookupMatch(qpid::acl::Action action, + qpid::acl::ObjectType object, + const AclData::specPropertyMap& props, + std::vector<int>& result) const; + void tracePropertyDefs(); + + AclValidator(); + ~AclValidator(); + +private: + void registerProperties(const std::string& source, + const std::string& description, + Action action, + ObjectType object, + const std::string& properties = ""); + int propertyIndex; +}; + +}} // namespace qpid::acl + +#endif // QPID_ACL_ACLVALIDATOR_H diff --git a/qpid/cpp/src/qpid/acl/management-schema.xml b/qpid/cpp/src/qpid/acl/management-schema.xml new file mode 100644 index 0000000000..2ac20bb324 --- /dev/null +++ b/qpid/cpp/src/qpid/acl/management-schema.xml @@ -0,0 +1,85 @@ +<schema package="org.apache.qpid.acl"> + +<!-- + * Copyright (c) 2008 The Apache Software Foundation + * + * Licensed 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. +--> + + <class name="Acl"> + <property name="brokerRef" type="objId" references="org.apache.qpid.broker:Broker" access="RO" index="y" parentRef="y"/> + <property name="policyFile" type="lstr" access="RO" desc="Name of the policy file"/> + <property name="enforcingAcl" type="bool" access="RO" desc="Currently Enforcing ACL"/> + <property name="transferAcl" type="bool" access="RO" desc="Any transfer ACL rules in force"/> + <property name="lastAclLoad" type="absTime" access="RO" desc="Timestamp of last successful load of ACL"/> + <property name="maxConnections" type="uint16" access="RO" desc="Maximum allowed connections"/> + <property name="maxConnectionsPerIp" type="uint16" access="RO" desc="Maximum allowed connections"/> + <property name="maxConnectionsPerUser" type="uint16" access="RO" desc="Maximum allowed connections"/> + <property name="maxQueuesPerUser" type="uint16" access="RO" desc="Maximum allowed queues"/> + <statistic name="aclDenyCount" type="count64" unit="request" desc="Number of ACL requests denied"/> + <statistic name="connectionDenyCount" type="count64" unit="connection" desc="Number of connections denied"/> + <statistic name="queueQuotaDenyCount" type="count64" unit="queue" desc="Number of queue creations denied"/> + + <method name="reloadACLFile" desc="Reload the ACL file"/> + + <!-- + Lookup is a general object lookup + User Name + Action + Object + Object Name + Property Map consisting of <"name" "value"> string pairs. + --> + <method name="Lookup" desc="Lookup: user action object [objectName [propertyMap]]"> + <arg name="userId" dir="I" type="lstr"/> + <arg name="action" dir="I" type="lstr"/> + <arg name="object" dir="I" type="lstr"/> + <arg name="objectName" dir="I" type="lstr"/> + <arg name="propertyMap" dir="I" type="map"/> + <arg name="result" dir="O" type="lstr"/> + </method> + + <!-- + LookupPublish is a specific lookup for a PUBLISH EXCHANGE fastpath + User Name + Exchange Name + Routing Key + --> + <method name="LookupPublish" desc="Lookup PUBLISH EXCHANGE: user exchangeName routingKey"> + <arg name="userId" dir="I" type="lstr"/> + <arg name="exchangeName" dir="I" type="lstr"/> + <arg name="routingKey" dir="I" type="lstr"/> + <arg name="result" dir="O" type="lstr"/> + </method> + + </class> + + <eventArguments> + <arg name="action" type="sstr"/> + <arg name="arguments" type="map"/> + <arg name="objectName" type="sstr"/> + <arg name="objectType" type="sstr"/> + <arg name="reason" type="lstr"/> + <arg name="userId" type="sstr"/> + <arg name="clientAddr" type="sstr"/> + <arg name="queueName" type="sstr"/> + </eventArguments> + + <event name="allow" sev="inform" args="userId, action, objectType, objectName, arguments"/> + <event name="deny" sev="notice" args="userId, action, objectType, objectName, arguments"/> + <event name="connectionDeny" sev="notice" args="userId, clientAddr"/> + <event name="queueQuotaDeny" sev="notice" args="userId, queueName"/> + <event name="fileLoaded" sev="inform" args="userId"/> + <event name="fileLoadFailed" sev="error" args="userId, reason"/> + +</schema> |