Skip to content

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:

  1. Create a template
  2. Create a Plan
  3. Add a Stage
  4. Add a Step
  5. Create a Run
  6. Execute the Run
  7. Create a new Step
  8. Execute the new Step
  9. 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 = {
    "plan": {
        "name": "example",
        "owner": "Cryton",
        "dynamic": True,
        "stages": []
    }
}

STAGE = {
    "name": "no delay stage {{ id }}",
    "trigger_type": "delta",
    "trigger_args": {
        "seconds": 0
    },
    "steps": []
}

STEP = {
    "name": "initial step",
    "step_type": "worker/execute",
    "is_init": True,
    "arguments": {
        "module": "command",
        "module_arguments": {
            "cmd": "whoami"
        }
    }
}

STEP_REUSABLE = {
    "name": "reusable step {{ id }}",
    "step_type": "worker/execute",
    "arguments": {
        "module": "command",
        "module_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 Core repository.

It's best to switch to the example directory, so we will assume that's true.

cd /path/to/cryton-core/examples/dynamic-execution-example/

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