def get_all_problems(): """ Returns a dictionary of name:object mappings """ problems = {} if os.path.isdir(PROBLEM_ROOT): for name in os.listdir(PROBLEM_ROOT): try: problem = get_problem(get_problem_root(name, absolute=True)) problems[name] = problem except FileNotFoundError as e: pass return problems
def undeploy_problems(args, config): """ Main entrypoint for problem undeployment """ problem_names = args.problem_paths if args.bundle: bundle_problems = [] for bundle_path in args.problem_paths: if isfile(bundle_path): bundle = get_bundle(bundle_path) bundle_problems.extend(bundle["problems"]) else: bundle_sources_path = get_bundle_root(bundle_path, absolute=True) if isdir(bundle_sources_path): bundle = get_bundle(bundle_sources_path) bundle_problems.extend(bundle["problems"]) else: logger.error("Could not find bundle at '%s'.", bundle_path) raise FatalException problem_names = bundle_problems # before deploying problems, load in already_deployed instances already_deployed = {} for path, problem in get_all_problems().items(): already_deployed[problem["name"]] = [] for instance in get_all_problem_instances(path): already_deployed[problem["name"]].append(instance["instance_number"]) lock_file = join(HACKSPORTS_ROOT, "deploy.lock") if os.path.isfile(lock_file): logger.error("Cannot undeploy while other deployment in progress. If you believe this is an error, " "run 'shell_manager clean'") raise FatalException logger.debug("Obtaining deployment lock file %s", lock_file) with open(lock_file, "w") as f: f.write("1") if args.instances: instance_list = args.instances else: instance_list = list(range(0, args.num_instances)) try: for problem_name in problem_names: problem_root = get_problem_root(problem_name, absolute=True) if isdir(problem_root): problem = get_problem(problem_root) instances = list(filter(lambda x: x in already_deployed[problem["name"]], instance_list)) if len(instances) == 0: logger.warn("No deployed instances %s were found for problem '%s'.", instance_list, problem["name"]) else: logger.debug("Undeploying problem '%s'.", problem["name"]) remove_instances(problem_name, instance_list) logger.info("Problem instances %s were successfully removed from '%s'.", instances, problem["name"]) else: logger.error("Problem '%s' doesn't appear to be installed.", problem_name) raise FatalException finally: logger.debug("Releasing lock file %s", lock_file) os.remove(lock_file)
def deploy_problem(problem_directory, instances=[0], test=False, deployment_directory=None, debug=False): """ Deploys the problem specified in problem_directory. Args: problem_directory: The directory storing the problem instances: The list of instances to deploy. Defaults to [0] test: Whether the instances are test instances or not. Defaults to False. deployment_directory: If not None, the challenge will be deployed here instead of their home directory """ global current_problem, current_instance problem_object = get_problem(problem_directory) current_problem = problem_object["name"] instance_list = [] logger.debug("Beginning to deploy problem '%s'.", problem_object["name"]) for instance_number in instances: current_instance = instance_number staging_directory = generate_staging_directory(problem_name=problem_object["name"], instance_number=instance_number) if test and deployment_directory is None: deployment_directory = join(staging_directory, "deployed") instance = generate_instance(problem_object, problem_directory, instance_number, staging_directory, deployment_directory=deployment_directory) instance_list.append((instance_number, instance)) deployment_json_dir = join(DEPLOYED_ROOT, sanitize_name(problem_object["name"])) if not os.path.isdir(deployment_json_dir): os.makedirs(deployment_json_dir) # ensure that the deployed files are not world-readable os.chmod(DEPLOYED_ROOT, 0o750) # all instances generated without issue. let's do something with them for instance_number, instance in instance_list: problem_path = join(instance["staging_directory"], PROBLEM_FILES_DIR) problem = instance["problem"] deployment_directory = instance["deployment_directory"] logger.debug("...Copying problem files %s to deployment directory %s.", instance["files"], deployment_directory) deploy_files(problem_path, deployment_directory, instance["files"], problem.user, problem.__class__) if test: logger.info("Test instance %d information:", instance_number) logger.info("...Description: %s", problem.description) logger.info("...Deployment Directory: %s", deployment_directory) logger.debug("Cleaning up test instance side-effects.") logger.debug("...Killing user processes.") #This doesn't look great. try: execute("killall -u {}".format(problem.user)) sleep(0.1) except RunProcessError as e: pass logger.debug("...Removing test user '%s'.", problem.user) execute(["userdel", problem.user]) deployment_json_dir = instance["staging_directory"] else: # copy files to the web root logger.debug("...Copying web accessible files: %s", instance["web_accessible_files"]) for source, destination in instance["web_accessible_files"]: if not os.path.isdir(os.path.dirname(destination)): os.makedirs(os.path.dirname(destination)) shutil.copy2(source, destination) install_user_service(instance["service_file"], instance["socket_file"]) # keep the staging directory if run with debug flag # this can still be cleaned up by running "shell_manager clean" if not debug: shutil.rmtree(instance["staging_directory"]) unique = problem_object["name"] + problem_object["author"] + str(instance_number) + deploy_config.deploy_secret deployment_info = { "user": problem.user, "deployment_directory": deployment_directory, "service": os.path.basename(instance["service_file"]), "socket": None if instance["socket_file"] is None else os.path.basename(instance["socket_file"]), "server": problem.server, "description": problem.description, "flag": problem.flag, "instance_number": instance_number, "should_symlink": not isinstance(problem, Service) and len(instance["files"]) > 0, "files": [f.to_dict() for f in instance["files"]] } if isinstance(problem, Service): deployment_info["port"] = problem.port logger.debug("...Port %d has been allocated.", problem.port) instance_info_path = os.path.join(deployment_json_dir, "{}.json".format(instance_number)) with open(instance_info_path, "w") as f: f.write(json.dumps(deployment_info, indent=4, separators=(", ", ": "))) logger.debug("The instance deployment information can be found at '%s'.", instance_info_path) logger.info("Problem instances %s were successfully deployed for '%s'.", instances, problem_object["name"])
def deploy_problem(problem_directory, instances=1, test=False, deployment_directory=None): """ Deploys the problem specified in problem_directory. Args: problem_directory: The directory storing the problem instances: The number of instances to deploy. Defaults to 1. test: Whether the instances are test instances or not. Defaults to False. deployment_directory: If not None, the challenge will be deployed here instead of their home directory """ global current_problem, current_instance problem_object = get_problem(problem_directory) current_problem = problem_object["name"] instance_list = [] for instance_number in range(instances): current_instance = instance_number print('Generating instance {} of "{}".'.format(instance_number, problem_object["name"])) staging_directory = generate_staging_directory() if test and deployment_directory is None: deployment_directory = os.path.join(staging_directory, "deployed") instance = generate_instance( problem_object, problem_directory, instance_number, staging_directory, deployment_directory=deployment_directory, ) instance_list.append(instance) deployment_json_dir = os.path.join(DEPLOYED_ROOT, sanitize_name(problem_object["name"])) if not os.path.isdir(deployment_json_dir): os.makedirs(deployment_json_dir) # ensure that the deployed files are not world-readable os.chmod(DEPLOYED_ROOT, 0o750) # all instances generated without issue. let's do something with them for instance_number, instance in enumerate(instance_list): print('Deploying instance {} of "{}".'.format(instance_number, problem_object["name"])) problem_path = os.path.join(instance["staging_directory"], PROBLEM_FILES_DIR) problem = instance["problem"] deployment_directory = instance["deployment_directory"] deploy_files(problem_path, deployment_directory, instance["files"], problem.user) if test is True: print("Description: {}".format(problem.description)) print("Deployment Directory: {}".format(deployment_directory)) # This doesn't look great. try: execute("killall -u {}".format(problem.user)) sleep(0.1) except RunProcessError as e: pass execute(["userdel", problem.user]) shutil.rmtree(instance["home_directory"]) deployment_json_dir = instance["staging_directory"] else: # copy files to the web root for source, destination in instance["web_accessible_files"]: if not os.path.isdir(os.path.dirname(destination)): os.makedirs(os.path.dirname(destination)) shutil.copy2(source, destination) install_user_service(instance["service_file"]) # delete staging directory shutil.rmtree(instance["staging_directory"]) unique = problem_object["name"] + problem_object["author"] + str(instance_number) + deploy_config.DEPLOY_SECRET iid = md5(unique.encode("utf-8")).hexdigest() deployment_info = { "user": problem.user, "service": os.path.basename(instance["service_file"]), "server": problem.server, "description": problem.description, "flag": problem.flag, "iid": iid, "instance_number": instance_number, "files": [f.to_dict() for f in problem.files], } if isinstance(problem, Service): deployment_info["port"] = problem.port instance_info_path = os.path.join(deployment_json_dir, "{}.json".format(instance_number)) with open(instance_info_path, "w") as f: f.write(json.dumps(deployment_info, indent=4, separators=(", ", ": "))) print("The instance deployment information can be found at {}.".format(instance_info_path))
def problem_builder(args, config): """ Main entrypoint for package building operations. """ #Grab a problem_path problem_base_path = args.problem_paths.pop() problem_paths = find_problems(problem_base_path) if len(problem_paths) == 0: logging.critical("No problems found under '%s'!", problem_base_path) raise FatalException for problem_path in problem_paths: problem = get_problem(problem_path) logger.debug("Starting to package: '%s'.", problem["name"]) paths = {} if args.staging_dir is None: paths["staging"] = join(problem_path, "__staging") else: paths["staging"] = join(args.staging_dir, "__staging") paths["debian"] = join(paths["staging"], "DEBIAN") paths["data"] = join(paths["staging"], get_problem_root(problem["name"])) paths["install_data"] = join(paths["data"], "__files") #Make all of the directories, order does not matter with makedirs [makedirs(staging_path) for _, staging_path in paths.items() if not isdir(staging_path)] args.ignore.append("__staging") full_copy(problem_path, paths["data"], ignore=args.ignore) # note that this chmod does not work correct if on a vagrant shared folder, # so we need to package the problems elsewhere chmod(paths["data"], 0o750) problem_to_control(problem, paths["debian"]) postinst_dependencies(problem, problem_path, paths["debian"], paths["install_data"]) deb_directory = args.out if args.out is not None else getcwd() def format_deb_file_name(problem): """ Prepare the file name of the deb package according to deb policy. Args: problem: the problem object Returns: An acceptable file name for the problem. """ raw_package_name = "{}-{}-{}.deb".format( sanitize_name(problem.get("organization", "ctf")), sanitize_name(problem.get("pkg_name", problem["name"])), sanitize_name(problem.get("version", "1.0-0")) ) return raw_package_name deb_path = join(deb_directory, format_deb_file_name(problem)) shell = spur.LocalShell() result = shell.run(["fakeroot", "dpkg-deb", "--build", paths["staging"], deb_path]) if result.return_code != 0: logger.error("Error building problem deb for '%s'.", problem["name"]) logger.error(result.output) else: logger.info("Problem '%s' packaged successfully.", problem["name"]) logger.debug("Clearning up '%s' staging directory '%s'.", problem["name"], paths["staging"]) rmtree(paths["staging"]) if len(args.problem_paths) >= 1: return problem_builder(args, config)
def undeploy_problems(args, config): """ Main entrypoint for problem undeployment """ problem_names = args.problem_paths if args.bundle: bundle_problems = [] for bundle_path in args.problem_paths: if isfile(bundle_path): bundle = get_bundle(bundle_path) bundle_problems.extend(bundle["problems"]) else: bundle_sources_path = get_bundle_root(bundle_path, absolute=True) if isdir(bundle_sources_path): bundle = get_bundle(bundle_sources_path) bundle_problems.extend(bundle["problems"]) else: logger.error("Could not find bundle at '%s'.", bundle_path) raise FatalException problem_names = bundle_problems # before deploying problems, load in already_deployed instances already_deployed = {} for path, problem in get_all_problems().items(): already_deployed[problem["name"]] = [] for instance in get_all_problem_instances(path): already_deployed[problem["name"]].append( instance["instance_number"]) lock_file = join(HACKSPORTS_ROOT, "deploy.lock") if os.path.isfile(lock_file): logger.error( "Cannot undeploy while other deployment in progress. If you believe this is an error, " "run 'shell_manager clean'") raise FatalException logger.debug("Obtaining deployment lock file %s", lock_file) with open(lock_file, "w") as f: f.write("1") if args.instances: instance_list = args.instances else: instance_list = list(range(0, args.num_instances)) try: for problem_name in problem_names: problem_root = get_problem_root(problem_name, absolute=True) if isdir(problem_root): problem = get_problem(problem_root) instances = list( filter(lambda x: x in already_deployed[problem["name"]], instance_list)) if len(instances) == 0: logger.warn( "No deployed instances %s were found for problem '%s'.", instance_list, problem["name"]) else: logger.debug("Undeploying problem '%s'.", problem["name"]) remove_instances(problem_name, instance_list) logger.info( "Problem instances %s were successfully removed from '%s'.", instances, problem["name"]) else: logger.error("Problem '%s' doesn't appear to be installed.", problem_name) raise FatalException finally: logger.debug("Releasing lock file %s", lock_file) os.remove(lock_file)
def deploy_problem(problem_directory, instances=[0], test=False, deployment_directory=None, debug=False): """ Deploys the problem specified in problem_directory. Args: problem_directory: The directory storing the problem instances: The list of instances to deploy. Defaults to [0] test: Whether the instances are test instances or not. Defaults to False. deployment_directory: If not None, the challenge will be deployed here instead of their home directory """ global current_problem, current_instance problem_object = get_problem(problem_directory) current_problem = problem_object["name"] instance_list = [] logger.debug("Beginning to deploy problem '%s'.", problem_object["name"]) for instance_number in instances: current_instance = instance_number staging_directory = generate_staging_directory( problem_name=problem_object["name"], instance_number=instance_number) if test and deployment_directory is None: deployment_directory = join(staging_directory, "deployed") instance = generate_instance(problem_object, problem_directory, instance_number, staging_directory, deployment_directory=deployment_directory) instance_list.append((instance_number, instance)) deployment_json_dir = join(DEPLOYED_ROOT, sanitize_name(problem_object["name"])) if not os.path.isdir(deployment_json_dir): os.makedirs(deployment_json_dir) # ensure that the deployed files are not world-readable os.chmod(DEPLOYED_ROOT, 0o750) # all instances generated without issue. let's do something with them for instance_number, instance in instance_list: problem_path = join(instance["staging_directory"], PROBLEM_FILES_DIR) problem = instance["problem"] deployment_directory = instance["deployment_directory"] logger.debug("...Copying problem files %s to deployment directory %s.", instance["files"], deployment_directory) deploy_files(problem_path, deployment_directory, instance["files"], problem.user, problem.__class__) if test: logger.info("Test instance %d information:", instance_number) logger.info("...Description: %s", problem.description) logger.info("...Deployment Directory: %s", deployment_directory) logger.debug("Cleaning up test instance side-effects.") logger.debug("...Killing user processes.") #This doesn't look great. try: execute("killall -u {}".format(problem.user)) sleep(0.1) except RunProcessError as e: pass logger.debug("...Removing test user '%s'.", problem.user) execute(["userdel", problem.user]) deployment_json_dir = instance["staging_directory"] else: # copy files to the web root logger.debug("...Copying web accessible files: %s", instance["web_accessible_files"]) for source, destination in instance["web_accessible_files"]: if not os.path.isdir(os.path.dirname(destination)): os.makedirs(os.path.dirname(destination)) shutil.copy2(source, destination) install_user_service(instance["service_file"], instance["socket_file"]) # keep the staging directory if run with debug flag # this can still be cleaned up by running "shell_manager clean" if not debug: shutil.rmtree(instance["staging_directory"]) unique = problem_object["name"] + problem_object["author"] + str( instance_number) + deploy_config.deploy_secret deployment_info = { "user": problem.user, "deployment_directory": deployment_directory, "service": os.path.basename(instance["service_file"]), "socket": None if instance["socket_file"] is None else os.path.basename( instance["socket_file"]), "server": problem.server, "description": problem.description, "flag": problem.flag, "instance_number": instance_number, "should_symlink": not isinstance(problem, Service) and len(instance["files"]) > 0, "files": [f.to_dict() for f in instance["files"]] } if isinstance(problem, Service): deployment_info["port"] = problem.port logger.debug("...Port %d has been allocated.", problem.port) instance_info_path = os.path.join(deployment_json_dir, "{}.json".format(instance_number)) with open(instance_info_path, "w") as f: f.write( json.dumps(deployment_info, indent=4, separators=(", ", ": "))) logger.debug( "The instance deployment information can be found at '%s'.", instance_info_path) logger.info("Problem instances %s were successfully deployed for '%s'.", instances, problem_object["name"])
def problem_builder(args, config): """ Main entrypoint for package building operations. """ #Grab a problem_path problem_base_path = args.problem_paths.pop() problem_paths = find_problems(problem_base_path) if len(problem_paths) == 0: logging.critical("No problems found under '%s'!", problem_base_path) raise FatalException for problem_path in problem_paths: problem = get_problem(problem_path) logger.debug("Starting to package: '%s'.", problem["name"]) paths = {} if args.staging_dir is None: paths["staging"] = join(problem_path, "__staging") else: paths["staging"] = join(args.staging_dir, "__staging") paths["debian"] = join(paths["staging"], "DEBIAN") paths["data"] = join(paths["staging"], get_problem_root(problem["name"])) paths["install_data"] = join(paths["data"], "__files") #Make all of the directories, order does not matter with makedirs [ makedirs(staging_path) for _, staging_path in paths.items() if not isdir(staging_path) ] args.ignore.append("__staging") full_copy(problem_path, paths["data"], ignore=args.ignore) # note that this chmod does not work correct if on a vagrant shared folder, # so we need to package the problems elsewhere chmod(paths["data"], 0o750) problem_to_control(problem, paths["debian"]) postinst_dependencies(problem, problem_path, paths["debian"], paths["install_data"]) deb_directory = args.out if args.out is not None else getcwd() def format_deb_file_name(problem): """ Prepare the file name of the deb package according to deb policy. Args: problem: the problem object Returns: An acceptable file name for the problem. """ raw_package_name = "{}-{}-{}.deb".format( sanitize_name(problem.get("organization", "ctf")), sanitize_name(problem.get("pkg_name", problem["name"])), sanitize_name(problem.get("version", "1.0-0"))) return raw_package_name deb_path = join(deb_directory, format_deb_file_name(problem)) shell = spur.LocalShell() result = shell.run( ["fakeroot", "dpkg-deb", "--build", paths["staging"], deb_path]) if result.return_code != 0: logger.error("Error building problem deb for '%s'.", problem["name"]) logger.error(result.output) else: logger.info("Problem '%s' packaged successfully.", problem["name"]) logger.debug("Clearning up '%s' staging directory '%s'.", problem["name"], paths["staging"]) rmtree(paths["staging"]) if len(args.problem_paths) >= 1: return problem_builder(args, config)