Example #1
0
    def list_scenario_instance(self, scenario_name_or_path):
        scenario_name = normalize_scenario_name(scenario_name_or_path)
        scenario_instance_dir_path = find_scenario_instance_dir(
            self.base_dir, scenario_name)

        if scenario_instance_dir_path is None:
            print(
                f'[cloudgoat] Error: No scenario instance for "{scenario_name}" found.'
                f" Try: cloudgoat.py list deployed")
            return

        terraform = Terraform(
            working_dir=os.path.join(scenario_instance_dir_path, "terraform"))

        show_retcode, show_stdout, show_stderr = terraform.show(
            capture_output=False, no_color=IsNotFlagged)
        if show_retcode != 0:
            display_terraform_step_error("terraform show", show_retcode,
                                         show_stdout, show_stderr)
            return
        else:
            print(
                f"\n[cloudgoat] terraform show completed with no error code.")

        return
Example #2
0
    def list_deployed_scenario_instances(self):
        deployed_scenario_instances = list()
        for scenario_name in self.scenario_names:
            scenario_instance_dir_path = find_scenario_instance_dir(
                self.base_dir, scenario_name)

            if scenario_instance_dir_path is None:
                continue
            else:
                deployed_scenario_instances.append(scenario_instance_dir_path)

        if not deployed_scenario_instances:
            print(
                f'\n  No scenario instance directories exist. Try "list undeployed" or'
                f' "list all"\n')
            return
        else:
            print(
                f"\n  Deployed scenario instances: {len(deployed_scenario_instances)}"
            )

        for scenario_instance_dir_path in deployed_scenario_instances:
            directory_name = os.path.basename(scenario_instance_dir_path)
            scenario_name, cgid = directory_name.split("_cgid")

            print(f"\n    {scenario_name}"
                  f"\n        CGID: {'cgid' + cgid}"
                  f"\n        Path: {scenario_instance_dir_path}")

        print("")
Example #3
0
    def list_all_scenarios(self):
        undeployed_scenarios = list()
        deployed_scenario_instance_paths = list()

        for scenario_name in self.scenario_names:
            scenario_instance_dir_path = find_scenario_instance_dir(
                self.base_dir, scenario_name)
            if scenario_instance_dir_path:
                deployed_scenario_instance_paths.append(
                    scenario_instance_dir_path)

            else:
                undeployed_scenarios.append(scenario_name)

        print(
            f"\n  Deployed scenario instances: {len(deployed_scenario_instance_paths)}"
        )

        for scenario_instance_dir_path in deployed_scenario_instance_paths:
            directory_name = os.path.basename(scenario_instance_dir_path)
            scenario_name, cgid = directory_name.split("_cgid")
            print(f"\n    {scenario_name}"
                  f"\n        CGID: {'cgid' + cgid}"
                  f"\n        Path: {scenario_instance_dir_path}")

        print(f"\n  Undeployed scenarios: {len(undeployed_scenarios)}")

        # Visual spacing.
        if undeployed_scenarios:
            print(f"")

        for scenario_name in undeployed_scenarios:
            print(f"    {scenario_name}")

        print(f"")
Example #4
0
 def test_find_scenario_instance_dir(self):
     core.python.utils.dirs_at_location = unittest.mock.Mock(return_value=[
         '/tmp/other_scenario_takeover_cgid5o8kwrb5ir',
         '/tmp/ecs_takeover_cgidkcjqvxvjh8',
     ])
     self.assertEqual(
         find_scenario_instance_dir('/tmp', 'ecs_takeover'),
         '/tmp/ecs_takeover_cgidkcjqvxvjh8',
     )
     core.python.utils.dirs_at_location.assert_called_with('/tmp')
Example #5
0
    def list_undeployed_scenarios(self):
        undeployed_scenarios = list()
        for scenario_name in self.scenario_names:
            if not find_scenario_instance_dir(self.base_dir, scenario_name):
                undeployed_scenarios.append(scenario_name)

        if undeployed_scenarios:
            return print(
                f"\n  Undeployed scenarios: {len(undeployed_scenarios)}\n\n    "
                + f"\n    ".join(undeployed_scenarios) + f"\n")
        else:
            return print(
                f'\n  All scenarios have been deployed. Try "list deployed" or "list'
                f' all"\n')
