Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ Enhancement suggestions are tracked as [GitHub issues][issues-url].

### Your First Code Contribution

To implement a new experiment have a read through our [Implementing a new Experiment][implementing-a-new-experiment-docs] documentation.

1. Fork the Project
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
Expand All @@ -103,5 +105,6 @@ Enhancement suggestions are tracked as [GitHub issues][issues-url].

Please ensure you have unit tests for key pieces of code.

[implementing-a-new-experiment-docs]: https://github.com/operantai/secops-chaos/blob/main/experiments/README.md#implementing-a-new-experiment
[issues-url]: https://github.com/operantai/secops-chaos/issues
[documentation-url]: https://github.com/operantai/secops-chaos/blob/main/README.md
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ The design of **secops-chaos** can be broken down into two components:
- **Experiments** - Experiments actively try to run something to discover if a security weakness is present.
- **Verifiers** - Verifiers look at the results of an Experiment and reports their outcome.

The secops-chaos CLI mirrors this, and exposes `run` & `verify` commands. To start, you need to run an experiment:
The secops-chaos CLI mirrors this, and exposes `run` & `verify` commands. To start, you need to run an experiment.

Each experiment is defined by a `experiment` file which allows you to tweak your experiment parameters to suit your scenarios.

For a full list of experiments you can run, see the [experiments][experiments-dir-url] directory.

``` sh
secops-chaos run -f experiments/host_path_volume.yaml
Expand All @@ -57,8 +61,6 @@ secops-chaos verify -f experiments/host_path_volume.yaml

You can also output a JSON with the verifier results by using the `-j` flag.

For a full list of experiments you can run, see the [experiments][experiments-dir-url] directory.

## Contributing

Please read the contribution guidelines, [here][contributing-url].
Expand Down
111 changes: 111 additions & 0 deletions experiments/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Experiments

**secops-chaos** experiments are driven by **experiment** files, they all follow a common format, made up of a `metadata` and `parameters` section.

``` yaml
experiments:
- metadata:
name: my-experiment # A meaningful name for your experiment
type: prvileged_container # The type of experiment, see table below for a list of valid types
namespace: my-namespace # What namespace to apply the experiment to
parameters: # Parameters holds the settings for your experiment, tweak them to suit your needs.
host_pid: true
```


## Available Experiments

| Type | Description | Framework |
|-------------------------------------------------------|-------------------------------------------------------------------------------------|-----------|
| [privileged_container](run_privileged_container.yaml) | This experiment attempts to run a privileged container in a namespace | MITRE |
| [host_path_mount](host_path_volume.yaml) | This experiment attempts to mount a sensitive host filesystem path into a container | MITRE |


## Implementing a new Experiment
Comment thread
glenn-operant marked this conversation as resolved.

Each experiment within `secops-chaos` adheres to a shared interface, this allows for a common set of functionality to be used across all experiments.

When implementing a new experiment you should create a new file starting with `experiment_` within the [internal/experiments](https://github.com/OperantAI/secops-chaos/blob/main/internal/experiments/) directory, and implement the `Experiment` interface.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could also add an example of the config model saying that every experiment would need to define its own config model like the PrivilegedContainerConfig and add in an example of one of the experiments

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should now be added, have a read through when you have a sec.


```go
type Experiment interface {
// Type returns the type of the experiment
Type() string
// Description describes the experiment in a brief sentence
Description() string
// Framework returns the attack framework e.g., MITRE/OWASP
Framework() string
// Tactic returns the attack tactic category
Tactic() string
// Technique returns the attack method
Technique() string
// Run runs the experiment, returning an error if it fails
Run(ctx context.Context, client *kubernetes.Clientset, experimentConfig *ExperimentConfig) error
// Verify verifies the experiment, returning an error if it fails
Verify(ctx context.Context, client *kubernetes.Clientset, experimentConfig *ExperimentConfig) (*Outcome, error)
// Cleanup cleans up the experiment, returning an error if it fails
Cleanup(ctx context.Context, client *kubernetes.Clientset, experimentConfig *ExperimentConfig) error
}
```

Additionally, you need some way of configuring your experiment, this is done via the `ExperimentConfig` struct, which is passed to the `Run`, `Verify` and `Cleanup` methods for your experiment.

```go
type ExperimentsConfig struct {
ExperimentConfigs []ExperimentConfig `yaml:"experiments"`
}

