示例#1
0
 async def on_after_insert(mapper, connection, target):
     logger.warning("on_after_insert: Zone")
     print("on_after_insert", mapper, connection, target)
     publisher = await make_redis()
     res = await publisher.publish_json("channel:auth", {
         "type": "MESSAGE",
         "name": "ZONE_CREATED",
         "payload": ""
     })
示例#2
0
    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)
示例#3
0
 async def on_after_insert(mapper, connection, target):
     try:
         publisher = await make_redis()
         res = await publisher.publish_json(
             "channel:auth",
             {
                 "type": "MESSAGE",
                 "name": "DNS_REQUEST_CREATED",
                 "payload": ""
             },
         )
     except Exception as e:
         logger.warning(f"on_after_insert error: {str(e)}")
示例#4
0
    def resolve(self, request, handler):
        reply = request.reply()
        logger.warning(request.__class__.__name__)
        qname = request.q.qname
        qtype = QTYPE[request.q.qtype]
        for record in self.zones:
            # Check if label & type match
            if record.match(request.q):
                a = copy.copy(record.rr)
                reply.add_answer(a)
        if reply.rr:
            return reply

        if not reply.rr:
            reply.header.rcode = RCODE.NXDOMAIN
        return reply
示例#5
0
async def index(
        form: DnsRequestCreateForm,
        dns_request_repo: DnsRequestRepo = Depends(DnsRequestRepo),
        zone_repo: ZoneRepo = Depends(ZoneRepo),
        token: str = ScopedTo("dns-request:create"),
):

    data = dict(form)
    domain_name = data["name"]
    if zone_repo.filter(
            literal(domain_name).contains(
                zone_repo.model_column("domain"))).exists():
        data["zone_id"] = zone_repo.data().id
    else:
        logger.warning(f"No zone found for dns request {domain_name}")
    dns_request = dns_request_repo.create(data).data()
    return DnsRequestResponse(dns_request=dns_request)
示例#6
0
 def wait_for_up(self):
     seconds = 0
     while True:
         if seconds > 60:
             logger.warning("could not connect to api. api not up")
             return False
         logger.info("checking for api status")
         try:
             sleep(1)
             self.get_status()
             sleep(3)
             return True
         except Exception as e:
             logger.info("api check not ready after {} seconds: {}".format(
                 str(seconds), str(e.__class__.__name__)))
         seconds = seconds + 1
         sleep(1)
示例#7
0
async def store(
        form: ApiTokenCreateForm,
        api_token_repo: ApiTokenRepo = Depends(ApiTokenRepo()),
        dns_server_repo: DnsServerRepo = Depends(DnsServerRepo()),
        token: TokenPayload = Depends(ScopedTo("api-token:create")),
        user: User = Depends(current_user),
):
    dns_server = dns_server_repo.first_or_fail(id=form.dns_server_id).results()

    scopes = []
    for requested_scope in form.scopes.split(" "):
        request_scope_satisfied = False
        for user_token in token.scopes:
            # TODO: double check this, pretty lenient
            # if a:b in a:b:c
            if user_token in requested_scope:
                request_scope_satisfied = True
        if not request_scope_satisfied:
            logger.warning(
                f"[email protected]: Attempt to create unauthorized scope {requested_scope}"
            )
            raise HTTPException(403, detail="unauthorized")
        else:
            scopes.append(requested_scope)

    # TODO: use better randomness

    token = create_bearer_token(
        data={
            "sub": user.id,
            "scopes": " ".join(scopes),
            "dns_server_name": dns_server.name,
        })

    data = {
        "scopes": " ".join(scopes),
        "token": str(token),
        "expires_at": form.expires_at,
        "dns_server_id": form.dns_server_id,
    }

    api_token = api_token_repo.create(data).data()
    return ApiTokenResponse(api_token=api_token)
