Dynamic execution
To support dynamic security testing. We've added support for creating dynamic plans. They allow the user to create an empty Plan/Stage and create their agent to control the execution instead of Cryton's advanced scheduler.
Features¶
- Create a Plan/Step/Stage for dynamic execution (an empty list of Stages/Steps can be provided)
- Add Step to Stage execution and execute it
- Add Stage to Plan execution and start it
- Added Steps are automatically set as a successor of the last Step (only if the
is_init
variable is not set to True and a possible parent Step exists)
Limitations¶
- Dynamic plan must have the
dynamic
variable set to True - If you don't want to pass any Stages/Steps you must provide an empty list
- Each Stage and Step must have a unique name in the same Plan (utilize inventory variables to overcome this limitation)
- The Stage/Step you're trying to add must be valid
- Run's Plan must contain the instance (Stage/Step) you are trying to execute
- You cannot create multiple executions for an instance (you can execute an instance only once) under the same Plan execution
Example using Python¶
You will probably want to automate these actions rather than using CLI to do them. For this purpose, we will create a simple Python script that will:
- Create a template
- Create a Plan
- Add a Stage
- Add a Step
- Create a Run
- Execute the Run
- Create a new Step
- Execute the new Step
- Get the Run report
Requirements
- Cryton Core is running (REST API is accessible at localhost:8000)
- Worker is registered in Core and running
- module command is accessible from the Worker
Download the example script:
curl -O https://cryton.gitlab-pages.ics.muni.cz/cryton//execution-phase/dynamic_example.py
wget https://cryton.gitlab-pages.ics.muni.cz/cryton//execution-phase/dynamic_example.py
Update the WORKER_ID
variable, and run the script:
python3 dynamic_example.py
Show the example
import requests
import yaml
import time
WORKER_ID = 0
TEMPLATE = {"name": "example", "dynamic": True, "stages": {}}
STAGE = {"no-delay-stage-{{ id }}": {"steps": {}}}
STEP = {"initial-step": {"is_init": True, "module": "command", "arguments": {"command": "whoami"}}}
STEP_REUSABLE = {"reusable-step-{{ id }}": {"module": "command", "arguments": {"cmd": "{{ command }}"}}}
def get_api_root():
api_address = "localhost"
api_port = 8000
return f"http://{api_address}:{api_port}/api/"
if __name__ == "__main__":
# Check if the Worker is specified
if WORKER_ID < 1:
raise Exception("Please specify a correct Worker ID at the top of the file.")
print(f"Worker id: {WORKER_ID}")
# Get api root
api_root = get_api_root()
# 1. Create a template
r_create_template = requests.post(f"{api_root}templates/", files={"file": yaml.dump(TEMPLATE)})
template_id = r_create_template.json()["id"]
print(f"Template id: {template_id}")
# 2. Create a Plan
r_create_plan = requests.post(f"{api_root}plans/", data={"template_id": template_id})
plan_id = r_create_plan.json()["id"]
print(f"Plan id: {plan_id}")
# 3. Add a Stage
stage_inventory = {"id": 1}
r_create_stage = requests.post(
f"{api_root}stages/",
data={"plan_id": plan_id},
files={"file": yaml.dump(STAGE), "inventory_file": yaml.dump(stage_inventory)},
)
stage_id = r_create_stage.json()["id"]
print(f"Stage id: {stage_id}")
# 4. Add a Step
r_create_step = requests.post(f"{api_root}steps/", data={"stage_id": stage_id}, files={"file": yaml.dump(STEP)})
step_id = r_create_step.json()["id"]
print(f"Step id: {step_id}")
# 5. Create a new Run
r_create_run = requests.post(f"{api_root}runs/", data={"plan_id": plan_id, "worker_ids": [WORKER_ID]})
run_id = r_create_run.json()["id"]
print(f"Run id: {run_id}")
# 6. Execute the Run
r_execute_run = requests.post(f"{api_root}runs/{run_id}/execute/", data={"run_id": run_id})
print(f"Run response: {r_execute_run.text}")
# 7. Create a new Step
step_inventory = {"id": 1, "command": "echo test"}
r_create_step2 = requests.post(
f"{api_root}steps/",
data={"stage_id": stage_id},
files={"file": yaml.dump(STEP_REUSABLE), "inventory_file": yaml.dump(step_inventory)},
)
step_id2 = r_create_step2.json()["id"]
print(f"Second step id: {step_id2}")
# 8. Execute the new Step (First, get Stage execution's id)
stage_execution_id = requests.get(f"{api_root}runs/{run_id}/report/").json()["detail"]["plan_executions"][0][
"stage_executions"
][0]["id"]
r_execute_step = requests.post(
f"{api_root}steps/{step_id2}/execute/", data={"stage_execution_id": stage_execution_id}
)
print(f"Second Step response: {r_execute_step.text}")
# 9. Get Run report
for i in range(5):
time.sleep(3)
current_state = requests.get(f"{api_root}runs/{run_id}/").json()["state"]
if current_state == "FINISHED":
break
print(f"Waiting for a final state. Current state: {current_state}")
print()
print("Report: ")
print(yaml.dump(requests.get(f"{api_root}runs/{run_id}/report/").json()["detail"]))
Example using CLI¶
For this example we will assume that:
Requirements
- Cryton Core is running (REST API is accessible at localhost:8000)
- Worker is registered in Core and running
- module command is accessible from the Worker
Files used in this guide can be found in the Cryton repository.
It's best to switch to the example directory, so we will assume that's true.
cd cryton/examples/dynamic-execution/
Building a base Plan and executing it¶
First, we create a template
cryton-cli plan-templates create template.yml
Create a Plan (instance)
cryton-cli plans create <template_id>
Add a Stage to the Plan (update the inventory file to your needs)
cryton-cli stages create <plan_id> stage.yml -i stage-inventory.yml
Add an initial Step to the Stage
cryton-cli steps create <stage_id> step-init.yml
Add a reusable Step to the Stage (update the inventory file to your needs)
cryton-cli steps create <stage_id> step-reusable.yml -i step-reusable-inventory.yml
Create a Worker you want to test on
cryton-cli workers create local
Create a Run
cryton-cli runs create <plan_id> <worker_id>
Execute the Run
cryton-cli runs execute <run_id>
Start a standalone Stage:¶
Add your Stage to the desired Plan (Update the inventory file! Stage names must be unique.)
cryton-cli stages create <plan_id> stage.yml -i stage-inventory.yml
Start your Stage (its trigger) under the desired Plan execution
cryton-cli stages start-trigger <stage_id> <plan_execution_id>
Execute a standalone Step:¶
Add your Step to the desired Stage (Update the inventory file! Step names must be unique.)
cryton-cli steps create <stage_id> step-reusable.yml -i step-reusable-inventory.yml
Execute your Step under the desired Stage execution
cryton-cli steps execute <step_id> <stage_execution_id>
Check the results - works only once the Run is created:¶
cryton-cli runs report 1 --less