Skip to content

Commit

Permalink
Merge pull request #16 from DataDog/aws-cli-detonator
Browse files Browse the repository at this point in the history
Introduce AWS CLI detonator
  • Loading branch information
christophetd authored May 15, 2023
2 parents 7b298d6 + 56464cb commit fb37a9f
Show file tree
Hide file tree
Showing 11 changed files with 190 additions and 68 deletions.
45 changes: 41 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ Supported detonators:
* Local command execution
* SSH command execution
* Stratus Red Team
* AWS detonator
* AWS CLI detonator
* AWS detonator (programmatic only, does not work with the CLI)

### Alert matchers

Expand Down Expand Up @@ -69,11 +70,13 @@ $ export THREATEST_SSH_USERNAME=vagrant
$ threatest run scenarios.threatest.yaml
```

Sample scenario definition file:
**Sample scenario definition files**

* Detonating over SSH

```yaml
scenarios:
# Example 1: Remote detonation over SSH
# Remote detonation over SSH
# Note: SSH configuration is provided using the --ssh-host, --ssh-username and --ssh-keyfile CLI arguments
- name: curl metadata service
detonate:
Expand All @@ -84,8 +87,13 @@ scenarios:
datadogSecuritySignal:
name: "Network utility accessed cloud metadata service"
severity: medium
```
* Detonating using Stratus Red Team
# Example 2: Stratus Red Team detonation
```yaml
scenarios:
# Stratus Red Team detonation
# Note: You must be authenticated to the relevant cloud provider before running it
# The example below is equivalent to manually running "stratus detonate aws.exfiltration.ec2-security-group-open-port-22-ingress"
- name: opening a security group to the Internet
Expand All @@ -99,6 +107,35 @@ scenarios:
```
* Detonating using AWS CLI commands
```yaml
scenarios:
# AWS CLI detonation
# Note: You must be authenticated to AWS before running it and have the AWS CLI installed
- name: opening a security group to the Internet
detonate:
awsCliDetonator:
script: |
set -e
# Setup
vpc=$(aws ec2 create-vpc --cidr-block 10.0.0.0/16 --query Vpc.VpcId --output text)
sg=$(aws ec2 create-security-group --group-name sample-sg --description "Test security group" --vpc-id $vpc --query GroupId --output text)
# Open security group
aws ec2 authorize-security-group-ingress --group-id $sg --protocol tcp --port 22 --cidr 0.0.0.0/0
# Cleanup
aws ec2 delete-security-group --group-id $sg
aws ec2 delete-vpc --vpc-id $vpc
expectations:
- timeout: 15m
datadogSecuritySignal:
name: "Potential administrative port open to the world via AWS security group"
```
You can output the test results to a JSON file:
```
Expand Down
4 changes: 4 additions & 0 deletions cmd/threatest/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"os"
)
Expand All @@ -15,6 +16,9 @@ func init() {
}

