3 # S.Çağlar Onur <caglar@cs.princeton.edu>
5 from PLC.Config import Config
6 from PLC.Faults import PLCPermissionDenied
8 from PLC.Nodes import Node, Nodes
9 from PLC.Persons import Person, Persons
10 from PLC.Sessions import Session, Sessions
12 from datetime import datetime, timedelta
14 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] == socket.gethostbyname(self.config.PLC_API_HOST) 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, metaclass=MetaAspect):
153 name = "ratelimitaspect_class"
156 BaseRateLimit.__init__(self)
158 def before(self, wobj, data, *args, **kwargs):
159 BaseRateLimit.before(self, wobj, data, *args, **kwargs)
161 def after(self, wobj, data, *args, **kwargs):
162 BaseRateLimit.after(self, wobj, data, *args, **kwargs)
164 RateLimitAspect = RateLimitAspect_class