Exemplo n.º 1
0
def sigint_handler():
    '''Handles user interrupt signal
    '''
    app_log.warning("\nOuch... you just killed me... (x_x)")
    loop = get_event_loop()
    loop.stop()
    loop.close()
Exemplo n.º 2
0
 def show(self):
     while True:
         self._name = readline("Enter a name", default=self.default['name'])
         # - tags & difficulties
         self._tags = choose(self.default['tags'], "Tags Selection", min_count=2, multi=True, custom=True)
         tags_str = '\n - '.join(self._tags)
         print(f"Selected tags:\n - {tags_str}")
         self._difficulties = choose(self.default['difficulties'], "Difficulties Selection",
                                     min_count=2, multi=True, custom=True)
         difficulties_str = '\n - '.join(self._difficulties)
         print(f"Selected difficulties:\n - {difficulties_str}")
         # - flag
         self._flag_prefix = readline("Enter flag prefix", default=self.default['flag_prefix'])
         self._flag_suffix = readline("Enter flag prefix", default=self.default['flag_suffix'])
         # - domain
         self._domain = readline("Enter domain", default=self.default['domain'])
         # - docker
         self._docker_user = readline("Enter docker user", default=self.default['docker_user'])
         self._docker_registry = readline("Enter docker registry host", default=self.default['docker_registry'])
         # confirm, abort or retry
         answer = confirm(f"Are you ok with this configuration:\n{json.dumps(self.result, indent=2)}", abort=True)
         if answer == Answer.YES:
             return True
         elif answer == Answer.ABORT:
             app_log.warning("user canceled the operation.")
             return False
Exemplo n.º 3
0
    def export(self, export_dir, include_disabled):
        '''Exports challenge

        Creates an archive containing all of the challenge "exportable" files.
        '''
        if not self.is_standalone:
            app_log.warning(f"challenge ignored (not standalone): {self.slug}.")
            return {'ignored': True}
        if not include_disabled and not self.enabled:
            app_log.warning(f"challenge ignored (disabled): {self.slug}.")
            return {'ignored': True}
        app_log.info(f"exporting {self.slug}...")
        archive_name = f'{self.slug}.tgz'
        archive_path = export_dir.joinpath(archive_name)
        with tarfile.open(str(archive_path), 'w:gz') as arch:
            for entry in self.exportable():
                arch.add(entry.path, arcname=entry.name)
        checksum_name = f'{archive_name}.sha256'
        checksum_path = export_dir.joinpath(checksum_name)
        archive_hash = hash_file(archive_path)
        checksum_path.write_text(f'{archive_hash}  {archive_name}\n')
        app_log.info("done.")
        return {
            'archive_path': archive_path,
            'checksum_path': checksum_path
        }
Exemplo n.º 4
0
async def build(api, args):
    '''Builds at least one challenge
    '''
    if not args.yes and confirm(
            'do you really want to perform a build?') == Answer.NO:
        app_log.warning("operation cancelled by user.")
        return False
    err_sep = format_text(f'{HSEP[:35]} [STDERR] {HSEP[:35]}', 'red')
    out_sep = format_text(f'{HSEP[:35]} [STDOUT] {HSEP[:35]}', 'blue')
    exc_sep = format_text(f'{HSEP[:35]} [EXCEPT] {HSEP[:35]}', 'magenta')
    chall_sep = format_text(HSEP, 'blue', attrs=['bold'])
    success = True
    async for build_result in api.build(tags=args.tags,
                                        categories=args.categories,
                                        slug=args.slug,
                                        dev=args.dev,
                                        timeout=args.timeout):
        rcode = build_result['rcode']
        chall_desc = format_text(f"{build_result['slug']}", 'blue')
        chall_status = api.rcode2str(rcode)
        print(chall_sep)
        print(f"{chall_desc} {chall_status}")
        if rcode < 0:
            success = False
            print(exc_sep)
            print(build_result['exception'])
        elif rcode > 0:
            print(out_sep)
            print(build_result['stdout'].decode().strip())
            print(err_sep)
            print(build_result['stderr'].decode().strip())
    return success