示例#8
0
    def refresh_zones_if_needed(self):
        logger.info(
            "refresh_zones_if_needed@api_client.py - Checking for New Zones and Records..."
        )
        old_zones = self.zones
        new_zones = self.get_zones()
        # TODO: fix this mess

        if len(old_zones) != len(new_zones):
            logger.warning(
                f"refresh_zones_if_needed@api_client.py - Zone Length mistmatch. New or Changed Zone Found: {str(old_zones)} != {str(new_zones)}. Reloading zones."
            )
            self.load_zones()
            return True

        for nz in new_zones:
            # make sure new zones are in old zones
            is_nz_exists = False
            for oz in old_zones:
                if oz.domain == nz.domain and oz.ip == nz.ip:
                    nz_dns_records = nz.dns_records or []
                    for nrec in nz_dns_records:
                        is_rec_satisfied = False
                        oz_dns_records = oz.dns_records or []
                        if len(nz_dns_records) != len(oz_dns_records):
                            logger.warning(
                                f"refresh_zones_if_needed@api_client.py - Zone Record Length mistmatch {str(len(nz_dns_records))} != {str(len(oz_dns_records))}. New or Changed Zone Record Found: {str(nz_dns_records)} != {str(oz_dns_records)}. Reloading zones."
                            )
                            self.load_zones()
                            return True
                        for orec in oz_dns_records:
                            if orec.record == nrec.record and orec.sort == nrec.sort:
                                is_rec_satisfied = True
                        if not is_rec_satisfied:
                            logger.warning(
                                f"refresh_zones_if_needed@api_client.py - New or Changed Zone Record {str(nz)}: {str(nrec)} found for server. Reloading zones."
                            )
                            self.load_zones()
                            return True
                    is_nz_exists = True
            if not is_nz_exists:
                logger.warning(
                    f"refresh_zones_if_needed@api_client.py - New or Changed Zone {str(nz)} found for server. Reloading zones."
                )
                self.load_zones()
                return True

        logger.info(
            "refresh_zones_if_needed@api_client.py - No New Zones or Records Found. All is well"
        )

        return False
示例#9
0
 def wait_for_up(self):
     attempts = 0
     while True:
         if attempts > 60:
             logger.warning("could not connect to api. api not up")
             return False
         logger.info(
             f"wait_for_up@api_client.py - checking for api status : {self.url('/status')}"
         )
         try:
             sleep(1)
             self.get_status()
             sleep(3)
             return True
         except Exception as e:
             logger.info(
                 "wait_for_up@api_client.py - api check not ready after {} attempts: {}"
                 .format(str(attempts), str(e.__class__.__name__)))
         attempts = attempts + 1
         sleep(1)
示例#10
0
    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"]
示例#11
0
 def zones_to_records(self, zones):
     records = []
     for zone in zones:
         try:
             logger.warning(f"loading zone: {zone.domain} ({zone.id})")
             for rr in self.zone_to_rr(zone):
                 logger.debug(
                     "registering zone rr name {} and type {}".format(
                         rr.rname, QTYPE[rr.rtype]
                     )
                 )
                 record = Record(zone, rr)
                 records.append(record)
                 logger.debug(" %2d: %s", len(records), record)
         except Exception as e:
             raise RuntimeError(
                 f'Error processing line ({e.__class__.__name__}: {e}) "{zone.domain}"'
             ) from e
         logger.debug("zone map generated {}".format(str(zone)))
     logger.debug("%d zone resource records generated", len(records))
     return records
示例#12
0
    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])
示例#13
0
async def login(
        ws_access_token: bool = False,
        db: Session = Depends(session),
        form: OAuth2PasswordRequestForm = Depends(),
):
    user = db.query(User).filter_by(email=form.username).first()
    if not user or not user.hashed_password:
        logger.warning(f"user exists failed for {form.username}")
        raise HTTPException(status_code=401,
                            detail="Incorrect email or password")
    if not verify_password(form.password, user.hashed_password):
        logger.warning(f"hash verification failed for {form.username}")
        raise HTTPException(status_code=401,
                            detail="Incorrect email or password")

    if user.mfa_secret:  # mfa is enabled
        scopes = "profile mfa_required"
    elif user.is_superuser:
        scopes = "profile super zone user dns-request api-token refresh dns-record dns-server"  # grant access to super routes
    else:
        scopes = "profile zone user:list dns-request api-token:list api-token:create api-token:destroy refresh dns-record:list dns-record:show dns-server:list dns-server:show"
    logger.warning(f"creating token with scopes {scopes}")
    token = create_bearer_token(data={"sub": user.id, "scopes": scopes})
    data = {"token_type": "bearer", "access_token": str(token.decode())}
    if ws_access_token:
        data["ws_access_token"] = create_bearer_token(
            data={
                "sub": user.id,
                "scopes": "zone:publish dns-request:publish refresh"
            })
    return PasswordAuthResponse(**data)
