Leonidas Framework Documentation

repository·master·Indexed 20 days ago

https://github.com/reverseclabs/leonidas

A framework for defining and executing cloud attacker Tactics, Techniques, and Procedures (TTPs) using YAML. Leonidas supports AWS and Kubernetes environments, allowing developers to transform YAML definitions into executable APIs, Sigma detection rules, and human-readable documentation. It includes a test case orchestrator called Leo for executing killchains and provides utilities for deploying via CI/CD pipelines or Kubernetes manifests.

Tokens
11.9K
Snippets
40
Records
48
Agent score
70%

What's inside Leonidas

  1. Overview of Leonidas

    master

    Leonidas is a framework for executing attacker actions in the cloud. It uses a YAML-based format to define cloud attacker Tactics, Techniques, and Procedures (TTPs) along with their associated detection properties.

    These YAML definitions can be compiled into three distinct outputs:

    1. A web API: Exposes each test case as an individual endpoint.
    2. Sigma rules: For detection engineering.
    3. Documentation: For visualizing the defined TTPs.

    Leonidas supports both AWS environments and Kubernetes environments.

  2. Configure permissions for AWS and Kubernetes

    master

    The permissions field is mandatory and defines the access required for the test case.

    AWS

    List individual IAM permissions required (e.g., secretsmanager:GetSecretValue). Do not use managed policy names.

    Kubernetes

    List items following the Kubernetes RBAC rule format. Each item must include a namespaced boolean.

    permissions:
      - namespaced: true
        apiGroups: [""]
        resources:
          - serviceaccounts
        verbs:
          - create

    If no permissions are required for a Kubernetes test case, use an empty block:

    permissions:
      - namespaced: true
        apiGroups: [""]
        resources: [""]
        verbs: [""]
  3. Define cloud attacker TTPs in YAML

    master

    Leonidas uses a YAML-based format to define attacker actions. A definition includes metadata (name, author, description, category, mitre_ids), the target platform, required permissions, input arguments, executors (the code that performs the action), and detection properties (Sigma IDs and event sources).

    ---
    name: Enumerate Cloudtrails for a Given Region
    author: Nick Jones
    description: |
      An adversary may attempt to enumerate the configured trails, to identify what actions will be logged and where they will be logged to. In AWS, this may start with a single call to enumerate the trails applicable to the default region.
    category: Discovery
    mitre_ids:
      - T1526
    platform: aws
    permissions:
      - cloudtrail:DescribeTrails
    input_arguments:
    executors:
      sh:
        code: |
          aws cloudtrail describe-trails
      leonidas_aws:
        implemented: True
        clients:
          - cloudtrail
        code: |
          result = clients["cloudtrail"].describe_trails()
    detection:
      sigma_id: 48653a63-085a-4a3b-88be-9680e9adb449
      status: experimental
      level: low
      sources:
        - name: "cloudtrail"
          attributes:
            eventName: "DescribeTrails"
            eventSource: "*.cloudtrail.amazonaws.com"
  4. Understand the Leonidas API log entry format

    master

    Leonidas generates a JSON log entry for every executed test case. The structure of this entry varies slightly depending on whether you are using the AWS or Kubernetes platform, but both follow a core pattern of request and response blocks.

    Core Fields

    • request:
      • usecase: The specific test case executed.
      • args: Parameters passed to the request.
      • timestamp: Execution time (UTC).
      • identity (AWS only): The identity block used for the test case.
    • response:
      • Contains the result of the execution. In AWS, this is the contents of the result variable. In Kubernetes, this includes the shell command, stdout, stderr, and exit_code.
  5. Implement Executors (sh, leonidas_aws, leonidas_kube)

    master

    Executors define how a test case is run.

    sh

    Used for standalone CLI execution and Kubernetes test cases. The code field is a multi-line string containing CLI commands. It supports Jinja2 templating (e.g., {{ variable_name }}) using values from input_arguments.

    leonidas_aws

    Used to embed Python attack logic into the Leonidas web API. Requires:

    • implemented: Boolean (set to True to include in API generation).
    • clients: A list of boto3 client names (e.g., ['iam', 's3']).
    • code: A multi-line string containing the Python code to be embedded as an API endpoint.

    leonidas_kube

    Used to signal that a test case should be embedded in the web API. It relies on the sh executor for the actual logic (e.g., kubectl commands).

  6. Define input_arguments for test cases

    master

    The input_arguments field is a YAML dictionary of arguments required for execution. These are exposed as HTTP POST parameters to the API and as variables within leonidas_aws executors.

    Each argument requires:

    • description: A text explanation of the parameter.
    • type: Supported types are str, int, and file.
    • value: A default value.

    Special Case: file type If the type is file, the argument must be named custom_yaml. Files can be uploaded via Content-Type: multipart/form-data or specified as YAML in the definition. For Kubernetes, the file is saved to /tmp/custom.yml and used with kubectl -f apply.

    input_arguments:
      custom_yaml:
        description: |
          YAML manifest for the pod - leave this empty to use the default spec
        type: file
        value: 
          apiVersion: v1
          kind: Pod
          # ... rest of manifest
  7. Generate documentation

    master

    To generate documentation from your definitions, use the generator to produce Markdown files in output/docs.

    To create a prettified HTML version of the documentation using mkdocs:

    1. Generate the markdown files.
    2. Navigate to the output directory.
    3. Run mkdocs build to create an HTML site in output/site.
    4. Alternatively, run mkdocs serve to view the documentation locally.
    # Generate markdown
    poetry run ./generator.py docs
    
    # Build HTML site
    cd ../output
    mkdocs build
    
    # Or serve locally
    mkdocs serve
  8. Access logs for Leonidas on Kubernetes

    master

    Leonidas in Kubernetes uses a streaming logging sidecar pattern. The primary container logs execution events to stdout in JSON format, while the Flask runtime output is handled by a sidecar container.

    Retrieve JSON execution logs

    To get the pure JSON logs for test executions, use the following command. This is the preferred method for retrieving execution data:

    kubectl logs -l app=leonidas -f

    Retrieve Flask runtime logs

    To debug the Flask server itself (e.g., to see HTTP requests or startup info), you must target the log-sidecar container:

    kubectl logs -l app=leonidas -c log-sidecar -f
    # Get JSON execution logs
    kubectl logs -l app=leonidas -f
    
    # Get Flask runtime logs for debugging
    kubectl logs -l app=leonidas -c log-sidecar -f
  9. Run the Leonidas API Locally

    master

    For development, you can build and run the Leonidas Python API on your local machine. The API will listen at http://127.0.0.1:5000 and use your default AWS (~/.aws/config) or Kubernetes credentials.

    Steps

    1. cd generator
    2. poetry install
    3. poetry run ./generator.py generate-aws-api and/or generate-kube-api
    4. cd ../output/leonidas
    5. poetry install
    6. poetry run python leonidas.py

    Authentication

    While it defaults to local profiles, you can override credentials by supplying a role ARN to assume, access keys, JWT tokens, or TLS client certificates in your requests.

    cd generator
    poetry install
    poetry run ./generator.py generate-aws-api
    cd ../output/leonidas
    poetry install
    poetry run python leonidas.py
  10. Install the Leonidas Generator locally

    master

    To build documentation or Sigma rules from your definitions, you must install the generator locally using poetry. Navigate to the generator directory and run the install command.

    cd generator
    poetry install
  11. Generate and configure the Leo config file

    master

    Before running Leo, you must generate a caseconfig.yml file containing the test case definitions.

    1. Navigate to the generator directory and run the generator script for Leo: poetry run python generator.py leo.
    2. Copy the generated configuration to the Leo directory: cp ./output/caseconfig/caseconfig.yml ./leo.
    3. Edit the caseconfig.yml file located in ./leo to configure the following:
      • URL: The target URL.
      • API gateway API key: Your authentication key.
      • Test case management: You can modify, reorder, or remove test cases as needed.
    cd generator && poetry run python generator.py leo
    cp ./output/caseconfig/caseconfig.yml ./leo
    # Then edit ./leo/caseconfig.yml
  12. Implement AWS test cases using `leonidas_aws`

    master

    To implement AWS test cases, use the leonidas_aws executor within your test case definition. The code block must contain Python code that interacts with AWS, typically using boto3 clients.

    Key Components:

    • clients: A dictionary of pre-instantiated boto3 clients. You must list the required services in the clients parameter of your YAML definition. Leonidas handles authentication (roles or access keys) automatically.
    • input_arguments: Arguments defined in the input_arguments block are available as local Python variables within your code block.
    • result: You must assign the outcome of your test to a variable named result. This value is returned in the HTTP response and recorded in the execution logs.

    Important: Do NOT use a return statement in your code, as it interferes with Leonidas' logging and auditing.

    input_arguments:
      secretid:
        description: ID of secret to access, either ARN or friendly name
        type: str
        value: "leonidas_created_secret"
    executors:
      leonidas_aws:
        implemented: True
        clients:
          - secretsmanager
        code: |
          result = clients["secretsmanager"].get_secret_value(SecretId=secretid)