Exemplo n.º 5
0
async def enum(api, args):
    '''Enumerates challenges
    '''
    found = False
    for challenge in api.enum(tags=args.tags,
                              categories=args.categories,
                              slug=args.slug):
        slug, conf = challenge['slug'], challenge['conf']
        if not found:
            found = True
            print("challenges:")
        if conf is None:
            app_log.error(
                f"configuration missing. Run `mkctf configure -s {slug}`")
            continue
        chall_entry = f"{TAB}- {slug} [{conf['category'].upper()}]"
        color = 'green' if conf['enabled'] else 'red'
        chall_entry = format_text(chall_entry, color, attrs=['bold'])
        del conf['slug']
        del conf['enabled']
        del conf['category']
        description = challenge['description'] or format_text(
            'empty description', 'red', attrs=['bold'])
        chall_details = format_dict2str(conf)
        chall_details += "\n+ description:"
        chall_details += indent(f"\n{HSEP}\n{description}\n{HSEP}", TAB)
        print(chall_entry)
        if not args.summarize:
            print(indent(chall_details[1:], TAB * 2))
    if not found:
        app_log.warning("no challenge found.")
    return found
Exemplo n.º 6
0
async def enum(api, args):
    '''Enumerates challenges
    '''
    found = False
    print("challenges:")
    for challenge in api.enum(args.tags, args.slug):
        slug, conf = challenge['slug'], challenge['conf']
        found = True
        if conf is None:
            app_log.error(
                f"configuration missing. Run `mkctf configure -s {slug}`")
            continue
        static = ' [STANDALONE]' if conf['standalone'] else ''
        chall_entry = f"{TAB}- {slug}{static}"
        color = 'green' if conf['enabled'] else 'red'
        chall_entry = format_text(chall_entry, color, attrs=['bold'])
        del conf['enabled']
        del conf['standalone']
        del conf['slug']
        description = challenge['description'] or format_text(
            'empty description', 'red', attrs=['bold'])
        text = format_dict2str(conf)
        text += "\n+ description:"
        text += indent(f"\n{HSEP}\n{description}\n{HSEP}", TAB)
        print(chall_entry)
        print(indent(text[1:], TAB * 2))
    if not found:
        app_log.warning("no challenge found matching given constraints.")
    return found
Exemplo n.º 7
0
async def status(api, args):
    '''Determines the status of at least one challenge
    '''
    if not args.yes and not cli.confirm('do you really want to check status?'):
        app_log.warning("operation cancelled by user.")
        return False
    err_sep = format_text(f'{HSEP[:35]} [STDERR] {HSEP[:35]}', 'red')
    out_sep = format_text(f'{HSEP[:35]} [STDOUT] {HSEP[:35]}', 'blue')
    exc_sep = format_text(f'{HSEP[:35]} [EXCEPT] {HSEP[:35]}', 'magenta')
    chall_sep = format_text(HSEP, 'blue', attrs=['bold'])
    success = True
    async for build_result in api.status(args.tags, args.slug, args.timeout):
        rcode = build_result['rcode']
        chall_desc = format_text(f"{build_result['slug']}", 'blue')
        chall_status = format_rcode2str(rcode)
        print(chall_sep)
        print(f"{chall_desc} {chall_status}")
        if rcode < 0:
            success = False
            print(exc_sep)
            print(build_result['exception'])
        elif rcode > 0:
            print(out_sep)
            print(build_result['stdout'].decode().strip())
            print(err_sep)
            print(build_result['stderr'].decode().strip())
    return success
Exemplo n.º 8
0
async def delete(api, args):
    '''Deletes a challenge
    '''
    if not args.yes and confirm(
            f"do you really want to run delete?") == Answer.NO:
        app_log.warning("operation cancelled by user.")
        return False
    result = api.delete(args.slug)
    return result['deleted']
Exemplo n.º 9
0
 async def _run(self, script, dev, timeout):
     '''Runs a script as an asynchronous subprocess
     '''
     script_path = Path(script)
     script_parents = script_path.parents
     script = f'./{script_path.name}'
     if script_path.is_absolute():
         cwd = script_parents[0]
     else:
         cwd = self.path
         if len(script_parents) > 1:
             cwd /= script_parents[0]
     app_log.info(f"running {script_path.name} within {cwd}.")
     if dev:
         proc = await create_subprocess_exec(script,
                                             '--dev',
                                             stdout=PIPE,
                                             stderr=PIPE,
                                             cwd=str(cwd))
     else:
         proc = await create_subprocess_exec(script,
                                             stdout=PIPE,
                                             stderr=PIPE,
                                             cwd=str(cwd))
     rcode = -1
     stdout = None
     stderr = None
     exception = None
     try:
         stdout, stderr = await wait_for(proc.communicate(),
                                         timeout=timeout)
         rcode = proc.returncode
     except TimeoutError:
         proc.terminate()
         exception = 'timeout'
     except CalledProcessError as exc:
         proc.terminate()
         rcode = exc.returncode
         stdout = exc.stdout
         stderr = exc.stderr
         exception = 'called process error'
     except Exception as exc:
         exception = str(exc)
     if rcode == 0:
         app_log.info("subprocess terminated successfully.")
     else:
         app_log.warning(
             f"subprocess terminated unsuccessfully (rcode={rcode}).")
     return {
         'rcode': rcode,
         'stdout': stdout,
         'stderr': stderr,
         'exception': exception
     }
