def __init__(self, api_url, api_token): self.api_url = api_url self.api_token = api_token payload = jwt.decode(api_token, verify=False) # do not trust if not "dns_server_name" in payload.keys( ) or not payload["dns_server_name"]: logger.critical("no dns_server_name on api token") raise Exception("no dns_server_name on api token") self.dns_server_name = payload["dns_server_name"]
def get(self, url, params=None, fail=True): params = params or {} headers = self.get_default_headers() res = requests.get(self.url(url), headers=headers) if fail: if res.status_code != 200: logger.critical(str(res.json())) res.raise_for_status() return res.json()
def token_has_one_required_scopes(token_payload: TokenPayload, scopes: List[str]): token_scopes = token_payload.scopes required_scopes = scopes or [] for required_scope in required_scopes: for token_scope in token_scopes: if token_scope == required_scope: return True logger.critical(f"auth token missing at least one scope: {required_scope}") return False
def from_zone(cls, zone): records = [] logger.info( f"[email protected] - Loading zone: {zone.domain}/{zone.ip} ({zone.id})" ) dns_records = zone.dns_records or [] # if the zone has no records, create some default ones if not dns_records: logger.warning( f"[email protected] - Zone has no dns_records. loading defaults: {zone.domain}/{zone.ip} ({zone.id})" ) rrs = RR.fromZone( ZONE_TEMPLATE.format(domain_name=zone.domain, domain_ip=zone.ip) ) zone_records = [Record.make(zone, rr) for rr in rrs] for zr in zone_records: # TODO: make this clean on output rrstr = str(dedent(str(zr.rr))) logger.debug(f"[email protected] - Loading record entry: {rrstr}") logger.debug( "[email protected] - Loaded record details - name: {} | rtype: {} | rr: {}".format( str(zr.rr.rname), str(QTYPE[zr.rr.rtype]), str(zr.rr) ) ) else: # loop over each dns_record of the zone and convert it to RR record dns_records = sorted(dns_records, key=lambda x: x.sort) zone_records = [] for dns_record in dns_records: try: rrs = RR.fromZone(dns_record.record) _zone_records = [Record.make(zone, rr) for rr in rrs] for zr in _zone_records: rrstr = str(dedent(str(zr.rr))) logger.debug( f"[email protected] - Loading record: {str(dns_record.record)}" ) logger.debug( f"[email protected] - Loading record entry: {rrstr}" ) logger.debug( "[email protected] - Loaded record details - name: {} | rtype: {} | rr: {}".format( str(zr.rr.rname), str(QTYPE[zr.rr.rtype]), str(zr.rr) ) ) zone_records = zone_records + _zone_records except Exception as e: logger.critical( f'[email protected] - Error processing line ({e.__class__.__name__}: {e}) "{dns_record.id}:{dns_record.record}" ' ) raise e # add the records for the zone to the rest of the records records = records + zone_records return cls(records)
def post(self, url: str, data=None, fail=True): data = data or {} headers = self.get_default_headers() res = requests.post(self.url(url), json=data, headers=headers) logger.info("Posting URL: " + str(self.url(url))) if fail: if res.status_code != 200: logger.critical(f"Error posting API {self.url(url)}: " + str(res.json())) res.raise_for_status() return res.json()
def label(self, key): if "." not in key: return getattr(self.model(), key) parent = self.model() parts = key.split(".") final_index = len(parts) - 1 for i, part in enumerate(parts): if i == final_index: logger.critical("LABEL " + str(getattr(parent, part))) return getattr(parent, part) parent = self._get_relationship_model(part, parent) raise Exception(f"Unable to decipher label for key: {key}")
async def run(self): env = self.option("env") self.load_env(f"api.{env}") from bountydns.db.checks import is_db_up, is_db_setup self.db_register() db_up = is_db_up() if not db_up: logger.critical( "run@api_server.py - Database not up error. please check logs" ) return self.exit(1) return 0
def get(self, url: str, params=None, fail=True): params = params or {} headers = self.get_default_headers() res = requests.get(self.url(url), headers=headers, params=params) logger.info("Getting URL: " + str(self.url(url))) if fail: if res.status_code != 200: logger.critical(f"Error getting API {self.url(url)}: " + str(res.json())) res.raise_for_status() return res.json()
async def index( sort_qs: SortQS = Depends(SortQS), pagination: PaginationQS = Depends(PaginationQS), api_token_repo: ApiTokenRepo = Depends(ApiTokenRepo()), token: TokenPayload = Depends(ScopedTo("api-token:list")), includes: List[str] = Query(None), ): logger.critical("LOOK AT MEEEEE") print("HELLOOOOOOOOOOO") includes = only(includes, ["dns_server"], values=True) pg, items = (api_token_repo.loads("dns_server").strict().sort( sort_qs).paginate(pagination).includes(includes).data()) return ApiTokensResponse(pagination=pg, api_tokens=items)
def make(cls, zone, rr): if not getattr(rr, "rtype", None): logger.critical( f"No rtype found for rr: {str(rr)} - {str(rr.__class__)}") # RR's don't have knowledge of domiain so replace "." with zone domain if rr.rname == ".": zone_rname = zone.domain + "." else: zone_rname = "." + zone.domain + "." new_label_name = str(rr.rname).replace(".", zone_rname) logger.debug( f"Replacing RR's rname {str(rr.rname)} with {new_label_name}") rr.set_rname(new_label_name) return cls(zone, rr)
async def broadcast_auth(websocket: WebSocket): try: await websocket.accept() except Exception as e: logger.critical( f"[email protected] - Accept Error: {str(type(e))}") logger.critical( f"[email protected] - Accept Trace: {str(e)}") return 1 params = parse_qs(urlparse(str(websocket.url)).query) token = verify_jwt_token(params["ws_access_token"][0]) if not token_has_required_scopes(token, []): # TODO: check scopes later raise HTTPException(403, detail="Forbidden") user_repo = UserRepo(session()) user = await current_user(token, user_repo) subscriber, channel = await make_subscriber("auth") try: while await channel.wait_message(): logger.info("[email protected] - Waiting for message") msg = await channel.get(encoding="utf-8") logger.info("[email protected] - Received message: " + str(msg)) data = json.loads(msg) logger.info("[email protected] - Sending message: " + str(data)) await websocket.send_json(data) except Exception as e: logger.critical( f"[email protected] - Receieve/Send: Error: {str(type(e))}" ) logger.critical( f"[email protected] - Receieve/Send: Trace:{str(e)}") logger.critical( f"[email protected] - Receieve/Send: Not closing or unsubscribing" ) return 1 logger.info(f"[email protected] - Attempting to unsuscribe") await subscriber.unsubscribe("channel:auth") logger.info( f"[email protected]: Websocket - Attempting to close socket" ) await websocket.close()
async def run(self): args = ["bountydns.api.main:api"] kwargs = self.get_kwargs() self.load_env("api") if self.should_import_check(): logger.info("performing import check") from bountydns.api.main import api logger.critical("starting api server with options: {}".format( str(kwargs))) from bountydns.db.checks import is_db_up, is_db_setup if self.should_db_check(): self.db_register() db_up = is_db_up() if not db_up: logger.critical("database not up error. please check logs") return self.exit(1) if self.option("db_setup"): logger.critical("running database migration") db_setup_options = self._args_to_dict(self.options) if self.option("db_seed"): db_setup_options["seed"] = True await DbSetup(db_setup_options).run() if self.should_db_check(): db_setup = is_db_setup() if not db_setup: logger.critical("database not setup error. please check logs") return self.exit(1) from bountydns.broadcast import is_broadcast_up if self.should_bcast_check(): bcast_up = await is_broadcast_up() if not bcast_up: logger.critical( "broadcast (queue) not up error. please check logs") return self.exit(1) if self.option("db_seed_env", False): self.seed_from_env() return uvicorn.run(*args, **kwargs)
def token_has_required_scopes(token_payload: TokenPayload, scopes: List[str]): token_scopes = token_payload.scopes required_scopes = scopes or [] for required_scope in required_scopes: satisfied = False for token_scope in token_scopes: if token_scope == required_scope: satisfied = True # probably bad / too generous # a:b in a:b:c elif token_scope in required_scope: satisfied = True if not satisfied: logger.critical(f"auth token missing scope: {required_scope}") return False return True
async def is_broadcast_up(): seconds = 0 while True: if seconds > 60: logger.critical("could not start api. broadcast (queue) not up") return False logger.debug("checking for broadcast (queue) status") try: redis = await make_redis() await redis.set("up-key", "value") val = await redis.get("up-key") val = await redis.delete("up-key") return True except Exception as e: logger.critical( "broadcast (queue) check not ready after {} seconds: {}". format(str(seconds), str(e.__class__.__name__))) seconds = seconds + 2 sleep(2)
async def run(self): port = self.get_port() listen = self.get_listen() # TODO: thread issues? api_client = ApiClient(self.get_api_url(), self.get_api_token()) if not api_client.wait_for_up(): logger.critical("could not connect to api. quitting") self.exit(1) if self.option("no_sync"): logger.info("skipping syncing api token") else: api_client.sync() resolver = Resolver(api_client) udp_server = DNSServer( resolver, address=listen, port=port, handler=DNSHandler, logger=DNSLogger(api_client), ) tcp_server = DNSServer( resolver, address=listen, port=port, tcp=True, handler=DNSHandler, logger=DNSLogger(api_client), ) logger.info("starting DNS server on port %d", port) udp_server.start_thread() tcp_server.start_thread() try: while udp_server.isAlive(): sleep(1) except KeyboardInterrupt: pass
def __init__(self, api_url, api_token, verify_ssl=True): self.api_url = api_url self.api_token = api_token self.zones = [] self.verify_ssl = verify_ssl if not self.verify_ssl: logger.warning( "__init__@dns_server.py - Disabling SSL Warnings. I hope this was intentional" ) requests.packages.urllib3.disable_warnings( category=InsecureRequestWarning) payload = jwt.decode(api_token, verify=False) # do not trust if not "dns_server_name" in payload.keys( ) or not payload["dns_server_name"]: logger.critical( f"__init__@api_client.py - No dns_server_name on api token payload: {str(payload)}" ) raise Exception("no dns_server_name on api token") self.dns_server_name = payload["dns_server_name"]
async def run(self): self.load_env("api") self.db_register() failed = [] if self.option("confirm"): for model in models: for item in self.session().query(model).all(): logger.info(f"deleting {item}") try: self.session().delete(item) self.session().commit() except Exception as e: failed.append((item, e)) else: logger.warning("You must confirm to drop data") if len(failed) > 0: logger.critical("encountered errors") for f in failed: print("Failed:", item[0]) print("Error", item[1])
def is_db_up(): seconds = 0 while True: if seconds > 60: logger.critical("could not start api. database not up") return False logger.debug("checking for db status") try: session().execute("SELECT 1") return True except KeyError as e: logger.critical( "database has not be registered. please call db_register(db_url)" ) return False except Exception as e: logger.critical( "database check not ready after {} seconds: {}".format( str(seconds), str(e.__class__.__name__))) seconds = seconds + 1 sleep(1)
def is_db_setup(): seconds = 0 while True: if seconds > 60: logger.critical("could not start api. database not setup") return False logger.debug("checking for db migrations") try: session().execute("SELECT * from alembic_version") return True except KeyError as e: logger.critical( "database has not be registered. please call db_register(db_url)" ) return False except Exception as e: logger.critical( "database has not been migrated. please run: bdnsctl.py db-setup" ) return False seconds = seconds + 1 sleep(1)
async def run(self): app = "bountydns.api.main:api" kwargs = self.get_kwargs() env = self.option("env") self.load_env(f"api.{env}") if self.should_import_check(): logger.info("performing import check") from bountydns.api.main import api logger.critical("starting api server with options: {}".format( str(kwargs))) from bountydns.db.checks import is_db_up, is_db_setup if self.should_db_check(): self.db_register() db_up = is_db_up() if not db_up: logger.critical("database not up error. please check logs") return self.exit(1) if self.option("db_setup"): logger.critical("running database migration") db_setup_options = self._args_to_dict(self.options) if self.option("db_seed"): db_setup_options["seed"] = True await DbSetup(db_setup_options).run() if self.should_db_check(): db_setup = is_db_setup() if not db_setup: logger.critical("database not setup error. please check logs") return self.exit(1) from bountydns.broadcast import is_broadcast_up if self.should_bcast_check(): bcast_up = await is_broadcast_up() if not bcast_up: logger.critical( "broadcast (queue) not up error. please check logs") return self.exit(1) if self.option("db_seed_env", False): self.seed_from_env() # taken from uvicorn/main.py:run config = UvicornConfig(app, **kwargs) server = UvicornServer(config=config) if isinstance(app, str) and (config.debug or config.reload): sock = config.bind_socket() supervisor = StatReload(config) logger.warning(f"running bountydns api in dev mode...") return supervisor.run(server.run, sockets=[sock]) elif config.workers > 1: sock = config.bind_socket() supervisor = Multiprocess(config) logger.warning(f"running bountydns api in worker mode...") return supervisor.run(server.run, sockets=[sock]) else: sockets = None logger.warning(f"running bountydns api in standard mode...") return await server.serve(sockets=sockets)
def get_workers(self): if self.option("debug", None) or self.option("reload", None): logger.critical( "Canot use debug or reload with workers. Skipping.") return None return self.option("workers", 5)
async def run(self): # TODO: thread issues? verify_ssl = True if bool(self.option("no_ssl_verify")): verify_ssl = False self.api_client = ApiClient(self.get_api_url(), self.get_api_token(), verify_ssl=verify_ssl) if not self.api_client.wait_for_up(): logger.critical( "run@dns_server.py - Could not connect to api. quitting") self.exit(1) if self.option("no_sync"): logger.info("run@dns_server.py - Skipping syncing api token") else: self.api_client.sync() self.boot() self.start_servers() try: count = 0 while self.udp_server.isAlive(): if count > 0 and count % self.option("refresh_ttl") == 0: if self.api_client.refresh_zones_if_needed(): logger.critical( "run@dns_server.py - API Client found new or changed zones. Stopping servers..." ) # TODO: figure out why "stop" does not release the address self.stop_servers() sleep(1) stop_count = 0 logger.critical( "run@dns_server.py - Waiting for UDP Server to stop..." ) while self.udp_server.thread and self.udp_server.isAlive( ): if stop_count > 30: logger.critical( "run@dns_server.py - UDP Server did not stop while reloading zones" ) raise Exception( "run@dns_server.py - UDP Server threads went rogue during zone reload" ) logger.info( "run@dns_server.py - Waiting for DNS Server to stop before reloading zones" ) stop_count = stop_count + 1 sleep(1) stop_count = 0 logger.critical( "run@dns_server.py - Waiting for TCP Server to stop..." ) while self.tcp_server.thread and self.tcp_server.isAlive( ): if stop_count > 30: logger.critical( "run@dns_server.py - TCP Server did not stop while reloading zones" ) raise Exception( "run@dns_server.py - TCP Server threads went rogue during zone reload" ) logger.info( "run@dns_server.py - Waiting for DNS Server to stop before reloading zones" ) stop_count = stop_count + 1 sleep(1) logger.critical( "run@dns_server.py - Rebooting server with fresh zones..." ) self.boot() self.start_servers() count = count + 1 sleep(1) except KeyboardInterrupt: pass
def log_request(self, handler, request, request_uuid): logger.critical(f"log_request: {handler}, {request}, {request_uuid}") self.api.create_dns_request(handler, request, request_uuid) super().log_request(handler, request)
async def run(self): env = self.option("env") self.load_env(f"api.{env}") self.db_register() if self.option("target", False) == False: self.set_option("target", "dev") if self.option("target") == "env": # self.load_env("seed") logger.info(f"run@db_seed.py - Seeding {self.option('target')}") raise NotImplementedError() # seed based on env vars elif self.option("target") == "dev": logger.info(f"run@db_seed.py - Seeding {self.option('target')}") logger.info("run@db_seed.py - reating superuser") super = factory("SuperUserFactory").create(email="*****@*****.**") logger.info("run@db_seed.py - Creating normal user") norm = factory("UserFactory").create(email="*****@*****.**") logger.info("run@db_seed.py - Creating dns_server") dns_server = factory("DnsServerFactory").create() logger.info("run@db_seed.py - Creating zones") zone = factory("ZoneFactory").create(domain="othersite.com", ip="127.0.1.1") zone2 = factory("ZoneFactory").create(domain="friends4life.com", ip="127.0.1.1", dns_server=dns_server) zone3 = factory("ZoneFactory").create(domain="differentzone.com", ip="127.0.1.1") logger.info("run@db_seed.py - Creating api_tokens") for i in range(65): factory("ApiTokenFactory").create(dns_server=dns_server) logger.info("run@db_seed.py - Creating dns_requests") for i in range(35): factory("DnsRequestFactory").create(dns_server=dns_server, zone=zone2) for i in range(35): factory("DnsRequestFactory").create(dns_server=dns_server, zone=zone3) logger.info("run@db_seed.py - Creating dns_records") for i in range(3): factory("DnsRecordFactory").create(zone=zone) for i in range(3): factory("DnsRecordFactory").create(zone=zone2) for i in range(3): factory("DnsRecordFactory").create(zone=zone3) else: logger.critical("run@db_seed.py - invalid target set for seeder") self.exit(1)
async def run(self): app = "bountydns.api.main:api" kwargs = self.get_kwargs() env = self.option("env") self.load_env(f"api.{env}") if self.should_import_check(): logger.info("run@api_server.py - Performing import check") from bountydns.api.main import api logger.critical( "run@api_server.py - Starting api server with options: {}".format( str(kwargs))) from bountydns.db.checks import is_db_up, is_db_setup # alembic just destroys the loggers, it's annoying if self.should_db_check(): logger.info( "run@api_server.py - Waiting for database service to be up") db_wait_options = self._args_to_dict(self.options) await DbWait(db_wait_options).run() if self.option("db_setup"): logger.critical("run@api_server.py - Running database migration") db_setup_options = self._args_to_dict(self.options) if self.option("db_seed"): db_setup_options["seed"] = True await DbSetup(db_setup_options).run() if self.should_db_check(): logger.info( "run@api_server.py - Checking if application database is setup and configured" ) db_setup = is_db_setup() if not db_setup: logger.critical( "run@api_server.py - Database not setup error. please check logs" ) return self.exit(1) from bountydns.broadcast import is_broadcast_up if self.should_bcast_check(): bcast_up = await is_broadcast_up() if not bcast_up: logger.critical( "run@api_server.py - Broadcast (queue) not up error. please check logs" ) return self.exit(1) if self.option("db_seed_env", False): self.seed_from_env() # taken from uvicorn/main.py:run logger.debug("run@api_server.py - Building Uvicorn Config and Server") config = UvicornConfig(app, log_config=self.get_uvicorn_logging(), **kwargs) server = UvicornServer(config=config) if self.option("force_exit"): server.force_exit = True if isinstance(app, str) and (config.debug or config.reload): logger.warning( f"run@api_server.py - Running bountydns api in dev mode...") sock = config.bind_socket() supervisor = StatReload(config) return supervisor.run(server.run, sockets=[sock]) elif config.workers > 1: sock = config.bind_socket() supervisor = Multiprocess(config) logger.warning( f"run@api_server.py - Running bountydns api in worker mode...") return supervisor.run(server.run, sockets=[sock]) else: sockets = None logger.warning( f"run@api_server.py - Running bountydns api in standard mode..." ) return await server.serve(sockets=sockets)
async def broadcast_index(websocket: WebSocket): try: logger.info("[email protected] - Accepting websockets") await websocket.accept() except Exception as e: logger.critical( f"[email protected] - Accept: Error: {str(type(e))}") logger.critical( f"[email protected] - Accept: Trace: {str(e)}") logger.critical( f"[email protected] - Accept:Not closing or unsubscribing" ) return 1 try: while True: logger.info("[email protected] - Receiving json") data = await websocket.receive_json() logger.info("[email protected] - Received json: " + str(data)) logger.info("[email protected] - Sending message: " + str({"message": "greetings"})) await websocket.send_json({"message": "greetings"}) except Exception as e: logger.critical( f"[email protected] - Receieve/Send: Error: {str(type(e))}" ) logger.critical( f"[email protected] - Receieve/Send: Trace: {str(e)}") logger.critical( f"[email protected] - Receieve/Send: Not closing or unsubscribing" ) return 1 await websocket.close()