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)}" ) self.dns_server_name = payload.get("dns_server_name", "") if not "http_server_name" in payload.keys( ) or not payload["http_server_name"]: logger.critical( f"__init__@api_client.py - No http_server_name on api token payload: {str(payload)}" ) self.http_server_name = payload.get("http_server_name", "")
def get_api_token(self): if os.environ.get("API_TOKEN", None): return os.environ.get("API_TOKEN") if self.option("api_token", None): return self.option("api_token") logger.critical("api token required") self.exit(1)
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.option("no_api_wait"): if not self.api_client.wait_for_up(): logger.critical( "run@http_server.py - Could not connect to api. quitting") self.exit(1) if self.option("no_sync"): logger.info("run@http_server.py - Skipping syncing api token") else: self.api_client.sync() self.boot() self.start_servers() try: count = 0 while self.is_alive(): # zones don't need to be refreshed for http server # may want to do something in the future thought count = count + 1 sleep(1) except KeyboardInterrupt: pass
def get_workers(self): if self.option("debug", None) or self.option("reload", None): logger.critical( "get_workers@api_server.py - Cannot use debug or reload with workers. Skipping." ) return None return self.option("workers", 5)
async def show( user_repo: UserRepo = Depends(UserRepo()), token: TokenPayload = Depends(ScopedTo("profile")), user: User = Depends(current_user), ): logger.critical("Loading user") item = user_repo.set_results(user).data() return UserResponse(user=item)
async def run(self): from boucanpy.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 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 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)
def post(self, url: str, data=None, fail=True): data = data or {} headers = self.get_default_headers() logger.info("post@api_client.py - Posting URL: " + str(self.url(url))) res = requests.post(self.url(url), json=data, headers=headers, verify=self.verify_ssl) 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 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
def get(self, url: str, params=None, fail=True): params = params or {} headers = self.get_default_headers() logger.info("get@api_client.py - Getting URL: " + str(self.url(url))) res = requests.get(self.url(url), headers=headers, params=params, verify=self.verify_ssl) if fail: if res.status_code != 200: logger.critical( f"get@api_client.py - Error getting API {self.url(url)}: " + str(res.json())) res.raise_for_status() return res.json()
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 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()
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 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()
async def run(self): self.db_register() if self.option("target", False) == False: self.set_option("target", "dev") if self.option("target") == "env": 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( name="mydnsserver") dns_server = factory("DnsServerFactory").create() logger.info("run@db_seed.py - Creating http_server") _http_server = factory("HttpServerFactory").create( name="myhttpserver") http_server = factory("HttpServerFactory").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, http_server=http_server, ) zone3 = factory("ZoneFactory").create(domain="differentzone.com", ip="127.0.1.1") logger.info("run@db_seed.py - Creating api_tokens") factory("ApiTokenFactory").create(dns_server=dns_server) factory("ApiTokenFactory").create(http_server=http_server) factory("ApiTokenFactory").create(dns_server=dns_server, http_server=http_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) logger.info("run@db_seed.py - Creating http_requests") for i in range(35): factory("HttpRequestFactory").create(http_server=http_server, zone=zone2) for i in range(35): factory("HttpRequestFactory").create(http_server=http_server, zone=zone3) else: logger.critical("run@db_seed.py - invalid target set for seeder") self.exit(1)
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
async def run(self): app = "boucanpy.api.main:api" kwargs = self.get_kwargs() if self.should_import_check(): logger.info("run@api_server.py - Performing import check") from boucanpy.api.main import api logger.critical( "run@api_server.py - Starting api server with options: {}".format( str(kwargs))) from boucanpy.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 boucanpy.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 boucanpy 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 boucanpy api in worker mode...") return supervisor.run(server.run, sockets=[sock]) else: sockets = None logger.warning( f"run@api_server.py - Running boucanpy api in standard mode..." ) return await server.serve(sockets=sockets)