示例#14
0
    async def __call__(
            self,
            request: Request,
            bl_token_repo: BlackListedTokenRepo = Depends(
                BlackListedTokenRepo()),
            token: str = Security(oauth2),
    ) -> TokenPayload:

        token = verify_jwt_token(token, bl_token_repo,
                                 self._leeway)  # proper validation goes here
        if self._satisfy not in ["all", "one"]:
            logger.warning(f"Invalid satisfy value: {self._satisfy}")

        if self._satisfy == "one":
            if not token_has_one_required_scopes(token, self._scopes):
                vmsg = f"Token does not have one of the required scopes: {str(self._scopes)}"
                logger.error(vmsg)
                abort(code=403, msg="Forbidden", debug=vmsg)
        else:
            if not token_has_required_scopes(token, self._scopes):
                vmsg = f"Token does not have all required scopes: {str(self._scopes)}"
                logger.error(vmsg)
                abort(code=403, msg="Forbidden", debug=vmsg)
        return token
示例#15
0
def verify_jwt_token(token: str, bl_token_repo=None, leeway=0) -> TokenPayload:
    from bountydns.api import config  # environment must be loaded

    if bl_token_repo:
        if bl_token_repo.exists(token=token):
            raise HTTPException(status_code=403, detail="Forbidden")
    else:
        logger.warning(
            "verifying token without checking the blacklist. dangerous!")
    try:
        payload = jwt.decode(token,
                             config.API_SECRET_KEY,
                             algorithms=config.JWT_ALGORITHM,
                             leeway=leeway)
    except jwt.PyJWTError:
        raise HTTPException(status_code=403, detail="Forbidden")

    return TokenPayload(
        payload=payload,
        scopes=payload.get("scopes", "").split(" "),
        token=token,
        sub=payload.get("sub", ""),
        exp=payload.get("exp", ""),
    )
示例#16
0
async def login(
        ws_access_token: bool = False,
        db: Session = Depends(async_session),
        form: OAuth2PasswordRequestForm = Depends(),
):
    username = form.username.lower() if form.username else ""

    user = db.query(User).filter_by(email=username).first()

    if not user or not user.hashed_password:
        logger.warning(f"user exists failed for {username}")
        raise HTTPException(status_code=401,
                            detail="Incorrect email or password")

    if not verify_password(form.password, user.hashed_password):
        logger.warning(f"hash verification failed for {username}")
        raise HTTPException(status_code=401,
                            detail="Incorrect email or password")

    if user.mfa_secret:  # mfa is enabled
        scopes = "profile mfa_required"
    elif user.is_superuser:
        scopes = SUPER_SCOPES  # grant access to super routes
    else:
        scopes = NORMAL_SCOPES

    logger.warning(f"creating token with scopes {scopes}")

    token = create_bearer_token(data={"sub": user.id, "scopes": scopes})
    data = {"token_type": "bearer", "access_token": str(token)}

    if ws_access_token and int(environ.get("BROADCAST_ENABLED", 0)) == 1:
        # TODO: make it so that you cannot get publish access without base scope
        data["ws_access_token"] = create_bearer_token(data={
            "sub": user.id,
            "scopes": PUBLISH_SCOPES
        })
    return PasswordAuthResponse(**data)
示例#17
0
    async def run(self):
        env = self.option("env")
        self.load_env(f"api.{env}")
        self.db_register()
        failed = []
        if self.option("confirm"):
            for class_name, model in models.items():
                for item in self.session().query(model).all():
                    logger.warning(f"run@db_truncate.py - Deleting {item}")
                    try:
                        self.session().delete(item)
                        self.session().commit()
                    except Exception as e:
                        failed.append((item, e))
        else:
            logger.warning(
                "run@db_truncate.py - You must confirm to drop data")

        if len(failed) > 0:
            logger.warning("run@db_truncate.py - Encountered errors")
            for f in failed:
                print("Failed:", item[0])
                print("Error", item[1])
示例#18
0
    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)
示例#19
0
    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)