guardicore / monkey

Infection Monkey - An open-source adversary emulation platform
https://www.guardicore.com/infectionmonkey/
GNU General Public License v3.0
6.63k stars 775 forks source link

Mutually exclusive plugin option groups #3870

Closed mssalvatore closed 10 months ago

mssalvatore commented 10 months ago

Spike

Objective

Allow mutually exclusive groups of options to be configured for plugins

Background

Certain plugins may want to allow users to configure mutually exclusive groups of options. For example, a data exfiltration payload may allow the user to exfiltrate data to an S3 bucket, tunneled over DNS, via SCP, or via any number of other mechanisms. Each of these mechanisms will have a different set of options to configure it. Ideally, when configuring the plugin, the user can select one, and only one, of these options and then configure it as appropriate.

Output

A plan/issue for any necessary modifications that need to be made, or documentation that explains how to accomplish this goal in the current system.

Starting point

The oneOf JSON schema keyword can be used to accomplish this in the current system with one problem: It appears as though the UI autofills fields, resulting in multiple options being configured. See the below configuration and GIF for more details:

{
    "title": "Mock1 exploiter",
    "description": "Configuration settings for Mock1 exploiter.",
    "type": "object",
    "required": [
        "exploitation_success_rate",
        "propagation_success_rate"
    ],
    "properties": {
        "exploitation_success_rate": {
            "title": "Exploitation success rate",
            "description": "The rate of successful exploitation in percentage",
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "default": 50
        },
        "propagation_success_rate": {
            "title": "Propagation success rate",
            "description": "The rate of successful propagation in percentage",
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "default": 50
        },
        "exfil": {
            "description": "Exfil method",
            "type": "object",
            "oneOf": [
                {
                    "description": "Option1",
                    "type": "object",
                    "properties": {
                        "prop_1_1": {
                            "type": "string"
                        },
                        "prop_1_2": {
                            "type": "string"
                        },
                        "prop_1_3": {
                            "type": "integer"
                        }
                    }
                },
                {
                    "description": "Option2",
                    "type": "object",
                    "properties": {
                        "prop_2_1": {
                            "type": "string"
                        },
                        "prop_2_2": {
                            "type": "string"
                        },
                        "prop_2_3": {
                            "type": "integer"
                        }
                    }
                }
            ]
        }
    }
}

exfil_config

ilija-lazoroski commented 10 months ago

Probably because oneOf needs properties first

First example same as above:

image

Then rsjf playground example:

{
  "type": "object",
  "oneOf": [
    {
      "properties": {
        "lorem": {
          "type": "string"
        }
      },
      "required": [
        "lorem"
      ]
    },
    {
      "properties": {
        "ipsum": {
          "type": "string"
        }
      },
      "required": [
        "ipsum"
      ]
    }
  ]
}
image
ilija-lazoroski commented 10 months ago

By rjsf documentation is exactly what I thought. Built a mock plugin with the above example and. everything seems fine. The configuration:

image

Mock plugin config schema: config-schema.json

Note: Filling the values from one and changing the option DOESN'T keep the state of the values. That makes sense because it is oneOf which means we can choose only one option out of many.

PoC:

https://github.com/guardicore/monkey/assets/15820737/6352320b-1a35-4b1f-bed4-a3f39ed8feab

mssalvatore commented 10 months ago

Awesome!