type ExperimentConfig struct {
// Metadata for the experiment
Metadata ExperimentMetadata `yaml:"metadata"`
// Parameters for the experiment
Parameters interface{} `yaml:"parameters"`
}

type ExperimentMetadata struct {
// Name of the experiment
Name string `yaml:"name"`
// Namespace to apply the experiment to
Namespace string `yaml:"namespace"`
// Type of the experiment
Type string `yaml:"type"`
}
```

Inside your experiment file, you also need to add Parameter configuration as parameters will differ per experiment. These params get parsed into the `Parameters` field of the `ExperimentConfig` struct.

``` go
type HostPathMountExperimentConfig struct {
Metadata ExperimentMetadata `yaml:"metadata"`
Parameters HostPathMount `yaml:"parameters"`
}

type HostPathMount struct {
HostPath HostPath `yaml:"host_path"`
}

type HostPath struct {
Path string `yaml:"path"`
}

func (p *HostPathMountExperimentConfig) Run(ctx context.Context, client *kubernetes.Clientset, experimentConfig *ExperimentConfig) error {
var hostPathMountExperimentConfig HostPathMountExperimentConfig
yamlObj, _ := yaml.Marshal(experimentConfig)
err := yaml.Unmarshal(yamlObj, &hostPathMountExperimentConfig)
if err != nil {
return err
}
params := hostPathMountExperimentConfig.Parameters
...rest of your experiment implementation...
}
```

`Verify`, and `Clean` also follow the same pattern, and are passed the same `ExperimentConfig` struct.

Finally, add your Experiment to the Experiment registry in [internal/experiments/registry.go](https://github.com/operantai/secops-chaos/blob/main/internal/experiments/registry.go), and create a new experiment file in the [experiments](https://github.com/operantai/secops-chaos/blob/main/experiments) directory.

Now you're set to cause some chaos! 🎉

``` go
secops-chaos run -f experiments/my-experiment.yaml
```
8 changes: 1 addition & 7 deletions internal/experiments/experiments.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@ import (
"k8s.io/client-go/kubernetes"
)

// Experiments is a list of all experiments
var Experiments = []Experiment{
&PrivilegedContainerExperimentConfig{},
&HostPathMountExperimentConfig{},
}

// Experiment is the interface for an experiment
type Experiment interface {
// Type returns the type of the experiment
Expand Down Expand Up @@ -58,7 +52,7 @@ func NewRunner(ctx context.Context, experimentFiles []string) *Runner {
experimentConfigMap := make(map[string]*ExperimentConfig)

// Create a map of experiment types to experiments
for _, e := range Experiments {
for _, e := range ExperimentsRegistry {
experimentMap[e.Type()] = e
}

Expand Down
16 changes: 10 additions & 6 deletions internal/experiments/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,20 @@ import (
"gopkg.in/yaml.v3"
)

// ExperimentsConfig is a structure which represents the configuration for a set of experiments
type ExperimentsConfig struct {
ExperimentConfigs []ExperimentConfig `yaml:"experiments"`
}

// ExperimentConfig is a structure which represents the configuration for an experiment
type ExperimentConfig struct {
// Metadata for the experiment
Metadata ExperimentMetadata `yaml:"metadata"`
// Parameters for the experiment
Parameters interface{} `yaml:"parameters"`
}

// ExperimentMetadata is a structure which represents the metadata required for an experiment
type ExperimentMetadata struct {
// Name of the experiment
Name string `yaml:"name"`
Expand All @@ -23,12 +33,6 @@ type ExperimentMetadata struct {
Type string `yaml:"type"`
}

type ExperimentConfig struct {
Metadata ExperimentMetadata `yaml:"metadata"`
// Parameters for the experiment
Parameters interface{} `yaml:"parameters"`
}

// parseExperimentConfig parses a YAML file and returns a slice of ExperimentConfig
func parseExperimentConfigs(file string) ([]ExperimentConfig, error) {
// Read the file and then unmarshal it into a slice of ExperimentConfig
Expand Down
7 changes: 7 additions & 0 deletions internal/experiments/registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package experiments

// ExperimentsRegistry is a list of all experiments
var ExperimentsRegistry = []Experiment{
&PrivilegedContainerExperimentConfig{},
&HostPathMountExperimentConfig{},
}