4 # S.Çağlar Onur <caglar@cs.princeton.edu>
6 from PLC.Config import Config
7 from PLC.Faults import PLCPermissionDenied
9 from PLC.Nodes import Node, Nodes
10 from PLC.Persons import Person, Persons
11 from PLC.Sessions import Session, Sessions
13 from datetime import datetime, timedelta
15 from pyaspects.meta import MetaAspect
22 class BaseRateLimit(object):
25 self.config = Config("/etc/planetlab/plc_config")
27 # FIXME: change with Config values
28 self.prefix = "ratelimit"
29 self.minutes = 5 # The time period
30 self.requests = 50 # Number of allowed requests in that time period
31 self.expire_after = (self.minutes + 1) * 60
36 log = open("/var/log/plc_api_ratelimit.log", "a")
37 date = datetime.now().strftime("%d/%m/%y %H:%M")
38 log.write("%s - %s\n" % (date, line))
42 sendmail = os.popen("/usr/sbin/sendmail -N never -t -f%s" % self.config.PLC_MAIL_SUPPORT_ADDRESS, "w")
44 subject = "[PLCAPI] Maximum allowed number of API calls exceeded"
46 header = {'from': "%s Support <%s>" % (self.config.PLC_NAME, self.config.PLC_MAIL_SUPPORT_ADDRESS),
47 'to': "%s, %s" % (to, self.config.PLC_MAIL_SUPPORT_ADDRESS),
48 'version': sys.version.split(" ")[0],
51 body = "Maximum allowed number of API calls exceeded for the user %s within the last %s minutes." % (to, self.minutes)
56 Content-type: text/plain
60 X-Mailer: Python/%(version)s
63 """.lstrip() % header)
70 def before(self, wobj, data, *args, **kwargs):
71 # ratelimit_128.112.139.115_201011091532 = 1
72 # ratelimit_128.112.139.115_201011091533 = 14
73 # ratelimit_128.112.139.115_201011091534 = 11
74 # Now, on every request we work out the keys for the past five minutes and use get_multi to retrieve them.
75 # If the sum of those counters exceeds the maximum allowed for that time period, we block the request.
77 api_method_name = wobj.name
78 api_method_source = wobj.source
81 api_method = args[0]["AuthMethod"]
85 # decode api_method_caller
86 if api_method == "session":
87 api_method_caller = Sessions(wobj.api, {'session_id': args[0]["session"]})
88 if api_method_caller == []:
90 elif api_method_caller[0]["person_id"] != None:
91 api_method_caller = Persons(wobj.api, api_method_caller[0]["person_id"])[0]["email"]
92 elif api_method_caller[0]["node_id"] != None:
93 api_method_caller = Nodes(wobj.api, api_method_caller[0]["node_id"])[0]["hostname"]
95 api_method_caller = args[0]["session"]
96 elif api_method == "password" or api_method == "capability":
97 api_method_caller = args[0]["Username"]
98 elif api_method == "gpg":
99 api_method_caller = args[0]["name"]
100 elif api_method == "hmac" or api_method == "hmac_dummybox":
101 api_method_caller = args[0]["node_id"]
102 elif api_method == "anonymous":
103 api_method_caller = "anonymous"
105 api_method_caller = "unknown"
108 if api_method_source == None or api_method_source[0] == self.config.PLC_API_IP or api_method_source[0] in self.whitelist:
112 if api_method_caller == None:
113 self.log("%s called from %s with Username = None?" % (api_method_name, api_method_source[0]))
116 # normalize unicode string otherwise memcache throws an exception
117 api_method_caller = str(api_method_caller)
119 mc = memcache.Client(["%s:11211" % self.config.PLC_API_HOST])
122 current_key = "%s_%s_%s_%s" % (self.prefix, api_method_caller, api_method_source[0], now.strftime("%Y%m%d%H%M"))
123 keys_to_check = ["%s_%s_%s_%s" % (self.prefix, api_method_caller, api_method_source[0], (now - timedelta(minutes = minute)).strftime("%Y%m%d%H%M")) for minute in range(self.minutes + 1)]
126 value = mc.incr(current_key)
131 mc.set(current_key, 1, time=self.expire_after)
133 results = mc.get_multi(keys_to_check)
136 total_requests += results[i]
138 if total_requests > self.requests:
139 self.log("%s - %s" % (api_method_source[0], api_method_caller))
141 caller_key = "%s_%s" % (self.prefix, api_method_caller)
142 if mc.get(caller_key) == None:
143 mc.set(caller_key, 1, time = self.expire_after)
144 if (api_method == "session" and api_method_caller.__contains__("@")) or (api_method == "password" or api_method == "capability"):
145 self.mail(api_method_caller)
147 raise PLCPermissionDenied, "Maximum allowed number of API calls exceeded"
149 def after(self, wobj, data, *args, **kwargs):
152 class RateLimitAspect_class(BaseRateLimit):
153 __metaclass__ = MetaAspect
154 name = "ratelimitaspect_class"
157 BaseRateLimit.__init__(self)
159 def before(self, wobj, data, *args, **kwargs):
160 BaseRateLimit.before(self, wobj, data, *args, **kwargs)
162 def after(self, wobj, data, *args, **kwargs):
163 BaseRateLimit.after(self, wobj, data, *args, **kwargs)
165 RateLimitAspect = RateLimitAspect_class