diff options
Diffstat (limited to 'qpid/cpp/src')
| -rw-r--r-- | qpid/cpp/src/qpid/acl/Acl.cpp | 29 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/acl/AclConnectionCounter.cpp | 85 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/acl/AclConnectionCounter.h | 10 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/acl/AclData.cpp | 103 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/acl/AclData.h | 46 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/acl/AclPlugin.cpp | 2 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/acl/AclReader.cpp | 151 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/acl/AclReader.h | 12 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/acl/AclTopicMatch.h | 2 | ||||
| -rwxr-xr-x | qpid/cpp/src/tests/acl.py | 222 |
10 files changed, 566 insertions, 96 deletions
diff --git a/qpid/cpp/src/qpid/acl/Acl.cpp b/qpid/cpp/src/qpid/acl/Acl.cpp index 61e0b56104..31ad9a38ac 100644 --- a/qpid/cpp/src/qpid/acl/Acl.cpp +++ b/qpid/cpp/src/qpid/acl/Acl.cpp @@ -24,6 +24,7 @@ #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" @@ -56,6 +57,15 @@ Acl::Acl (AclValues& av, Broker& b): aclValues(av), broker(&b), transferAcl(fals connectionCounter(new ConnectionCounter(*this, aclValues.aclMaxConnectPerUser, aclValues.aclMaxConnectPerIp, aclValues.aclMaxConnectTotal)), resourceCounter(new ResourceCounter(*this, aclValues.aclMaxQueuesPerUser)){ + 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::getMaxConnectSpecStr()); + agent = broker->getManagementAgent(); if (agent != 0){ @@ -138,7 +148,18 @@ bool Acl::authorise( bool Acl::approveConnection(const qpid::broker::Connection& conn) { - return connectionCounter->approveConnection(conn); + const std::string& userName(conn.getUserId()); + uint16_t connectionLimit(0); + + boost::shared_ptr<AclData> dataLocal; + { + Mutex::ScopedLock locker(dataLock); + dataLocal = data; //rcu copy + } + + bool enforcingConnQuotas = dataLocal->getConnQuotaForUser(userName, &connectionLimit); + + return connectionCounter->approveConnection(conn, enforcingConnQuotas, connectionLimit); } bool Acl::approveCreateQueue(const std::string& userId, const std::string& queueName) @@ -207,7 +228,7 @@ bool Acl::readAclFile(std::string& errorText) bool Acl::readAclFile(std::string& aclFile, std::string& errorText) { boost::shared_ptr<AclData> d(new AclData); - AclReader ar; + AclReader ar(aclValues.aclMaxConnectPerUser); if (ar.read(aclFile, d)){ agent->raiseEvent(_qmf::EventFileLoadFailed("", ar.getError())); errorText = ar.getError(); @@ -228,6 +249,10 @@ bool Acl::readAclFile(std::string& aclFile, std::string& errorText) { QPID_LOG(debug,"ACL: Transfer ACL is Enabled!"); } + if (data->enforcingConnectionQuotas()){ + QPID_LOG(debug, "ACL: Connection quotas are Enabled."); + } + data->aclSource = aclFile; if (mgmtObject!=0){ mgmtObject->set_transferAcl(transferAcl?1:0); diff --git a/qpid/cpp/src/qpid/acl/AclConnectionCounter.cpp b/qpid/cpp/src/qpid/acl/AclConnectionCounter.cpp index 195d8bee28..875137bf55 100644 --- a/qpid/cpp/src/qpid/acl/AclConnectionCounter.cpp +++ b/qpid/cpp/src/qpid/acl/AclConnectionCounter.cpp @@ -85,32 +85,32 @@ bool ConnectionCounter::limitApproveLH( // // countConnectionLH // -// Increment the name's count in map and return a comparison against the limit. -// called with dataLock already taken +// 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 emitLog, + bool enforceLimit) { bool result(true); uint16_t count(0); - if (theLimit > 0) { - connectCountsMap_t::iterator eRef = theMap.find(theName); - if (eRef != theMap.end()) { - count = (uint16_t)(*eRef).second + 1; - (*eRef).second = count; - result = count <= theLimit; - } else { - theMap[theName] = count = 1; - } - if (emitLog) { - QPID_LOG(trace, "ACL ConnectionApprover user=" << theName - << " limit=" << theLimit - << " curValue=" << count - << " result=" << (result ? "allow" : "deny")); - } + connectCountsMap_t::iterator eRef = theMap.find(theName); + if (eRef != theMap.end()) { + count = (uint16_t)(*eRef).second + 1; + (*eRef).second = count; + result = (enforceLimit ? count <= theLimit : true); + } else { + theMap[theName] = count = 1; + } + if (emitLog) { + QPID_LOG(trace, "ACL ConnectionApprover user=" << theName + << " limit=" << theLimit + << " curValue=" << count + << " result=" << (result ? "allow" : "deny")); } return result; } @@ -123,23 +123,21 @@ bool ConnectionCounter::countConnectionLH( // called with dataLock already taken // void ConnectionCounter::releaseLH( - connectCountsMap_t& theMap, const std::string& theName, uint16_t theLimit) { - - if (theLimit > 0) { - 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; - } + 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 { - // User had no connections. - QPID_LOG(notice, "ACL ConnectionCounter Connection for '" << theName - << "' not found in connection count pool"); + (*eRef).second = count - 1; } + } else { + // User had no connections. + QPID_LOG(notice, "ACL ConnectionCounter Connection for '" << theName + << "' not found in connection count pool"); } } @@ -161,7 +159,7 @@ void ConnectionCounter::connection(broker::Connection& connection) { connectProgressMap[connection.getMgmtId()] = C_CREATED; // Count the connection from this host. - (void) countConnectionLH(connectByHostMap, hostName, hostLimit, false); + (void) countConnectionLH(connectByHostMap, hostName, hostLimit, false, false); } @@ -180,8 +178,7 @@ void ConnectionCounter::closed(broker::Connection& connection) { // Normal case: connection was created and opened. // Decrement user in-use counts releaseLH(connectByNameMap, - connection.getUserId(), - nameLimit); + connection.getUserId()); } else { // Connection was created but not opened. // Don't decrement user count. @@ -189,8 +186,7 @@ void ConnectionCounter::closed(broker::Connection& connection) { // Decrement host in-use count. releaseLH(connectByHostMap, - getClientHost(connection.getMgmtId()), - hostLimit); + getClientHost(connection.getMgmtId())); // destroy connection progress indicator connectProgressMap.erase(eRef); @@ -211,7 +207,10 @@ void ConnectionCounter::closed(broker::Connection& connection) { // check total connections, connections from IP, connections by user and // disallow if over any limit // -bool ConnectionCounter::approveConnection(const broker::Connection& connection) +bool ConnectionCounter::approveConnection( + const broker::Connection& connection, + bool enforcingConnectionQuotas, + uint16_t connectionUserQuota ) { const std::string& hostName(getClientHost(connection.getMgmtId())); const std::string& userName( connection.getUserId()); @@ -220,7 +219,7 @@ bool ConnectionCounter::approveConnection(const broker::Connection& connection) // Bump state from CREATED to OPENED (void) countConnectionLH(connectProgressMap, connection.getMgmtId(), - C_OPENED, false); + C_OPENED, false, false); // Approve total connections bool okTotal = true; @@ -235,7 +234,9 @@ bool ConnectionCounter::approveConnection(const broker::Connection& connection) bool okByIP = limitApproveLH(connectByHostMap, hostName, hostLimit, true); // Count and Approve the connection by the user - bool okByUser = countConnectionLH(connectByNameMap, userName, nameLimit, true); + bool okByUser = countConnectionLH(connectByNameMap, userName, + connectionUserQuota, true, + enforcingConnectionQuotas); // Emit separate log for each disapproval if (!okTotal) { @@ -252,7 +253,7 @@ bool ConnectionCounter::approveConnection(const broker::Connection& connection) } if (!okByUser) { QPID_LOG(error, "Client max per-user connection count limit of " - << nameLimit << " exceeded by '" + << connectionUserQuota << " exceeded by '" << connection.getMgmtId() << "', user: '" << userName << "'. Connection refused."); } diff --git a/qpid/cpp/src/qpid/acl/AclConnectionCounter.h b/qpid/cpp/src/qpid/acl/AclConnectionCounter.h index eec8e90256..e8ef35c1ba 100644 --- a/qpid/cpp/src/qpid/acl/AclConnectionCounter.h +++ b/qpid/cpp/src/qpid/acl/AclConnectionCounter.h @@ -77,12 +77,12 @@ private: bool countConnectionLH(connectCountsMap_t& theMap, const std::string& theName, uint16_t theLimit, - bool emitLog); + bool emitLog, + bool enforceLimit); /** Release a connection */ void releaseLH(connectCountsMap_t& theMap, - const std::string& theName, - uint16_t theLimit); + const std::string& theName); public: ConnectionCounter(Acl& acl, uint16_t nl, uint16_t hl, uint16_t tl); @@ -93,7 +93,9 @@ public: void closed(broker::Connection& connection); // Connection counting - bool approveConnection(const broker::Connection& conn); + bool approveConnection(const broker::Connection& conn, + bool enforcingConnectionQuotas, + uint16_t connectionLimit ); }; }} // namespace qpid::ha diff --git a/qpid/cpp/src/qpid/acl/AclData.cpp b/qpid/cpp/src/qpid/acl/AclData.cpp index 997fbdf2eb..ca866ab7d3 100644 --- a/qpid/cpp/src/qpid/acl/AclData.cpp +++ b/qpid/cpp/src/qpid/acl/AclData.cpp @@ -25,11 +25,19 @@ namespace qpid { namespace acl { // - // Instantiate the substitution keyword string + // Instantiate the keyword strings // - const std::string AclData::USER_SUBSTITUTION_KEYWORD = "${user}"; - const std::string AclData::DOMAIN_SUBSTITUTION_KEYWORD = "${domain}"; - const std::string AclData::USERDOMAIN_SUBSTITUTION_KEYWORD = "${userdomain}"; + 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 char AclData::ACL_SYMBOL_WILDCARD = '*'; + const std::string AclData::ACL_KEYWORD_WILDCARD = "*"; + const char AclData::ACL_SYMBOL_LINE_CONTINUATION = '\\'; // // constructor @@ -37,7 +45,9 @@ namespace acl { AclData::AclData(): decisionMode(qpid::acl::DENY), transferAcl(false), - aclSource("UNKNOWN") + aclSource("UNKNOWN"), + connQuotaRulesExist(false), + connQuotaRuleSettings(new quotaRuleSet) { for (unsigned int cnt=0; cnt< qpid::acl::ACTIONSIZE; cnt++) { @@ -60,6 +70,9 @@ namespace acl { } delete[] actionList[cnt]; } + transferAcl = false; + connQuotaRulesExist = false; + connQuotaRuleSettings->clear(); } @@ -73,7 +86,7 @@ namespace acl { const std::string& lookupStr) { // allow wildcard on the end of rule strings... - if (ruleStr.data()[ruleStr.size()-1]=='*') + if (ruleStr.data()[ruleStr.size()-1]==ACL_SYMBOL_WILDCARD) { return ruleStr.compare(0, ruleStr.size()-1, @@ -124,7 +137,7 @@ namespace acl { // If individual actorId not found then find a rule set for '*'. if (itrRule == actionList[action][objType]->end()) - itrRule = actionList[action][objType]->find("*"); + itrRule = actionList[action][objType]->find(ACL_KEYWORD_WILDCARD); if (itrRule != actionList[action][objType]->end()) { @@ -390,7 +403,7 @@ namespace acl { AclData::actObjItr itrRule = actionList[action][objType]->find(id); if (itrRule == actionList[action][objType]->end()) - itrRule = actionList[action][objType]->find("*"); + itrRule = actionList[action][objType]->find(ACL_KEYWORD_WILDCARD); if (itrRule != actionList[action][objType]->end() ) { @@ -436,9 +449,9 @@ namespace acl { if (match && rsItr->pubRoutingKeyInRule) { - if ((routingKey.find(USER_SUBSTITUTION_KEYWORD, 0) != std::string::npos) || - (routingKey.find(DOMAIN_SUBSTITUTION_KEYWORD, 0) != std::string::npos) || - (routingKey.find(USERDOMAIN_SUBSTITUTION_KEYWORD, 0) != std::string::npos)) + 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 << @@ -489,6 +502,62 @@ namespace acl { } + + // + // + // + void AclData::setConnQuotaRuleSettings ( + bool rulesExist, boost::shared_ptr<quotaRuleSet> quotaPtr) + { + connQuotaRulesExist = rulesExist; + 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 (connQuotaRulesExist) { + // 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 connQuotaRulesExist; + } + // // // @@ -656,9 +725,9 @@ namespace acl { domain = normalizeUserId(userId.substr(locDomSeparator+1)); } - substituteString(ruleString, USER_SUBSTITUTION_KEYWORD, user); - substituteString(ruleString, DOMAIN_SUBSTITUTION_KEYWORD, domain); - substituteString(ruleString, USERDOMAIN_SUBSTITUTION_KEYWORD, userdomain); + substituteString(ruleString, ACL_KEYWORD_USER_SUBST, user); + substituteString(ruleString, ACL_KEYWORD_DOMAIN_SUBST, domain); + substituteString(ruleString, ACL_KEYWORD_USERDOMAIN_SUBST, userdomain); } @@ -689,8 +758,8 @@ namespace acl { domain = normalizeUserId(userId.substr(locDomSeparator+1)); } std::string oRule(ruleString); - substituteString(ruleString, userdomain, USERDOMAIN_SUBSTITUTION_KEYWORD); - substituteString(ruleString, user, USER_SUBSTITUTION_KEYWORD); - substituteString(ruleString, domain, DOMAIN_SUBSTITUTION_KEYWORD); + 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 index b4b13c44b6..43cb5193f5 100644 --- a/qpid/cpp/src/qpid/acl/AclData.h +++ b/qpid/cpp/src/qpid/acl/AclData.h @@ -111,6 +111,8 @@ public: 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; // Action*[] -> Object*[] -> map<user -> set<Rule> > aclAction* actionList[qpid::acl::ACTIONSIZE]; @@ -134,9 +136,18 @@ public: bool matchProp(const std::string & src, const std::string& src1); void clear (); - static const std::string USER_SUBSTITUTION_KEYWORD; - static const std::string DOMAIN_SUBSTITUTION_KEYWORD; - static const std::string USERDOMAIN_SUBSTITUTION_KEYWORD; + 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 char ACL_SYMBOL_WILDCARD; + static const std::string ACL_KEYWORD_WILDCARD; + static const char ACL_SYMBOL_LINE_CONTINUATION; + void substituteString(std::string& targetString, const std::string& placeholder, const std::string& replacement); @@ -146,6 +157,31 @@ public: void substituteKeywords(std::string& ruleString, const std::string& userId); + // Per user connection quotas extracted from acl rule file + // Set by reader + void setConnQuotaRuleSettings (bool, boost::shared_ptr<quotaRuleSet>); + // Get by connection approvers + bool enforcingConnectionQuotas() { return connQuotaRulesExist; } + bool getConnQuotaForUser(const std::string&, uint16_t*) const; + + /** 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"; + } + + AclData(); virtual ~AclData(); @@ -157,6 +193,10 @@ private: bool compareIntMin(const qpid::acl::SpecProperty theProperty, const std::string theAclValue, const std::string theLookupValue); + + // Per-user connection quota + bool connQuotaRulesExist; + boost::shared_ptr<quotaRuleSet> connQuotaRuleSettings; // Map of user-to-N values from rule file }; }} // namespace qpid::acl diff --git a/qpid/cpp/src/qpid/acl/AclPlugin.cpp b/qpid/cpp/src/qpid/acl/AclPlugin.cpp index 64bf040309..c666eb5420 100644 --- a/qpid/cpp/src/qpid/acl/AclPlugin.cpp +++ b/qpid/cpp/src/qpid/acl/AclPlugin.cpp @@ -42,8 +42,8 @@ struct AclOptions : public Options { values.aclMaxConnectTotal = 500; addOptions() ("acl-file", optValue(values.aclFile, "FILE"), "The policy file to load from, loaded from data dir") - ("max-connections" , optValue(values.aclMaxConnectTotal, "N"), "The maximum combined number of connections allowed. 0 implies no limit.") ("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.") ; diff --git a/qpid/cpp/src/qpid/acl/AclReader.cpp b/qpid/cpp/src/qpid/acl/AclReader.cpp index 3d5a6662db..7eb9b82c64 100644 --- a/qpid/cpp/src/qpid/acl/AclReader.cpp +++ b/qpid/cpp/src/qpid/acl/AclReader.cpp @@ -24,6 +24,7 @@ #include <sstream> #include "qpid/log/Statement.h" #include "qpid/Exception.h" +#include <boost/lexical_cast.hpp> #include <iomanip> // degug #include <iostream> // debug @@ -95,7 +96,7 @@ namespace acl { << cnt << " " << (*i)->toString()); if (!foundmode && (*i)->actionAll && (*i)->names.size() == 1 - && (*((*i)->names.begin())).compare("*") == 0) { + && (*((*i)->names.begin())).compare(AclData::ACL_KEYWORD_WILDCARD) == 0) { d->decisionMode = (*i)->res; QPID_LOG(debug, "ACL: FoundMode " << AclHelper::getAclResultStr(d->decisionMode)); @@ -105,9 +106,9 @@ namespace acl { // Record which properties have the user substitution string for (pmCitr pItr=rule.props.begin(); pItr!=rule.props.end(); pItr++) { - if ((pItr->second.find(AclData::USER_SUBSTITUTION_KEYWORD, 0) != std::string::npos) || - (pItr->second.find(AclData::DOMAIN_SUBSTITUTION_KEYWORD, 0) != std::string::npos) || - (pItr->second.find(AclData::USERDOMAIN_SUBSTITUTION_KEYWORD, 0) != std::string::npos)) { + 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; } } @@ -167,7 +168,7 @@ namespace acl { // add users and Rule to object set bool allNames = false; // check to see if names.begin is '*' - if ((*(*i)->names.begin()).compare("*") == 0) + if ((*(*i)->names.begin()).compare(AclData::ACL_KEYWORD_WILDCARD) == 0) allNames = true; for (nsCitr itr = (allNames ? names.begin() : (*i)->names.begin()); @@ -199,7 +200,7 @@ namespace acl { objstr << AclHelper::getObjectTypeStr((ObjectType) ocnt) << ","; } - bool allNames = ((*(*i)->names.begin()).compare("*") == 0); + 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()); @@ -218,12 +219,15 @@ namespace acl { << "}" ); } } + + // connection quota + d->setConnQuotaRuleSettings(connQuotaRulesExist, connQuota); } void AclReader::aclRule::processName(const std::string& name, const groupMap& groups) { - if (name.compare("all") == 0) { - names.insert("*"); + if (name.compare(AclData::ACL_KEYWORD_ALL) == 0) { + names.insert(AclData::ACL_KEYWORD_WILDCARD); } else { gmCitr itr = groups.find(name); if (itr == groups.end()) { @@ -234,9 +238,13 @@ namespace acl { } } - AclReader::AclReader() : lineNumber(0), contFlag(false), validationMap(new AclHelper::objectMap) { + AclReader::AclReader(uint16_t theCliMaxConnPerUser) : lineNumber(0), contFlag(false), + validationMap(new AclHelper::objectMap), + cliMaxConnPerUser (theCliMaxConnPerUser), + connQuotaRulesExist(false), + connQuota(new AclData::quotaRuleSet) { AclHelper::loadValidationMap(validationMap); - names.insert("*"); + names.insert(AclData::ACL_KEYWORD_WILDCARD); } AclReader::~AclReader() {} @@ -254,6 +262,11 @@ namespace acl { 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) { + (*connQuota)[AclData::ACL_KEYWORD_ACL] = cliMaxConnPerUser; + } + // Loop to process the Acl file try { bool err = false; while (ifs.good()) { @@ -282,6 +295,7 @@ namespace acl { } printNames(); printRules(); + printConnectionQuotas(); loadDecisionData(d); return 0; @@ -292,7 +306,7 @@ namespace acl { std::vector<std::string> toks; // Check for continuation - char* contCharPtr = std::strrchr(line, '\\'); + char* contCharPtr = std::strrchr(line, AclData::ACL_SYMBOL_LINE_CONTINUATION); bool cont = contCharPtr != 0; if (cont) *contCharPtr = 0; @@ -303,10 +317,12 @@ namespace acl { return false; } - if (numToks && (toks[0].compare("group") == 0 || contFlag)) { + if (numToks && (toks[0].compare(AclData::ACL_KEYWORD_GROUP) == 0 || contFlag)) { ret = processGroupLine(toks, cont); - } else if (numToks && toks[0].compare("acl") == 0) { + } 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; @@ -317,7 +333,10 @@ namespace acl { ret = true; } else { errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber - << ", Non-continuation line must start with \"group\" or \"acl\"."; + << ", Non-continuation line must start with \"" + << AclData::ACL_KEYWORD_GROUP << "\", \"" + << AclData::ACL_KEYWORD_ACL << "\". or \"" + << AclData::ACL_KEYWORD_QUOTA << "\"."; ret = false; } } @@ -337,6 +356,102 @@ namespace acl { 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) { + return processQuotaConnLine(toks); + } else { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Quota type \"" << toks[1] << "\" unrecognized."; + return false; + } + } + + + // Process 'quota connections' rule lines + // Return true if the line is successfully processed without errors + bool AclReader::processQuotaConnLine(tokList& toks) { + const unsigned toksSize = toks.size(); + + uint16_t nConns(0); + try { + nConns = boost::lexical_cast<uint16_t>(toks[2]); + } catch(const boost::bad_lexical_cast&) { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Connection quota value \"" << toks[2] + << "\" cannot be converted to a 16-bit unsigned integer."; + return false; + } + + // limit check the connection setting + if (nConns > AclData::getConnectMaxSpec()) + { + errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber + << ", Connection quota value \"" << toks[2] + << "\" exceeds maximum configuration setting of " + << AclData::getConnectMaxSpec(); + return false; + } + + // Apply the connection count 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 + (*connQuota)[toks[idx]] = nConns; + } else { + if (!processQuotaConnGroup(toks[idx], nConns)) + return false; + } + } + return true; + } + + + // Process 'quota connections' group expansion + // Return true if the quota is applied to all members of the group + bool AclReader::processQuotaConnGroup(const std::string& theGroup, uint16_t theQuota) { + 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()) { + (*connQuota)[*gni] = theQuota; + } else { + if (!processQuotaConnGroup(*gni, theQuota)) + return false; + } + } + return true; + } + + + void AclReader::printConnectionQuotas() const { + QPID_LOG(debug, "ACL: connection quota: " << (*connQuota).size() << " rules found:"); + int cnt = 1; + for (AclData::quotaRuleSetItr itr=(*connQuota).begin(); + itr != (*connQuota).end(); + ++itr,++cnt) { + QPID_LOG(debug, "ACL: quota " << cnt << " : " << (*itr).second + << " connections 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) { @@ -462,8 +577,8 @@ namespace acl { return false; } - bool actionAllFlag = toks[3].compare("all") == 0; - bool userAllFlag = toks[2].compare("all") == 0; + bool actionAllFlag = toks[3].compare(AclData::ACL_KEYWORD_ALL) == 0; + bool userAllFlag = toks[2].compare(AclData::ACL_KEYWORD_ALL) == 0; Action action; if (actionAllFlag) { @@ -492,7 +607,7 @@ namespace acl { } if (toksSize >= 5) { // object name-value pair - if (toks[4].compare("all") == 0) { + if (toks[4].compare(AclData::ACL_KEYWORD_ALL) == 0) { rule->setObjectTypeAll(); } else { try { @@ -526,7 +641,7 @@ namespace acl { } } // Check if name (toks[2]) is group; if not, add as name of individual - if (toks[2].compare("all") != 0) { + if (toks[2].compare(AclData::ACL_KEYWORD_ALL) != 0) { if (groups.find(toks[2]) == groups.end()) { addName(toks[2]); } diff --git a/qpid/cpp/src/qpid/acl/AclReader.h b/qpid/cpp/src/qpid/acl/AclReader.h index 6351c1e509..1fa374c59c 100644 --- a/qpid/cpp/src/qpid/acl/AclReader.h +++ b/qpid/cpp/src/qpid/acl/AclReader.h @@ -28,6 +28,7 @@ #include <sstream> #include <memory> #include "qpid/acl/AclData.h" +#include "qpid/acl/Acl.h" #include "qpid/broker/AclModule.h" namespace qpid { @@ -96,7 +97,7 @@ class AclReader { std::ostringstream errorStream; public: - AclReader(); + AclReader(uint16_t cliMaxConnPerUser); virtual ~AclReader(); int read(const std::string& fn, boost::shared_ptr<AclData> d); // return=0 for success std::string getError(); @@ -116,8 +117,17 @@ class AclReader { void printRules() const; // debug aid bool isValidUserName(const std::string& name); + bool processQuotaLine(tokList& toks); + bool processQuotaConnLine(tokList& toks); + bool processQuotaConnGroup(const std::string&, uint16_t); + void printConnectionQuotas() const; + static bool isValidGroupName(const std::string& name); static nvPair splitNameValuePair(const std::string& nvpString); + + const uint16_t cliMaxConnPerUser; + bool connQuotaRulesExist; + boost::shared_ptr<AclData::quotaRuleSet> connQuota; }; }} // namespace qpid::acl diff --git a/qpid/cpp/src/qpid/acl/AclTopicMatch.h b/qpid/cpp/src/qpid/acl/AclTopicMatch.h index 486c229ad5..654d1d63d4 100644 --- a/qpid/cpp/src/qpid/acl/AclTopicMatch.h +++ b/qpid/cpp/src/qpid/acl/AclTopicMatch.h @@ -30,7 +30,7 @@ namespace qpid { namespace broker { // Class for executing topic exchange routing key matching rules in -// Acl code the allows or denies users publishing to an exchange. +// Acl code. Allows or denies users publishing to an exchange. class TopicExchange::TopicExchangeTester { class boundNode; diff --git a/qpid/cpp/src/tests/acl.py b/qpid/cpp/src/tests/acl.py index 1020a2eff6..48723bfde9 100755 --- a/qpid/cpp/src/tests/acl.py +++ b/qpid/cpp/src/tests/acl.py @@ -2065,36 +2065,242 @@ class ACLTests(TestBase010): # Connection limits #===================================== - def test_connection_limits(self): + def test_connection_limits_cli_sets_all(self): + + try: + sessiona1 = self.get_session_by_port('alice','alice', self.port_u()) + sessiona2 = self.get_session_by_port('alice','alice', self.port_u()) + except Exception, e: + self.fail("Could not create two connections for user alice: " + str(e)) + + # Third session should fail + try: + sessiona3 = self.get_session_by_port('alice','alice', self.port_u()) + self.fail("Should not be able to create third connection for user alice") + except Exception, e: + result = None + + + + def test_connection_limits_by_named_user(self): """ Test ACL control connection limits """ + aclf = self.get_acl_file() + aclf.write('quota connections 2 alice bob\n') + aclf.write('quota connections 0 evildude\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + # By username should be able to connect twice per user try: - sessiona1 = self.get_session_by_port('alice','alice', self.port_u()) - sessiona2 = self.get_session_by_port('alice','alice', self.port_u()) + sessiona1 = self.get_session('alice','alice') + sessiona2 = self.get_session('alice','alice') except Exception, e: self.fail("Could not create two connections for user alice: " + str(e)) # Third session should fail try: - sessiona3 = self.get_session_by_port('alice','alice', self.port_u()) + sessiona3 = self.get_session('alice','alice') + self.fail("Should not be able to create third connection for user alice") + except Exception, e: + result = None + + # Disconnecting should allow another session. + sessiona1.close() + try: + sessiona3 = self.get_session('alice','alice') + except Exception, e: + self.fail("Could not recreate second connection for user alice: " + str(e)) + + # By username should be able to connect twice per user + try: + sessionb1 = self.get_session('bob','bob') + sessionb2 = self.get_session('bob','bob') + except Exception, e: + self.fail("Could not create two connections for user bob: " + str(e)) + + # Third session should fail + try: + sessionb3 = self.get_session('bob','bob') + self.fail("Should not be able to create third connection for user bob") + except Exception, e: + result = None + + + # User with quota of 0 is denied + try: + sessione1 = self.get_session('evildude','evildude') + self.fail("Should not be able to create a connection for user evildude") + except Exception, e: + result = None + + + # User not named in quotas is denied + try: + sessionc1 = self.get_session('charlie','charlie') + self.fail("Should not be able to create a connection for user charlie") + except Exception, e: + result = None + + # Clean up the sessions + sessiona2.close() + sessiona3.close() + sessionb1.close() + sessionb2.close() + + + + def test_connection_limits_by_unnamed_all(self): + """ + Test ACL control connection limits + """ + aclf = self.get_acl_file() + aclf.write('quota connections 2 alice bob\n') + aclf.write('quota connections 1 all\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + # By username should be able to connect twice per user + try: + sessiona1 = self.get_session('alice','alice') + sessiona2 = self.get_session('alice','alice') + except Exception, e: + self.fail("Could not create two connections for user alice: " + str(e)) + + # Third session should fail + try: + sessiona3 = self.get_session('alice','alice') self.fail("Should not be able to create third connection for user alice") except Exception, e: result = None + # By username should be able to connect twice per user try: - sessionb1 = self.get_session_by_port('bob','bob', self.port_u()) - sessionb2 = self.get_session_by_port('bob','bob', self.port_u()) + sessionb1 = self.get_session('bob','bob') + sessionb2 = self.get_session('bob','bob') except Exception, e: self.fail("Could not create two connections for user bob: " + str(e)) + # Third session should fail try: - sessionb3 = self.get_session_by_port('bob','bob', self.port_u()) + sessionb3 = self.get_session('bob','bob') self.fail("Should not be able to create third connection for user bob") except Exception, e: result = None + # User not named in quotas gets 'all' quota + try: + sessionc1 = self.get_session('charlie','charlie') + except Exception, e: + self.fail("Could not create one connection for user charlie: " + str(e)) + + # Next session should fail + try: + sessionc2 = self.get_session('charlie','charlie') + self.fail("Should not be able to create second connection for user charlie") + except Exception, e: + result = None + + # Clean up the sessions + sessiona1.close() + sessiona2.close() + sessionb1.close() + sessionb2.close() + sessionc1.close() + + + def test_connection_limits_by_group(self): + """ + Test ACL control connection limits + """ + aclf = self.get_acl_file() + aclf.write('group stooges moe@QPID larry@QPID curly@QPID\n') + aclf.write('quota connections 2 alice bob\n') + aclf.write('quota connections 2 stooges charlie\n') + aclf.write('# user and groups may be overwritten. Should use last value\n') + aclf.write('quota connections 3 bob stooges\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + # Alice gets 2 + try: + sessiona1 = self.get_session('alice','alice') + sessiona2 = self.get_session('alice','alice') + except Exception, e: + self.fail("Could not create two connections for user alice: " + str(e)) + + # Third session should fail + try: + sessiona3 = self.get_session('alice','alice') + self.fail("Should not be able to create third connection for user alice") + except Exception, e: + result = None + + # Bob gets 3 + try: + sessionb1 = self.get_session('bob','bob') + sessionb2 = self.get_session('bob','bob') + sessionb3 = self.get_session('bob','bob') + except Exception, e: + self.fail("Could not create three connections for user bob: " + str(e)) + + # Fourth session should fail + try: + sessionb4 = self.get_session('bob','bob') + self.fail("Should not be able to create fourth connection for user bob") + except Exception, e: + result = None + + # Moe gets 3 + try: + sessionm1 = self.get_session('moe','moe') + sessionm2 = self.get_session('moe','moe') + sessionm3 = self.get_session('moe','moe') + except Exception, e: + self.fail("Could not create three connections for user moe: " + str(e)) + + # Fourth session should fail + try: + sessionb4 = self.get_session('moe','moe') + self.fail("Should not be able to create fourth connection for user ,pe") + except Exception, e: + result = None + + # User not named in quotas is denied + try: + sessions1 = self.get_session('shemp','shemp') + self.fail("Should not be able to create a connection for user shemp") + except Exception, e: + result = None + + # Clean up the sessions + sessiona1.close() + sessiona2.close() + sessionb1.close() + sessionb2.close() + sessionb3.close() + sessionm1.close() + sessionm2.close() + sessionm3.close() + + + def test_connection_limits_by_ip_address(self): + """ + Test ACL control connection limits by ip address + """ # By IP address should be able to connect twice per client address try: sessionb1 = self.get_session_by_port('alice','alice', self.port_i()) @@ -2109,6 +2315,8 @@ class ACLTests(TestBase010): except Exception, e: result = None + sessionb1.close() + sessionb2.close() #===================================== # User name substitution |