Exemplo n.º 10
0
    def find_chall(self, slug):
        '''Finds challenge
        '''
        chall_path = self.working_dir().joinpath(slug)

        if not chall_path.is_dir():
            app_log.warning(f"challenge not found: {slug}")
            return None
        repo_conf = self.get_conf()
        chall_conf_path = chall_path.joinpath(repo_conf['files']['chall_conf'])
        return Challenge(chall_conf_path, repo_conf)
Exemplo n.º 11
0
 def delete_chall(self, slug):
     '''Deletes a challenge
     '''
     chall = self.find_chall(slug)
     if chall is None:
         return False
     if not cli.confirm(f"do you really want to remove {slug}?"):
         app_log.warning("operation cancelled by user.")
         return False
     rmtree(str(chall.working_dir()))
     return True
Exemplo n.º 12
0
 def __init__(self, repo_dir, general_conf, conf=None):
     '''[summary]
     '''
     self._conf = conf
     self._repo_dir = repo_dir
     self._conf_path = repo_dir.joinpath('.mkctf', 'repo.yml')
     self._general_conf = general_conf
     if not conf:
         self._conf = RepositoryConfiguration.load(self._conf_path)
         if not self._conf.validate(throw=False):
             app_log.warning("repository requires initialization.")
Exemplo n.º 13
0
 def find(self, slug):
     '''Finds challenge
     '''
     chall_path = self.challenges_dir.joinpath(slug)
     if not chall_path.is_dir():
         app_log.warning(f"{slug} not found")
         return None
     chall = Challenge(self, chall_path)
     if not chall.conf.validate(throw=False):
         app_log.warning(f"{slug} has invalid configuration")
         return None
     return chall
Exemplo n.º 14
0
 def init(self):
     '''Create challenge files
     '''
     self.path.mkdir(parents=True, exist_ok=True)
     dir_list = self.repo.conf.directories(self.conf.category)
     for directory in dir_list:
         if not self._create_dir(directory):
             app_log.warning(f"directory exists already: {directory}")
     file_list = self.repo.conf.files(self.conf.category)
     for file in file_list:
         if not self._create_file(file):
             app_log.warning(f"file exists already: {file}")
     return self._save_conf()
Exemplo n.º 15
0
async def renew_flag(api, args):
    '''[summary]
    '''
    if not args.yes and confirm(
            'do you really want to renew flags?') == Answer.NO:
        app_log.warning("operation cancelled by user.")
        return False
    renewed = [
        None for _ in api.renew_flag(tags=args.tags,
                                     categories=args.categories,
                                     slug=args.slug,
                                     size=args.size)
    ]
    return renewed
Exemplo n.º 16
0
 def create_chall(self, configuration=None):
     '''Creates a challenge
     '''
     repo_conf = self.get_conf()
     chall_conf = self.__make_chall_conf(override=configuration)
     if not chall_conf['slug']:
         app_log.warning("aborted challenge creation, slug is empty.")
         return False
     chall_conf_path = self.working_dir().joinpath(
         chall_conf['slug'], repo_conf['files']['chall_conf'])
     chall = Challenge(chall_conf_path, repo_conf)
     if not chall.create():
         return False
     chall.set_conf(chall_conf)
     return True