func main() {
if os.Getenv("THREATEST_DEBUG") == "1" {
log.SetLevel(log.DebugLevel)
}
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
Expand Down
14 changes: 7 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ require (
github.com/hashicorp/go-uuid v1.0.3
github.com/kevinburke/ssh_config v1.2.0
github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.6.1
github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.8.1
golang.org/x/crypto v0.3.0
golang.org/x/crypto v0.7.0
gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61
sigs.k8s.io/yaml v1.3.0
)
Expand Down Expand Up @@ -74,7 +74,7 @@ require (
github.com/hashicorp/terraform-exec v0.17.3 // indirect
github.com/hashicorp/terraform-json v0.14.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
Expand All @@ -91,11 +91,11 @@ require (
github.com/stretchr/objx v0.5.0 // indirect
github.com/zclconf/go-cty v1.12.1 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/oauth2 v0.2.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/time v0.2.0 // indirect
google.golang.org/api v0.103.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
Expand Down
28 changes: 14 additions & 14 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,8 @@ github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZb
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
Expand Down Expand Up @@ -288,8 +288,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
Expand Down Expand Up @@ -332,8 +332,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
Expand All @@ -358,8 +358,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.2.0 h1:GtQkldQ9m7yvzCL1V+LrYow3Khe0eJH0w7RbX/VbaIU=
Expand Down Expand Up @@ -389,18 +389,18 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE=
golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down
48 changes: 48 additions & 0 deletions pkg/threatest/detonators/aws_cli_detonator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package detonators

import (
"context"
"fmt"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/google/uuid"
"os"
"os/exec"
)

/*
AWSCLIDetonator allows to execute arbitrary AWS CLI commands, pre-configured to inject the detonation UUID
in the user-agent.
*/
type AWSCLIDetonator struct {
Script string
}

func NewAWSCLIDetonator(script string) *AWSCLIDetonator {
return &AWSCLIDetonator{Script: script}
}

func (m *AWSCLIDetonator) Detonate() (string, error) {
detonationUuid := uuid.New()

// Sanity check: are we authenticated to AWS?
awsConfig, err := config.LoadDefaultConfig(context.Background())
if err != nil {
return "", fmt.Errorf("unable to load AWS configuration: %v", err)
}
_, err = awsConfig.Credentials.Retrieve(context.Background())
if err != nil {
return "", fmt.Errorf("you are not authenticated to AWS")
}

cmd := exec.Command("bash", "-c", m.Script)
cmd.Env = os.Environ() // inherit environment
cmd.Env = append(cmd.Env, "AWS_EXECUTION_ENV=threatest_"+detonationUuid.String())
output, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("AWS CLI script failed. Output shown below:\n%s", output)
}

fmt.Println("Execution ID: " + detonationUuid.String())

return detonationUuid.String(), nil
}
4 changes: 2 additions & 2 deletions pkg/threatest/detonators/ssh_command_detonator.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
"github.com/hashicorp/go-uuid"
"github.com/kevinburke/ssh_config"
"golang.org/x/crypto/ssh"
"io/ioutil"
"net"
"os"
"os/user"
"path/filepath"
"strconv"
Expand Down Expand Up @@ -56,7 +56,7 @@ func (m *SSHCommandExecutor) init() error {
return fmt.Errorf("unable to resolve path of private key at %s: %v", sshKey, err)
}

pemBytes, err := ioutil.ReadFile(sshKey)
pemBytes, err := os.ReadFile(sshKey)
if err != nil {
return fmt.Errorf("unable to read private key file at %s: %v", sshKey, err)
}
Expand Down
7 changes: 6 additions & 1 deletion pkg/threatest/parser/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ func buildScenarios(parsed *ThreatestSchemaJson, sshHostname string, sshUsername
scenario.Detonator = detonators.NewCommandDetonator(sshExecutor, commandToRun)
} else if stratusRedTeamDetonator := parsedScenario.Detonate.StratusRedTeamDetonator; stratusRedTeamDetonator != nil {
scenario.Detonator = detonators.StratusRedTeamTechnique(*stratusRedTeamDetonator.AttackTechnique)
} else if awsCliDetonator := parsedScenario.Detonate.AwsCliDetonator; awsCliDetonator != nil {
scenario.Detonator = detonators.NewAWSCLIDetonator(*awsCliDetonator.Script)
}

// Assertions
Expand Down Expand Up @@ -88,5 +90,8 @@ func buildScenarios(parsed *ThreatestSchemaJson, sshHostname string, sshUsername
// hasDetonation returns true if the scenario has at least 1 detonation defined
func hasDetonation(scenario ThreatestSchemaJsonScenariosElem) bool {
detonations := scenario.Detonate
return detonations.LocalDetonator != nil || detonations.RemoteDetonator != nil || detonations.StratusRedTeamDetonator != nil
return detonations.LocalDetonator != nil ||
detonations.RemoteDetonator != nil ||
detonations.StratusRedTeamDetonator != nil ||
detonations.AwsCliDetonator != nil
}
Loading

0 comments on commit fb37a9f

Please sign in to comment.