Example #6
0
    def destroy_scenario(self,
                         scenario_name_or_path,
                         profile,
                         confirmed=False):
        # Information gathering.
        scenario_name = normalize_scenario_name(scenario_name_or_path)
        scenario_instance_dir_path = find_scenario_instance_dir(
            self.base_dir, scenario_name)

        if scenario_instance_dir_path is None:
            print(
                f'[cloudgoat] Error: No scenario instance for "{scenario_name}" found.'
                f" Try: cloudgoat.py list deployed")
            return

        instance_name = os.path.basename(scenario_instance_dir_path)

        # Confirmation.
        if not confirmed:
            delete_permission = input(
                f'Destroy "{instance_name}"? [y/n]: ').strip()
            if not delete_permission or not delete_permission[0].lower(
            ) == "y":
                print(f"\nCancelled destruction of {instance_name}.\n")
                return

        # Terraform execution.
        terraform_directory = os.path.join(scenario_instance_dir_path,
                                           "terraform")

        if os.path.exists(
                os.path.join(terraform_directory, "terraform.tfstate")):
            terraform = Terraform(working_dir=terraform_directory)

            cgid = extract_cgid_from_dir_name(
                os.path.basename(scenario_instance_dir_path))

            destroy_retcode, destroy_stdout, destroy_stderr = terraform.destroy(
                capture_output=False,
                var={
                    "cgid": cgid,
                    "cg_whitelist": list(),
                    "profile": profile,
                    "region": self.aws_region,
                },
                no_color=IsNotFlagged,
            )
            if destroy_retcode != 0:
                display_terraform_step_error("terraform destroy",
                                             destroy_retcode, destroy_stdout,
                                             destroy_stderr)
                return
            else:
                print(
                    "\n[cloudgoat] terraform destroy completed with no error code."
                )
        else:
            print(
                f"\nNo terraform.tfstate file was found in the scenario instance's"
                f' terraform directory, so "terraform destroy" will not be run.'
            )

        # Scenario instance directory trashing.
        trash_dir = create_dir_if_nonexistent(self.base_dir, "trash")

        trashed_instance_path = os.path.join(
            trash_dir, os.path.basename(scenario_instance_dir_path))

        shutil.move(scenario_instance_dir_path, trashed_instance_path)

        print(
            f"\nSuccessfully destroyed {instance_name}."
            f"\nScenario instance files have been moved to {trashed_instance_path}"
        )

        return
Example #7
0
    def destroy_all_scenarios(self, profile):
        # Information gathering.
        extant_scenario_instance_names_and_paths = list()
        for scenario_name in self.scenario_names:
            scenario_instance_dir_path = find_scenario_instance_dir(
                self.base_dir, scenario_name)

            if scenario_instance_dir_path is None:
                continue
            else:
                extant_scenario_instance_names_and_paths.append(
                    (scenario_name, scenario_instance_dir_path))
                print(f"Scenario instance for {scenario_name} found.")

        if not extant_scenario_instance_names_and_paths:
            print(f"\n  No scenario instance directories exist.\n")
            return
        else:
            print(
                f"\n  {len(extant_scenario_instance_names_and_paths)} scenario"
                f" instance directories found.")

        # Iteration.
        success_count, failure_count, skipped_count = 0, 0, 0

        for scenario_name, instance_path in extant_scenario_instance_names_and_paths:
            print(f"\n--------------------------------\n")

            # Confirmation.
            delete_permission = input(f'Destroy "{scenario_name}"? [y/n]: ')

            if not delete_permission.strip()[0].lower() == "y":
                skipped_count += 1
                print(f"\nSkipped destruction of {scenario_name}.\n")
                continue

            # Terraform execution.
            terraform_directory = os.path.join(instance_path, "terraform")

            if os.path.exists(
                    os.path.join(terraform_directory, "terraform.tfstate")):
                terraform = Terraform(working_dir=terraform_directory)

                cgid = extract_cgid_from_dir_name(
                    os.path.basename(instance_path))

                destroy_retcode, destroy_stdout, destroy_stderr = terraform.destroy(
                    capture_output=False,
                    var={
                        "cgid": cgid,
                        "cg_whitelist": list(),
                        "profile": profile,
                        "region": self.aws_region,
                    },
                    no_color=IsNotFlagged,
                )
                if destroy_retcode != 0:
                    display_terraform_step_error(
                        "terraform destroy",
                        destroy_retcode,
                        destroy_stdout,
                        destroy_stderr,
                    )
                    failure_count += 1
                    # Subsequent destroys should not be skipped when one fails.
                    continue
                else:
                    print(
                        f"\n[cloudgoat] terraform destroy completed with no error code."
                    )
            else:
                print(
                    f"\nNo terraform.tfstate file was found in the scenario instance's"
                    f' terraform directory, so "terraform destroy" will not be run.'
                )

            # Scenario instance directory trashing.
            trash_dir = create_dir_if_nonexistent(self.base_dir, "trash")

            trashed_instance_path = os.path.join(
                trash_dir, os.path.basename(instance_path))

            shutil.move(instance_path, trashed_instance_path)

            success_count += 1

            print(
                f"\nSuccessfully destroyed {scenario_name}."
                f"\nScenario instance files have been moved to {trashed_instance_path}"
            )

        # Iteration summary.
        print(f"\nDestruction complete."
              f"\n    {success_count} scenarios successfully destroyed"
              f"\n    {failure_count} destroys failed"
              f"\n    {skipped_count} skipped\n")

        return
