def template_staging_directory(staging_directory, problem): """ Templates every file in the staging directory recursively other than problem.json and challenge.py. Args: staging_directory: The path of the staging directory problem: The problem object """ # prepend the staging directory to all dont_template = copy(problem.dont_template) + ["problem.json", "challenge.py", "templates", "__pre_templated"] dont_template_files = list(filter(isfile, dont_template)) dont_template_directories = list(filter(isdir, dont_template)) dont_template_directories = [join(staging_directory, directory) for directory in dont_template_directories] for root, dirnames, filenames in os.walk(staging_directory): if any(os.path.commonprefix([root, path]) == path for path in dont_template_directories): logger.debug("....Not templating anything in the directory '{}'".format(root)) continue for filename in filenames: if filename in dont_template_files: logger.debug("....Not templating the file '{}'".format(filename)) continue fullpath = join(root, filename) try: template_file(fullpath, fullpath, **get_attributes(problem)) except UnicodeDecodeError as e: # tried templating binary file pass
def template_staging_directory( staging_directory, problem, dont_template_files=["problem.json", "challenge.py"], dont_template_directories=["templates"], ): """ Templates every file in the staging directory recursively other than problem.json and challenge.py. Args: staging_directory: The path of the staging directory problem: The problem object dont_template_files: The list of files not to template. Defaults to ["problem.json", "challenge.py"] dont_template_directories: The list of files not to recurse into. Defaults to ["templates"] """ # prepend the staging directory to all dont_template_directories = [join(staging_directory, directory) for directory in dont_template_directories] for root, dirnames, filenames in os.walk(staging_directory): if root in dont_template_directories: continue for filename in filenames: if filename in dont_template_files: continue fullpath = join(root, filename) try: template_file(fullpath, fullpath, **get_attributes(problem)) except UnicodeDecodeError as e: # tried templating binary file pass
def template_staging_directory(staging_directory, problem): """ Templates every file in the staging directory recursively other than problem.json and challenge.py. Args: staging_directory: The path of the staging directory problem: The problem object """ # prepend the staging directory to all dont_template = copy(problem.dont_template) + [ "app/templates", "problem.json", "challenge.py", "templates", "__pre_templated", ] dont_template_files = list(filter(isfile, dont_template)) dont_template_directories = list(filter(isdir, dont_template)) dont_template_directories = [ join(staging_directory, directory) for directory in dont_template_directories ] for root, dirnames, filenames in os.walk(staging_directory): if any( os.path.commonprefix([root, path]) == path for path in dont_template_directories): logger.debug( "....Not templating anything in the directory '{}'".format( root)) continue for filename in filenames: if filename in dont_template_files: logger.debug( "....Not templating the file '{}'".format(filename)) continue fullpath = join(root, filename) try: template_file(fullpath, fullpath, **get_attributes(problem)) except UnicodeDecodeError as e: # tried templating binary file pass
def generate_instance(problem_object, problem_directory, instance_number, staging_directory, deployment_directory=None): """ Runs the setup functions of Problem in the correct order Args: problem_object: The contents of the problem.json problem_directory: The directory to the problem instance_number: The instance number to be generated staging_directory: The temporary directory to store files in deployment_directory: The directory that will be deployed to. Defaults to a deterministic, unique directory generated for each problem,instance pair using the configuration options PROBLEM_DIRECTORY_ROOT and OBFUSCATE_PROBLEM_DIRECTORIES Returns: A dict containing (problem, staging_directory, deployment_directory, files, web_accessible_files, service_file, socket_file) """ logger.debug("Generating instance %d of problem '%s'.", instance_number, problem_object["name"]) logger.debug("...Using staging directory %s", staging_directory) username, new = create_instance_user(problem_object['name'], instance_number) if new: logger.debug("...Created problem user '%s'.", username) else: logger.debug("...Using existing problem user '%s'.", username) if deployment_directory is None: deployment_directory = generate_instance_deployment_directory(username) logger.debug("...Using deployment directory '%s'.", deployment_directory) seed = generate_seed(problem_object['name'], deploy_config.deploy_secret, str(instance_number)) logger.debug("...Generated random seed '%s' for deployment.", seed) copy_path = join(staging_directory, PROBLEM_FILES_DIR) shutil.copytree(problem_directory, copy_path) pretemplated_directory = join(copy_path, "__pre_templated") if isdir(pretemplated_directory): shutil.rmtree(pretemplated_directory) # store cwd to restore later cwd = os.getcwd() os.chdir(copy_path) challenge = load_source("challenge", join(copy_path, "challenge.py")) Problem = update_problem_class(challenge.Problem, problem_object, seed, username, deployment_directory) # run methods in proper order problem = Problem() # reseed and generate flag problem.flag = problem.generate_flag(Random(seed)) problem.flag_sha1 = sha1(problem.flag.encode("utf-8")).hexdigest() logger.debug("...Instance %d flag is '%s'.", instance_number, problem.flag) logger.debug("...Running problem initialize.") problem.initialize() shutil.copytree(copy_path, pretemplated_directory) web_accessible_files = [] def url_for(web_accessible_files, source_name, display=None, raw=False, pre_templated=False): if pre_templated: source_path = join(copy_path, "__pre_templated", source_name) else: source_path = join(copy_path, source_name) problem_hash = problem_object[ "name"] + deploy_config.deploy_secret + str(instance_number) problem_hash = md5(problem_hash.encode("utf-8")).hexdigest() destination_path = join(STATIC_FILE_ROOT, problem_hash, source_name) link_template = "<a href='{}'>{}</a>" web_accessible_files.append( (source_path, join(deploy_config.web_root, destination_path))) uri_prefix = "//" uri = join(uri_prefix, deploy_config.hostname, destination_path) if not raw: return link_template.format( uri, source_name if display is None else display) return uri problem.url_for = functools.partial(url_for, web_accessible_files) logger.debug("...Templating the staging directory") template_staging_directory(copy_path, problem) if isinstance(problem, Compiled): problem.compiler_setup() if isinstance(problem, Remote): problem.remote_setup() if isinstance(problem, FlaskApp): problem.flask_setup() if isinstance(problem, PHPApp): problem.php_setup() if isinstance(problem, Service): problem.service_setup() logger.debug("...Running problem setup.") problem.setup() os.chdir(cwd) all_files = copy(problem.files) if isinstance(problem, Compiled): all_files.extend(problem.compiled_files) if isinstance(problem, Service): all_files.extend(problem.service_files) if not all([isinstance(f, File) for f in all_files]): logger.error("All files must be created using the File class!") raise FatalException for f in all_files: if not isinstance(f, Directory) and not os.path.isfile( join(copy_path, f.path)): logger.error("File '%s' does not exist on the file system!", f) service_file, socket_file = create_service_files(problem, instance_number, staging_directory) logger.debug("...Created service files '%s','%s'.", service_file, socket_file) # template the description problem.description = template_string(problem.description, **get_attributes(problem)) logger.debug("...Instance description: %s", problem.description) return { "problem": problem, "staging_directory": staging_directory, "deployment_directory": deployment_directory, "files": all_files, "web_accessible_files": web_accessible_files, "service_file": service_file, "socket_file": socket_file }
def generate_instance(problem_object, problem_directory, instance_number, staging_directory, deployment_directory=None): """ Runs the setup functions of Problem in the correct order Args: problem_object: The contents of the problem.json problem_directory: The directory to the problem instance_number: The instance number to be generated staging_directory: The temporary directory to store files in deployment_directory: The directory that will be deployed to. Defaults to a deterministic, unique directory generated for each problem,instance pair using the configuration options PROBLEM_DIRECTORY_ROOT and OBFUSCATE_PROBLEM_DIRECTORIES Returns: A dict containing (problem, staging_directory, deployment_directory, files, web_accessible_files, service_file, socket_file) """ logger.debug("Generating instance %d of problem '%s'.", instance_number, problem_object["name"]) logger.debug("...Using staging directory %s", staging_directory) username, new = create_instance_user(problem_object['name'], instance_number) if new: logger.debug("...Created problem user '%s'.", username) else: logger.debug("...Using existing problem user '%s'.", username) if deployment_directory is None: deployment_directory = generate_instance_deployment_directory(username) logger.debug("...Using deployment directory '%s'.", deployment_directory) seed = generate_seed(problem_object['name'], deploy_config.deploy_secret, str(instance_number)) logger.debug("...Generated random seed '%s' for deployment.", seed) copy_path = join(staging_directory, PROBLEM_FILES_DIR) shutil.copytree(problem_directory, copy_path) pretemplated_directory = join(copy_path, "__pre_templated") if isdir(pretemplated_directory): shutil.rmtree(pretemplated_directory) # store cwd to restore later cwd = os.getcwd() os.chdir(copy_path) challenge = load_source("challenge", join(copy_path, "challenge.py")) Problem = update_problem_class(challenge.Problem, problem_object, seed, username, deployment_directory) # run methods in proper order problem = Problem() # reseed and generate flag problem.flag = problem.generate_flag(Random(seed)) logger.debug("...Instance %d flag is '%s'.", instance_number, problem.flag) logger.debug("...Running problem initialize.") problem.initialize() shutil.copytree(copy_path, pretemplated_directory) web_accessible_files = [] def url_for(web_accessible_files, source_name, display=None, raw=False, pre_templated=False): if pre_templated: source_path = join(copy_path, "__pre_templated", source_name) else: source_path = join(copy_path, source_name) problem_hash = problem_object["name"] + deploy_config.deploy_secret + str(instance_number) problem_hash = md5(problem_hash.encode("utf-8")).hexdigest() destination_path = join(STATIC_FILE_ROOT, problem_hash, source_name) link_template = "<a href='{}'>{}</a>" web_accessible_files.append((source_path, join(deploy_config.web_root, destination_path))) uri_prefix = "//" uri = join(uri_prefix, deploy_config.hostname, destination_path) if not raw: return link_template.format(uri, source_name if display is None else display) return uri problem.url_for = functools.partial(url_for, web_accessible_files) logger.debug("...Templating the staging directory") template_staging_directory(copy_path, problem) if isinstance(problem, Compiled): problem.compiler_setup() if isinstance(problem, Remote): problem.remote_setup() if isinstance(problem, FlaskApp): problem.flask_setup() if isinstance(problem, PHPApp): problem.php_setup() if isinstance(problem, Service): problem.service_setup() logger.debug("...Running problem setup.") problem.setup() os.chdir(cwd) all_files = copy(problem.files) if isinstance(problem, Compiled): all_files.extend(problem.compiled_files) if isinstance(problem, Service): all_files.extend(problem.service_files) if not all([isinstance(f, File) for f in all_files]): logger.error("All files must be created using the File class!") raise FatalException for f in all_files: if not os.path.isfile(join(copy_path, f.path)): logger.error("File '%s' does not exist on the file system!", f) service_file, socket_file = create_service_files(problem, instance_number, staging_directory) logger.debug("...Created service files '%s','%s'.", service_file, socket_file) # template the description problem.description = template_string(problem.description, **get_attributes(problem)) logger.debug("...Instance description: %s", problem.description) return { "problem": problem, "staging_directory": staging_directory, "deployment_directory": deployment_directory, "files": all_files, "web_accessible_files": web_accessible_files, "service_file": service_file, "socket_file": socket_file }
def generate_instance(problem_object, problem_directory, instance_number, staging_directory, deployment_directory=None): """ Runs the setup functions of Problem in the correct order Args: problem_object: The contents of the problem.json problem_directory: The directory to the problem instance_number: The instance number to be generated staging_directory: The temporary directory to store files in deployment_directory: The directory that will be deployed to. Defaults to the home directory of the user created. Returns: A tuple containing (problem, staging_directory, home_directory, files) """ username, home_directory = create_instance_user(problem_object["name"], instance_number) seed = generate_seed(problem_object["name"], deploy_config.DEPLOY_SECRET, str(instance_number)) copypath = join(staging_directory, PROBLEM_FILES_DIR) shutil.copytree(problem_directory, copypath) # store cwd to restore later cwd = os.getcwd() os.chdir(copypath) challenge = load_source("challenge", join(copypath, "challenge.py")) if deployment_directory is None: deployment_directory = home_directory Problem = update_problem_class(challenge.Problem, problem_object, seed, username, deployment_directory) # run methods in proper order problem = Problem() # reseed and generate flag problem.flag = problem.generate_flag(Random(seed)) problem.initialize() web_accessible_files = [] def url_for(web_accessible_files, source_name, display=None, raw=False): source_path = join(copypath, source_name) problem_hash = problem_object["name"] + deploy_config.DEPLOY_SECRET + str(instance_number) problem_hash = md5(problem_hash.encode("utf-8")).hexdigest() destination_path = join(STATIC_FILE_ROOT, problem_hash, source_name) link_template = "<a href='{}'>{}</a>" web_accessible_files.append((source_path, join(deploy_config.WEB_ROOT, destination_path))) uri_prefix = "//" uri = join(uri_prefix, deploy_config.HOSTNAME, destination_path) if not raw: return link_template.format(uri, source_name if display is None else display) return uri problem.url_for = functools.partial(url_for, web_accessible_files) template_staging_directory(copypath, problem) if isinstance(problem, Compiled): problem.compiler_setup() if isinstance(problem, Remote): problem.remote_setup() if isinstance(problem, FlaskApp): problem.flask_setup() if isinstance(problem, PHPApp): problem.php_setup() if isinstance(problem, Service): problem.service_setup() problem.setup() os.chdir(cwd) all_files = copy(problem.files) if isinstance(problem, Compiled): all_files.extend(problem.compiled_files) if isinstance(problem, Service): all_files.extend(problem.service_files) assert all([isinstance(f, File) for f in all_files]), "files must be created using the File class." for f in all_files: assert os.path.isfile(join(copypath, f.path)), "{} does not exist on the file system.".format(f) service_file = create_service_file(problem, instance_number, staging_directory) # template the description problem.description = template_string(problem.description, **get_attributes(problem)) return { "problem": problem, "staging_directory": staging_directory, "home_directory": home_directory, "deployment_directory": deployment_directory, "files": all_files, "web_accessible_files": web_accessible_files, "service_file": service_file, }
def generate_instance( problem_object, problem_directory, instance_number, staging_directory, deployment_directory=None, ): """ Runs the setup functions of Problem in the correct order Args: problem_object: The contents of the problem.json problem_directory: The directory to the problem instance_number: The instance number to be generated staging_directory: The temporary directory to store files in deployment_directory: The directory that will be deployed to. Defaults to a deterministic, unique directory generated for each problem,instance pair using the configuration options PROBLEM_DIRECTORY_ROOT and OBFUSCATE_PROBLEM_DIRECTORIES Returns: A dict containing (problem, staging_directory, deployment_directory, files, web_accessible_files, service_file, socket_file) """ logger.debug( "Generating instance %d of problem '%s'.", instance_number, problem_object["unique_name"], ) logger.debug("...Using staging directory %s", staging_directory) username, new = create_instance_user(problem_object["name"], instance_number) if new: logger.debug("...Created problem user '%s'.", username) else: logger.debug("...Using existing problem user '%s'.", username) if deployment_directory is None: deployment_directory = generate_instance_deployment_directory(username) logger.debug("...Using deployment directory '%s'.", deployment_directory) seed = generate_seed(problem_object["name"], shared_config.deploy_secret, str(instance_number)) logger.debug("...Generated random seed '%s' for deployment.", seed) copy_path = join(staging_directory, PROBLEM_FILES_DIR) shutil.copytree(problem_directory, copy_path) pretemplated_directory = join(copy_path, "__pre_templated") if isdir(pretemplated_directory): shutil.rmtree(pretemplated_directory) # store cwd to restore later cwd = os.getcwd() os.chdir(copy_path) challenge = SourceFileLoader("challenge", join(copy_path, "challenge.py")).load_module() Problem = update_problem_class(challenge.Problem, problem_object, seed, username, deployment_directory) # run methods in proper order problem = Problem() # reseed and generate flag problem.flag = problem.generate_flag(Random(seed)) problem.flag_sha1 = sha1(problem.flag.encode("utf-8")).hexdigest() logger.debug("...Instance %d flag is '%s'.", instance_number, problem.flag) logger.debug("...Running problem initialize.") problem.initialize() shutil.copytree(copy_path, pretemplated_directory) web_accessible_files = [] def url_for(web_accessible_files, source_name, display=None, raw=False, pre_templated=False): if pre_templated: source_path = join(copy_path, "__pre_templated", source_name) else: source_path = join(copy_path, source_name) problem_hash = (problem_object["name"] + shared_config.deploy_secret + str(instance_number)) problem_hash = md5(problem_hash.encode("utf-8")).hexdigest() destination_path = join(STATIC_FILE_ROOT, problem_hash, source_name) link_template = "<a href='{}'>{}</a>" web_accessible_files.append( (source_path, join(shared_config.web_root, destination_path))) uri_prefix = "//" uri = join(uri_prefix, local_config.hostname, destination_path) if not raw: return link_template.format( uri, source_name if display is None else display) return uri problem.url_for = functools.partial(url_for, web_accessible_files) logger.debug("...Templating the staging directory") template_staging_directory(copy_path, problem) if isinstance(problem, Compiled): problem.compiler_setup() if isinstance(problem, Remote): problem.remote_setup() if isinstance(problem, FlaskApp): problem.flask_setup() if isinstance(problem, PHPApp): problem.php_setup() if isinstance(problem, Service): problem.service_setup() logger.debug("...Running problem setup.") problem.setup() os.chdir(cwd) all_files = copy(problem.files) if isinstance(problem, Compiled): all_files.extend(problem.compiled_files) if isinstance(problem, Service): all_files.extend(problem.service_files) if not all([isinstance(f, File) for f in all_files]): logger.error("All files must be created using the File class!") raise FatalException for f in all_files: if not isinstance(f, Directory) and not os.path.isfile( join(copy_path, f.path)): logger.error("File '%s' does not exist on the file system!", f) service_file, socket_file = create_service_files(problem, instance_number, staging_directory) logger.debug("...Created service files '%s','%s'.", service_file, socket_file) # template the description # change newline for <br>, otherwise it won't render on the pico website problem.description = template_string(problem.description, **get_attributes(problem)).replace( "\n", "<br>") problem.hints = [ template_string(hint, **get_attributes(problem)).replace("\n", "<br>") for hint in problem.hints ] logger.debug("...Instance description: %s", problem.description) logger.debug("...Instance hints: %s", problem.hints) # Steps to meet cmgr interface if containerize: # Create /challenge directory try: os.mkdir("/challenge", 0o700) except FileExistsError: logger.warn("/challenge already exists in container") # Write flag into /challenge/metadata.json with open("/challenge/metadata.json", "w") as out: metadata = {"flag": problem.flag} json.dump(metadata, out) # Collect web_accessible_files into /challenge/artifacts.tar.gz if len(web_accessible_files) >= 1: logger.debug( f"Collecting web accessible files to artifacts.tar.gz") with tarfile.open("/challenge/artifacts.tar.gz", "w:gz") as tar: for f, _ in web_accessible_files: tar.add(f, arcname=os.path.basename(f)) return { "problem": problem, "staging_directory": staging_directory, "deployment_directory": deployment_directory, "files": all_files, "web_accessible_files": web_accessible_files, "service_file": service_file, "socket_file": socket_file, }