Exemplo n.º 17
0
    def export(self, export_dir, include_disabled):
        '''Export the challenge

        Creates a gzipped tar archive containing all of the challenge "exportable" files
        '''
        if not include_disabled and not self.conf.enabled:
            app_log.warning(f"export ignored {self.conf.slug} (disabled)")
            return

        app_log.info(f"exporting {self.conf.slug}...")
        archive_name = self.conf.static_url.split('/')[-1]
        if not archive_name:
            app_log.error(
                f"export ignored {self.conf.slug} (invalid/empty static_url)")
            app_log.error(
                f"running `mkctf-cli update-meta` should be enough to fix this issue."
            )
            return

        archive_path = export_dir.joinpath(archive_name)
        checksum_file = ChecksumFile()
        with tarfile.open(str(archive_path), 'w:gz') as arch:
            for directory in self.repo.conf.directories(self.conf.category,
                                                        public_only=True):
                dir_path = self.path.joinpath(directory)
                for entry in scandir(dir_path):
                    entry_path = Path(entry.path)
                    if entry_path.is_dir():
                        app_log.warning(
                            f"export ignored {entry_path} within {self.conf.slug} (directory)"
                        )
                        continue
                    checksum_file.add(entry_path)
                    app_log.debug(f"adding {entry_path} to archive...")
                    arch.add(str(entry_path), arcname=entry.name)
            with tempfile.NamedTemporaryFile('w') as tmpfile:
                tmpfile.write(checksum_file.content)
                tmpfile.flush()
                app_log.debug(f"adding checksum.sha256 to archive...")
                arch.add(tmpfile.name, arcname='checksum.sha256')

        arch_checksum_file = ChecksumFile()
        arch_checksum_file.add(archive_path)
        export_dir.joinpath(f'{archive_name}.sha256').write_text(
            arch_checksum_file.content)
        return archive_path
Exemplo n.º 18
0
    def create(self):
        '''Creates challenge files
        '''
        self.working_dir().mkdir(parents=True, exist_ok=True)

        directories = self.repo_conf['directories']['public'][::]
        directories += self.repo_conf['directories']['private']

        for directory in directories:
            if not self.__create_dir(directory):
                app_log.warning(f"directory exists already: {directory}")

        for file in self.repo_conf['files']['txt']:
            if not self.__create_file(file):
                app_log.warning(f"file exists already: {file}")

        bin_files = [
            self.repo_conf['files']['build'],
            self.repo_conf['files']['deploy'],
            self.repo_conf['files']['status']
        ]

        for file in bin_files:
            if not self.__create_file(file, executable=True):
                app_log.warning(f"file exists already: {file}")

        return True
Exemplo n.º 19
0
 def __dict_check(self, obj, expected_obj, chain=''):
     '''Recursive diffing and type checking between two dicts
     '''
     if isinstance(expected_obj, dict) and isinstance(obj, dict):
         for ek, ev in expected_obj.items():
             v = obj.get(ek)
             chain += f'.{ek}'
             if v is None:
                 app_log.warning(
                     f"invalid {self.TYPE} configuration - missing key: {chain}"
                 )
                 return False
             if not self.__dict_check(v, ev, chain):
                 return False
     elif isinstance(expected_obj, tuple):
         if not isinstance(obj, expected_obj):
             app_log.warning(
                 f"invalid {self.TYPE} configuration - {chain} has invalid type: {obj} ({type(obj)})"
             )
             return False
     else:
         app_log.warning(
             f"invalid {self.TYPE} configuration - {chain} should be a dict: {obj}"
         )
         return False
     return True
Exemplo n.º 20
0
    def scan(self, tags=[], categories=[]):
        '''Returns a list of challenges having at least one tag in common with tags

        An empty list of tags means all tags
        '''
        tags = set(tags)
        categories = set(categories)
        keep = lambda entry: entry.is_dir() and not entry.name.startswith('.')
        challs = []
        for chall_dirent in scandir(self.challenges_dir, keep):
            chall = Challenge(self,
                              self.challenges_dir.joinpath(chall_dirent.name))
            if not chall.conf.validate(throw=False):
                app_log.warning(
                    f"{chall.conf.slug} has invalid configuration => skipped")
                continue
            if tags and not tags.intersection(chall.conf.tags):
                app_log.warning(
                    f"{chall.conf.slug} does not match selected tags => skipped"
                )
                continue
            if categories and chall.conf.category not in categories:
                app_log.warning(
                    f"{chall.conf.slug} does not match selected categories => skipped"
                )
                continue
            challs.append(chall)
        return sorted(challs, key=lambda chall: chall.conf.slug)
Exemplo n.º 21
0
 def get_conf(self, key=[]):
     '''Returns the configuration or specific values
     '''
     if isinstance(key, str):
         key = [key]
     if not self.conf_path.is_file():
         app_log.warning(f"file not found: {self.conf_path}")
         return None
     conf = config_load(self.conf_path)
     if len(key) == 0:
         return conf
     value = conf
     while len(key) > 0:
         keyp = key[0]
         key = key[1:]
         value = value.get(keyp)
         if value is None:
             app_log.warning(f"missing key: {keyp}")
             return None
         if len(key) > 0 and not isinstance(value, dict):
             app_log.warning(f"missing key: {key[0]}")
             return None
     return value