Example #8
0
    def create_scenario(self, scenario_name_or_path, profile):
        scenario_name = normalize_scenario_name(scenario_name_or_path)
        scenario_dir = os.path.join(self.scenarios_dir, scenario_name)

        if not scenario_dir or not scenario_name or not os.path.exists(
                scenario_dir):
            if not scenario_name:
                return print(
                    f"No recognized scenario name was entered. Did you mean one of"
                    f" these?\n    " + f"\n    ".join(self.scenario_names))
            else:
                return print(
                    f"No scenario named {scenario_name} exists in the scenarios"
                    f" directory. Did you mean one of these?"
                    f"\n    " + f"\n    ".join(self.scenario_names))

        if not os.path.exists(self.whitelist_path):
            cg_whitelist = self.configure_or_check_whitelist(auto=True)
        else:
            cg_whitelist = self.configure_or_check_whitelist()

        if not cg_whitelist:
            print(
                f"A valid whitelist.txt file must exist in the {self.base_dir}"
                f' directory before "create" may be used.')
            return

        # Create a scenario-instance folder in the project root directory.
        # This command should fail with an explanatory error message if a
        # scenario-instance of the same root name (i.e. without the CGID) already
        # exists.
        extant_dir = find_scenario_instance_dir(self.base_dir, scenario_name)
        if extant_dir is not None:
            destroy_and_recreate = input(
                f"You already have an instance of {scenario_name} deployed."
                f" Do you want to destroy and recreate it (y) or cancel (n)? [y/n]: "
            )

            if destroy_and_recreate.strip().lower() == "y":
                self.destroy_scenario(scenario_name, profile, confirmed=True)
            else:
                instance_name = os.path.basename(extant_dir)
                print(
                    f"\nCancelled destruction and recreation of {instance_name}.\n"
                )
                return

        cgid = generate_cgid()
        scenario_instance_dir_path = os.path.join(self.base_dir,
                                                  f"{scenario_name}_{cgid}")

        # Copy all the terraform files from the "/scenarios/scenario-name" folder
        # to the scenario-instance folder.
        source_dir_contents = os.path.join(scenario_dir, ".")
        shutil.copytree(source_dir_contents, scenario_instance_dir_path)

        if os.path.exists(os.path.join(scenario_instance_dir_path,
                                       "start.sh")):
            print(f"\nNow running {scenario_name}'s start.sh...")
            start_script_process = subprocess.Popen(
                ["sh", "start.sh"], cwd=scenario_instance_dir_path)
            start_script_process.wait()
        else:
            pass

        terraform = Terraform(
            working_dir=os.path.join(scenario_instance_dir_path, "terraform"))

        init_retcode, init_stdout, init_stderr = terraform.init(
            capture_output=False, no_color=IsNotFlagged)
        if init_retcode != 0:
            display_terraform_step_error("terraform init", init_retcode,
                                         init_stdout, init_stderr)
            return
        else:
            print(
                f"\n[cloudgoat] terraform init completed with no error code.")

        plan_retcode, plan_stdout, plan_stderr = terraform.plan(
            capture_output=False,
            var={
                "cgid": cgid,
                "cg_whitelist": cg_whitelist,
                "profile": profile,
                "region": self.aws_region,
            },
            no_color=IsNotFlagged,
        )
        # For some reason, `python-terraform`'s `terraform init` returns "2" even
        # when it appears to succeed. For that reason, it will temporarily permit
        # retcode 2.
        if plan_retcode not in (0, 2):
            display_terraform_step_error("terraform plan", plan_retcode,
                                         plan_stdout, plan_stderr)
            return
        else:
            print(
                f"\n[cloudgoat] terraform plan completed with no error code.")

        apply_retcode, apply_stdout, apply_stderr = terraform.apply(
            capture_output=False,
            var={
                "cgid": cgid,
                "cg_whitelist": cg_whitelist,
                "profile": profile,
                "region": self.aws_region,
            },
            skip_plan=True,
            no_color=IsNotFlagged,
        )
        if apply_retcode != 0:
            display_terraform_step_error("terraform apply", apply_retcode,
                                         apply_stdout, apply_stderr)
            return
        else:
            print(
                f"\n[cloudgoat] terraform apply completed with no error code.")

        # python-terraform uses the '-json' flag by default.
        # The documentation for `output` suggests using output_cmd to receive the
        # library's standard threeple return value.
        # Can't use capture_output here because we need to write stdout to a file.
        output_retcode, output_stdout, output_stderr = terraform.output_cmd()

        if output_retcode != 0:
            display_terraform_step_error("terraform output", output_retcode,
                                         output_stdout, output_stderr)
            return
        else:
            print(
                f"\n[cloudgoat] terraform output completed with no error code."
            )

        # Within this output will be values that begin with "cloudgoat_output".
        # Each line of console output which contains this tag will be written into
        # a text file named "start.txt" in the scenario-instance folder.
        start_file_path = os.path.join(scenario_instance_dir_path, "start.txt")
        with open(start_file_path, "w") as start_file:
            for line in output_stdout.split("\n"):
                if line.count("cloudgoat_output") != 0:
                    start_file.write(line + "\n")

        print(
            f"\n[cloudgoat] Output file written to:\n\n    {start_file_path}\n"
        )