def test_api_random_caller(self, display=False): methods = restfuzz.method.load_methods(API_DIR) api = FakeApi() fuzzer = ApiRandomCaller(api, methods) method_called = set() for i in xrange(10): event = fuzzer.step() self.assertTrue(len(event.url) > 5) method_called.add(event.name) if display: print event time.sleep(0.3) self.assertTrue(len(method_called) > 1) return
def test_api_random_caller(self, display=False): methods = restfuzz.method.load_methods(API_DIR) api = FakeApi() fuzzer = ApiRandomCaller(api, methods) method_called = set() for i in range(10): event = fuzzer.step() self.assertTrue(len(event.url) > 5) method_called.add(event.name) if display: print(event) time.sleep(0.3) self.assertTrue(len(method_called) > 1) return
def test_api_random_caller_ressouce_mgmt(self): methods = { 'update': restfuzz.method.Method({ 'name': 'update', 'url': ['PUT', '%(resource_id)s.json'], 'inputs': {'url_input': {'resource_id': {'type': 'resource', 'required': 'True'}}} }, base_url='http://localhost') } api = FakeApi(resp_code=404) fuzzer = ApiRandomCaller(api, methods, chaos_monkey=False) fuzzer.ig.resources['resource_id'] = ['aaaa-aa'] event = fuzzer.step() # Test resources is used in url self.assertEquals(event.url, 'http://localhost/aaaa-aa.json') # Test resource is removed because api returned 404 self.assertEquals(len(fuzzer.ig.resources), 0) methods = { 'delete': restfuzz.method.Method({ 'name': 'delete', 'url': ['DELETE', '%(resource_id)s.json'], 'inputs': {'url_input': {'resource_id': {'type': 'resource', 'required': 'True'}}} }, base_url='http://localhost') } api = FakeApi(resp_code=200) fuzzer = ApiRandomCaller(api, methods, chaos_monkey=False) fuzzer.ig.resources['resource_id'] = ['aaaa-aa'] # Test resource is removed because DELETE method called fuzzer.step() self.assertEquals(len(fuzzer.ig.resources), 0) methods = { 'resource_list': restfuzz.method.Method({ 'name': 'resource_list', 'url': ['GET', 'list.json'], 'outputs': {'resource_id': {'type': 'resource', 'json_extract': 'lambda x: [i["id"] for i in x]'}} }, base_url='http://localhost'), 'delete': restfuzz.method.Method({ 'name': 'delete', 'url': ['DELETE', '%(resource_id)s.json'], 'inputs': {'url_input': {'resource_id': {'type': 'resource', 'required': 'True'}}} }, base_url='http://localhost') } api = FakeApi(resp_content='[{"id": "41"}, {"id": "43"}]', resp_code=200) fuzzer = ApiRandomCaller(api, methods, chaos_monkey=False) fuzzer.sync_resources() self.assertTrue('41' in fuzzer.ig.resources['resource_id'])
def do_restfuzz(): parser = argparse.ArgumentParser() parser.add_argument("--api", action="append", metavar="file_or_dir", help="Api description", required=True) parser.add_argument("--os-api", action="append", metavar="file_or_dir", help="Os-Api-Ref documentation", required=True) parser.add_argument("--base_url", help="The base url") parser.add_argument("--method", action="append", help="Only fuzz this method.") parser.add_argument("--token", help="X-Auth-Token to use") parser.add_argument("--tenant_id", nargs='+', default=[], help="Adds tenant ids") parser.add_argument("--db", help="File path to store event in") parser.add_argument("--health", help="Python module path to call after each call") parser.add_argument("--debug", action="store_const", const=True) parser.add_argument("--verbose", action="store_const", const=True) parser.add_argument("--seed", help="PRNG seed") parser.add_argument("--max_events", default=1e6, type=int, help="Maximum number of event") parser.add_argument("--max_time", default=3600 * 12, type=int, help="Maximum running time") args = parser.parse_args() def verbose(msg): if args.verbose: print(msg) methods = {} for api in args.api: api_methods = method.load_methods(api, args.base_url) methods.update(api_methods) for os_api in args.os_api: osapiref = OsApiRefFile(os_api) methods.update(osapiref.render()) api = Api() if args.token: api.set_header("X-Auth-Token", args.token) fuzzer = ApiRandomCaller(api, methods, args.seed) if args.db: db = EventDb(open(args.db, "wb")) health = None if args.health: import imp health = imp.load_source('health', args.health).Health() for tenant_id in args.tenant_id: fuzzer.ig.resource_add("tenant_id", tenant_id) def refresh_keystone_token(): token, tenant_id = os.popen("openstack token issue | " "grep ' id\|project_id' | " "awk '{ print $4 }'").read().split() api.set_header("X-Auth-Token", token) fuzzer.ig.resource_add("tenant_id", tenant_id) fuzzer.sync_resources() return token, tenant_id if "OS_USERNAME" in os.environ and not args.token: refresh_keystone_token() else: fuzzer.sync_resources() stats = { "total": 0, "http_code": {}, "start_time": time.monotonic(), "last_speed": [0, time.monotonic()] } while (stats["total"] < args.max_events and (time.monotonic() - stats["start_time"]) < args.max_time): new_traceback = False event = fuzzer.step(args.debug, args.method) if event is None: continue if health: for health_event in health.check(): if "tb_id" in health_event: if "uniq_tb" in health_event: new_traceback = True event.tracebacks.append(health_event) else: print("Unknown health event", health_event) if args.db: db.append(event) if new_traceback: print("\n\n%s\n" % event.render('\033[91m')) else: if event.code in (400, 404, 409): verbose(event.render('\033[94m')) elif event.code >= 200 and event.code < 300: verbose(event.render('\033[92m')) else: verbose(event.render('\033[91m')) if event.code == 401 and \ event.json_output and \ "auth" in event.json_output.lower() and \ "OS_USERNAME" in os.environ: print("[+] Requesting a new token") try: refresh_keystone_token() except Exception: print("[+] Could not get keystone token") raise stats["http_code"][event.code] = 1 + stats["http_code"].setdefault( event.code, 0) stats["total"] += 1 if stats["total"] % 100 == 0: time_now = time.monotonic() status_line = "\r%d sec elapsed, %dk events, " \ "(%03.02f events/seconds), http_code: %s " % ( time_now - stats["start_time"], stats["total"] / 1000, (stats["total"] - stats["last_speed"][0]) / ( time_now - stats["last_speed"][1]), stats["http_code"], ) stats["last_speed"] = [stats["total"], time_now] print(status_line, end='') stdout.flush() print("\nOver.")
def test_api_random_caller_ressouce_mgmt(self): methods = { 'update': restfuzz.method.Method({ 'name': 'update', 'url': ['PUT', '%(resource_id)s.json'], 'inputs': {'url_input': { 'resource_id': {'_type': 'resource', 'required': 'True'}}} }, base_url='http://localhost') } api = FakeApi(resp_code=404) fuzzer = ApiRandomCaller(api, methods, chaos_monkey=False) fuzzer.ig.resources['resource_id'] = ['aaaa-aa'] event = fuzzer.step() # Test resources is used in url self.assertEquals(event.url, 'http://localhost/aaaa-aa.json') # Test resource is removed because api returned 404 self.assertEquals(len(fuzzer.ig.resources), 0) methods = { 'delete': restfuzz.method.Method({ 'name': 'delete', 'url': ['DELETE', '%(resource_id)s.json'], 'inputs': {'url_input': { 'resource_id': {'_type': 'resource', 'required': 'True'}}} }, base_url='http://localhost') } api = FakeApi(resp_code=200) fuzzer = ApiRandomCaller(api, methods, chaos_monkey=False) fuzzer.ig.resources['resource_id'] = ['aaaa-aa'] # Test resource is removed because DELETE method called fuzzer.step() self.assertEquals(len(fuzzer.ig.resources), 0) methods = { 'resource_list': restfuzz.method.Method({ 'name': 'resource_list', 'url': ['GET', 'list.json'], 'outputs': {'resource_id': { '_type': 'resource', 'json_extract': 'lambda x: [i["id"] for i in x]'}} }, base_url='http://localhost'), 'delete': restfuzz.method.Method({ 'name': 'delete', 'url': ['DELETE', '%(resource_id)s.json'], 'inputs': {'url_input': {'resource_id': {'_type': 'resource', 'required': 'True'}}} }, base_url='http://localhost') } api = FakeApi(resp_content='[{"id": "41"}, {"id": "43"}]', resp_code=200) fuzzer = ApiRandomCaller(api, methods, chaos_monkey=False) fuzzer.sync_resources() self.assertTrue('41' in fuzzer.ig.resources['resource_id'])