├── logo.png ├── shell.png ├── stratus-red-team.png ├── main.go ├── NOTICE ├── pkg └── grimoire │ ├── utils │ ├── main.go │ ├── aws_test.go │ ├── aws.go │ ├── string.go │ ├── string_test.go │ └── file.go │ ├── logs │ ├── logs.go │ └── cloudtrail.go │ └── detonators │ ├── detonator.go │ ├── stratus-red-team.go │ ├── stratus-red-team_test.go │ └── mocks │ └── StratusRedTeamRunner.go ├── .gitignore ├── .github └── workflows │ ├── test.yml │ ├── static-analysis.yml │ └── release.yml ├── Makefile ├── .goreleaser.yaml ├── cmd └── grimoire │ ├── main.go │ ├── lookup_flags.go │ ├── shell.go │ └── stratus-red-team.go ├── Formula └── grimoire.rb ├── go.mod ├── README.md ├── LICENSE ├── LICENSE-3rdparty.csv └── go.sum /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/grimoire/HEAD/logo.png -------------------------------------------------------------------------------- /shell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/grimoire/HEAD/shell.png -------------------------------------------------------------------------------- /stratus-red-team.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/grimoire/HEAD/stratus-red-team.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "github.com/datadog/stratus-red-team/v2/pkg/stratus/loader" // Note: This import is needed 5 | ) 6 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Grimoire 2 | Copyright 2024-Present Datadog, Inc. 3 | 4 | This product includes software developed at Datadog ( 5 | -------------------------------------------------------------------------------- /pkg/grimoire/utils/main.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/google/uuid" 5 | ) 6 | 7 | func NewDetonationID() string { 8 | return uuid.New().String() 9 | } 10 | -------------------------------------------------------------------------------- /pkg/grimoire/logs/logs.go: -------------------------------------------------------------------------------- 1 | package logs 2 | 3 | import ( 4 | "github.com/datadog/grimoire/pkg/grimoire/detonators" 5 | ) 6 | 7 | type Searcher interface { 8 | FindLogs(info *detonators.DetonationInfo) ([]map[string]interface{}, error) 9 | } 10 | -------------------------------------------------------------------------------- /pkg/grimoire/detonators/detonator.go: -------------------------------------------------------------------------------- 1 | package detonators 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type DetonationInfo struct { 8 | DetonationID string 9 | StartTime time.Time 10 | EndTime time.Time 11 | } 12 | 13 | type Detonator interface { 14 | String() string 15 | Detonate() (*DetonationInfo, error) 16 | CleanUp() error 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | .idea 17 | bin/ 18 | -------------------------------------------------------------------------------- /pkg/grimoire/utils/aws_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestGetCloudTrailEventFullName(t *testing.T) { 9 | event := map[string]interface{}{ 10 | "eventName": "SendCommand", 11 | "eventSource": "ssm.amazonaws.com", 12 | } 13 | result := GetCloudTrailEventFullName(&event) 14 | assert.Equal(t, "ssm:SendCommand", result) 15 | } 16 | -------------------------------------------------------------------------------- /pkg/grimoire/utils/aws.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // GetCloudTrailEventFullName returns the full name of a CloudTrail event, e.g. sts:GetCallerIdentity 9 | func GetCloudTrailEventFullName(event *map[string]interface{}) string { 10 | eventName := (*event)["eventName"].(string) 11 | eventSourceShort := strings.TrimSuffix((*event)["eventSource"].(string), ".amazonaws.com") 12 | return fmt.Sprintf("%s:%s", eventSourceShort, eventName) // e.g. "sts:GetCallerIdentity" 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: "Test" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | static-analysis: 16 | name: "Run Go static analysis" 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 1 22 | 23 | - name: Set up Go 24 | uses: actions/setup-go@v4 25 | with: 26 | go-version: 1.22 27 | 28 | - name: Unit tests 29 | run: make test -------------------------------------------------------------------------------- /.github/workflows/static-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "go static analysis" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | static-analysis: 16 | name: "Run Go static analysis" 17 | runs-on: ubuntu-latest 18 | steps: 19 | 20 | - uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 1 23 | 24 | - name: Set up Go 25 | uses: actions/setup-go@v4 26 | with: 27 | go-version: 1.22 28 | 29 | - uses: dominikh/staticcheck-action@v1 30 | with: 31 | version: "latest" 32 | install-go: false -------------------------------------------------------------------------------- /pkg/grimoire/utils/string.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | // StringMatches returns true if the candidate string matches a pattern. 9 | // Supports wildcards. Case-insensitive 10 | func StringMatches(candidate string, pattern string) bool { 11 | var regex strings.Builder 12 | regex.WriteString("(?i)") // case-insensitive 13 | for i, literal := range strings.Split(pattern, "*") { 14 | 15 | // Replace * with .* 16 | if i > 0 { 17 | regex.WriteString(".*") 18 | } 19 | 20 | // Quote any regular expression meta characters in the 21 | // literal text. 22 | regex.WriteString(regexp.QuoteMeta(literal)) 23 | } 24 | 25 | matches, _ := regexp.MatchString(regex.String(), candidate) 26 | return matches 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | permissions: 9 | contents: write 10 | pull-requests: write 11 | 12 | jobs: 13 | goreleaser: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Set up Go 22 | uses: actions/setup-go@v4 23 | with: 24 | go-version: 1.22 25 | 26 | - name: Run GoReleaser 27 | timeout-minutes: 60 28 | uses: goreleaser/goreleaser-action@v6 29 | with: 30 | distribution: goreleaser 31 | args: release --clean --config .goreleaser.yaml 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /pkg/grimoire/utils/string_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestStringMatches(t *testing.T) { 9 | assert.True(t, StringMatches("sts:GetCallerIdentity", "sts:GetCallerIdentity")) 10 | assert.True(t, StringMatches("sts:GetCallerIdentity", "sts:getcalleridentity")) 11 | assert.True(t, StringMatches("sts:GetCallerIdentity", "sts:*")) 12 | assert.True(t, StringMatches("sts:GetCallerIdentity", "*Identity")) 13 | assert.True(t, StringMatches("sts:GetCallerIdentity", "sts:Get*Identity")) 14 | 15 | assert.True(t, StringMatches("sts:GetCallerIdentity", "*")) 16 | assert.True(t, StringMatches("sts:GetCallerIdentity", "*:*")) 17 | 18 | assert.False(t, StringMatches("sts:GetCallerIdentity", "iam:*")) 19 | assert.False(t, StringMatches("sts:GetCallerIdentity", "*User")) 20 | } 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MAKEFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) 2 | ROOT_DIR := $(dir $(MAKEFILE_PATH)) 3 | BIN_DIR := $(ROOT_DIR)/bin 4 | 5 | build: 6 | @echo "Building Grimoire..." 7 | @mkdir -p "$(BIN_DIR)" 8 | @go build -o $(BIN_DIR)/grimoire cmd/grimoire/*.go 9 | @echo "Build completed. Binaries are saved in $(BIN_DIR)" 10 | 11 | test: 12 | @echo "Running unit tests..." 13 | @go test ./... -v 14 | 15 | thirdparty-licenses: 16 | @echo "Retrieving third-party licenses..." 17 | @go install github.com/google/go-licenses@latest 18 | @$(GOPATH)/bin/go-licenses csv github.com/datadog/grimoire/cmd/grimoire | sort > $(ROOT_DIR)/LICENSE-3rdparty.csv 19 | @echo "Third-party licenses retrieved and saved to $(ROOT_DIR)/LICENSE-3rdparty.csv" 20 | 21 | mocks: 22 | mocks: 23 | @echo "Generating mocks..." 24 | @mockery --dir $(GOPATH)/src/github.com/DataDog/stratus-red-team/v2/pkg/stratus/runner --name Runner --output=pkg/grimoire/detonators/mocks --structname StratusRedTeamRunner --filename StratusRedTeamRunner.go 25 | @echo "Mocks generated successfully." -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | before: 3 | hooks: 4 | - go mod tidy 5 | builds: 6 | - env: 7 | - CGO_ENABLED=0 8 | goos: 9 | - linux 10 | - darwin 11 | dir: ./cmd/grimoire 12 | binary: grimoire 13 | brews: 14 | - name: grimoire 15 | repository: 16 | owner: datadog 17 | name: grimoire 18 | branch: "homebrew-update-{{ .Version }}" 19 | pull_request: 20 | enabled: true 21 | base: 22 | owner: datadog 23 | name: grimoire 24 | branch: main 25 | directory: Formula 26 | url_template: "https://github.com/DataDog/grimoire/releases/download/{{ .Tag }}/{{ .ArtifactName }}" 27 | license: Apache-2.0 28 | homepage: "https://github.com/DataDog/grimoire" 29 | archives: 30 | - name_template: >- 31 | {{ .ProjectName }}_ 32 | {{- title .Os }}_ 33 | {{- if eq .Arch "amd64" }}x86_64 34 | {{- else if eq .Arch "386" }}i386 35 | {{- else }}{{ .Arch }}{{ end }} 36 | checksum: 37 | name_template: 'checksums.txt' 38 | snapshot: 39 | name_template: "{{ incpatch .Version }}-next" 40 | changelog: 41 | sort: asc 42 | filters: 43 | exclude: 44 | - '^docs:' 45 | - '^test:' 46 | -------------------------------------------------------------------------------- /cmd/grimoire/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/common-nighthawk/go-figure" 6 | log "github.com/sirupsen/logrus" 7 | "github.com/spf13/cobra" 8 | "os" 9 | ) 10 | 11 | var enableVerboseLogging = false 12 | var disableBanner = false 13 | 14 | var rootCmd = &cobra.Command{ 15 | Use: "grimoire", 16 | PersistentPreRun: func(cmd *cobra.Command, args []string) { 17 | setupLogging() 18 | if !disableBanner { 19 | figure.NewColorFigure("Grimoire", "", "purple", true).Print() 20 | fmt.Println() 21 | } 22 | }, 23 | } 24 | 25 | func setupLogging() { 26 | if enableVerboseLogging { 27 | log.SetLevel(log.DebugLevel) 28 | } 29 | customFormatter := new(log.TextFormatter) 30 | customFormatter.TimestampFormat = "2006-01-02 15:04:05" 31 | customFormatter.FullTimestamp = true 32 | log.SetFormatter(customFormatter) 33 | } 34 | 35 | func init() { 36 | rootCmd.PersistentFlags().BoolVarP(&enableVerboseLogging, "debug", "v", false, "Enable debug logging") 37 | rootCmd.PersistentFlags().BoolVarP(&enableVerboseLogging, "disable-banner", "", false, "Disable Grimoire ASCII-art banner") 38 | 39 | rootCmd.AddCommand(NewStratusRedTeamCommand()) 40 | rootCmd.AddCommand(NewShellCommand()) 41 | // Disable creation of the "completion" command 42 | rootCmd.CompletionOptions.DisableDefaultCmd = true 43 | } 44 | 45 | func main() { 46 | if err := rootCmd.Execute(); err != nil { 47 | os.Exit(1) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /cmd/grimoire/lookup_flags.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | "time" 6 | ) 7 | 8 | var timeout time.Duration 9 | var lookupInterval time.Duration 10 | var maxEvents int 11 | var includeEvents []string 12 | var excludeEvents []string 13 | var writeEventsOnly bool 14 | var extendSearchWindow time.Duration 15 | 16 | // CLI flags shared between 'shell' and 'stratus-red-team' commands 17 | func initLookupFlags(cmd *cobra.Command) { 18 | cmd.Flags().DurationVarP(&timeout, "timeout", "", 15*time.Minute, "Maximum time to wait for events to appear in CloudTrail logs") 19 | cmd.Flags().DurationVarP(&lookupInterval, "interval", "", 5*time.Second, "Interval to check for new events. You might need to increase this if you're hitting AWS API rate limits") 20 | cmd.Flags().DurationVarP(&extendSearchWindow, "extend-search-window", "", 0, "Extend the end of the search window by this amount of time") 21 | cmd.Flags().IntVarP(&maxEvents, "max-events", "", 0, "Maximum number of events to retrieve before exiting") 22 | cmd.Flags().BoolVarP(&writeEventsOnly, "only-write-events", "", false, "Only consider write events and ignore read-only ones") 23 | 24 | cmd.Flags().StringSliceVarP(&includeEvents, "include-events", "", []string{}, "Only consider specific CloudTrail events. Comma-separated and using the format 'service:Operation' (e.g. 's3:PutObject')") 25 | cmd.Flags().StringSliceVarP(&excludeEvents, "exclude-events", "", []string{}, "Exclude specific CloudTrail events. Comma-separated and using the format 'service:Operation' (e.g. 's3:PutObject')") 26 | } 27 | -------------------------------------------------------------------------------- /Formula/grimoire.rb: -------------------------------------------------------------------------------- 1 | # typed: false 2 | # frozen_string_literal: true 3 | 4 | # This file was generated by GoReleaser. DO NOT EDIT. 5 | class Grimoire < Formula 6 | desc "" 7 | homepage "https://github.com/DataDog/grimoire" 8 | version "0.1.0" 9 | license "Apache-2.0" 10 | 11 | on_macos do 12 | on_intel do 13 | url "https://github.com/DataDog/grimoire/releases/download/v0.1.0/grimoire_Darwin_x86_64.tar.gz" 14 | sha256 "ad353966a5b52dbb7777314e2115f39256e299bd9ceec849685eb830c7981689" 15 | 16 | def install 17 | bin.install "grimoire" 18 | end 19 | end 20 | on_arm do 21 | url "https://github.com/DataDog/grimoire/releases/download/v0.1.0/grimoire_Darwin_arm64.tar.gz" 22 | sha256 "a095df2d86790cf63b9b74e19aa26cbb98b541464419ab973ef719a6ecfd2e9e" 23 | 24 | def install 25 | bin.install "grimoire" 26 | end 27 | end 28 | end 29 | 30 | on_linux do 31 | on_intel do 32 | if Hardware::CPU.is_64_bit? 33 | url "https://github.com/DataDog/grimoire/releases/download/v0.1.0/grimoire_Linux_x86_64.tar.gz" 34 | sha256 "4b75dd11ea9dc1c18596be917b9abf6cb2035319db47546c639827e34fb61392" 35 | 36 | def install 37 | bin.install "grimoire" 38 | end 39 | end 40 | end 41 | on_arm do 42 | if Hardware::CPU.is_64_bit? 43 | url "https://github.com/DataDog/grimoire/releases/download/v0.1.0/grimoire_Linux_arm64.tar.gz" 44 | sha256 "e38ccb465cb1ae0c7ab974cec00fe1c9220e36cda0239d7ea54f4be9ec54dd86" 45 | 46 | def install 47 | bin.install "grimoire" 48 | end 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /pkg/grimoire/utils/file.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | func CreateOrTruncateJSONFile(outputFile string) error { 10 | if outputFile == "" || outputFile == "-" { 11 | return nil // nothing to do 12 | } 13 | return os.WriteFile(outputFile, []byte("[]"), 0600) 14 | } 15 | 16 | // AppendToJsonFileArray assumes that 'outputFile' is an existing JSON file containing an array of JSON objects, and appends 'payload' to it 17 | func AppendToJsonFileArray(outputFile string, payload map[string]interface{}) error { 18 | if outputFile == "" { 19 | return nil // nothing to do 20 | } 21 | 22 | // print to stdout, nothing else to do 23 | if outputFile == "-" { 24 | outputBytes, err := json.MarshalIndent(payload, "", " ") 25 | if err != nil { 26 | return err 27 | } 28 | fmt.Println(string(outputBytes)) 29 | return nil 30 | } 31 | 32 | // Read file contents and parse the JSON 33 | var events []map[string]interface{} 34 | inputBytes, err := os.ReadFile(outputFile) 35 | if err != nil { 36 | return fmt.Errorf("unable to read output file %s: %v", outputFile, err) 37 | } 38 | if err := json.Unmarshal(inputBytes, &events); err != nil { 39 | return fmt.Errorf("unable to unmarshal output file contents %s: %v", outputFile, err) 40 | } 41 | 42 | // Append our payload 43 | events = append(events, payload) 44 | 45 | // Re-convert it back to JSON 46 | outputBytes, err := json.MarshalIndent(events, "", " ") 47 | if err != nil { 48 | return fmt.Errorf("unable to marshal JSON to output file %s: %v", outputFile, err) 49 | } 50 | 51 | // Write back ot the output file 52 | if err := os.WriteFile(outputFile, outputBytes, 0600); err != nil { 53 | return fmt.Errorf("unable to write to output file %s for writing: %v", outputFile, err) 54 | } 55 | 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /pkg/grimoire/detonators/stratus-red-team.go: -------------------------------------------------------------------------------- 1 | package detonators 2 | 3 | import ( 4 | "fmt" 5 | "github.com/datadog/stratus-red-team/v2/pkg/stratus" 6 | _ "github.com/datadog/stratus-red-team/v2/pkg/stratus/loader" 7 | stratusrunner "github.com/datadog/stratus-red-team/v2/pkg/stratus/runner" 8 | log "github.com/sirupsen/logrus" 9 | "time" 10 | ) 11 | 12 | const StratusRedTeamUserAgentPrefix = "stratus-red-team_" 13 | 14 | type StratusRedTeamDetonator struct { 15 | AttackTechnique *stratus.AttackTechnique 16 | StratusRunner stratusrunner.Runner 17 | } 18 | 19 | func NewStratusRedTeamDetonator(attackTechniqueID string) (*StratusRedTeamDetonator, error) { 20 | ttp := stratus.GetRegistry().GetAttackTechniqueByName(attackTechniqueID) 21 | if ttp == nil { 22 | //lint:ignore ST1005 "Stratus Red Team" is a proper noun 23 | return nil, fmt.Errorf("Stratus Red Team attack technique %s not found", attackTechniqueID) 24 | } 25 | return &StratusRedTeamDetonator{ 26 | AttackTechnique: ttp, 27 | StratusRunner: stratusrunner.NewRunner(ttp, stratusrunner.StratusRunnerForce), 28 | }, nil 29 | } 30 | 31 | func (m *StratusRedTeamDetonator) Detonate() (*DetonationInfo, error) { 32 | ttp := m.AttackTechnique 33 | 34 | log.Infof("Warming up Stratus Red Team attack technique %s", ttp) 35 | if _, err := m.StratusRunner.WarmUp(); err != nil { 36 | return nil, fmt.Errorf("error warming up Stratus Red Team attack technique %s: %w", ttp, err) 37 | } 38 | 39 | startTime := time.Now() 40 | log.Infof("Detonating Stratus Red Team attack technique %s", ttp) 41 | if err := m.StratusRunner.Detonate(); err != nil { 42 | return nil, fmt.Errorf("error detonating Stratus Red Team attack technique %s: %w", ttp, err) 43 | } 44 | endTime := time.Now() 45 | 46 | return &DetonationInfo{ 47 | DetonationID: StratusRedTeamUserAgentPrefix + m.StratusRunner.GetUniqueExecutionId(), 48 | StartTime: startTime, 49 | EndTime: endTime, 50 | }, nil 51 | } 52 | 53 | func (m *StratusRedTeamDetonator) CleanUp() error { 54 | return m.StratusRunner.CleanUp() 55 | } 56 | 57 | func (m *StratusRedTeamDetonator) GetAttackTechniqueState() stratus.AttackTechniqueState { 58 | return m.StratusRunner.GetState() 59 | } 60 | 61 | func (m *StratusRedTeamDetonator) String() string { 62 | return fmt.Sprintf("Stratus Red Team attack technique %s", m.AttackTechnique.ID) 63 | } 64 | -------------------------------------------------------------------------------- /pkg/grimoire/detonators/stratus-red-team_test.go: -------------------------------------------------------------------------------- 1 | package detonators 2 | 3 | import ( 4 | "errors" 5 | "github.com/datadog/grimoire/pkg/grimoire/detonators/mocks" 6 | "github.com/datadog/stratus-red-team/v2/pkg/stratus" 7 | "github.com/stretchr/testify/assert" 8 | "testing" 9 | ) 10 | 11 | func TestStratusRedTeamDetonate(t *testing.T) { 12 | 13 | type DetonateTestScenario struct { 14 | Name string 15 | WarmUpErrors bool 16 | DetonateErrors bool 17 | UniqueExecutionID string 18 | CheckExpectations func(t *testing.T, runner *mocks.StratusRedTeamRunner, detonation *DetonationInfo, err error) 19 | } 20 | 21 | var scenario = []DetonateTestScenario{ 22 | { 23 | Name: "No warmup or detonation error", 24 | WarmUpErrors: false, 25 | DetonateErrors: false, 26 | UniqueExecutionID: "unique", 27 | CheckExpectations: func(t *testing.T, runner *mocks.StratusRedTeamRunner, detonation *DetonationInfo, err error) { 28 | runner.AssertCalled(t, "WarmUp") 29 | runner.AssertCalled(t, "Detonate") 30 | assert.Nil(t, err) 31 | assert.Contains(t, detonation.DetonationID, "unique") 32 | }, 33 | }, 34 | { 35 | Name: "Warmup error", 36 | WarmUpErrors: true, 37 | DetonateErrors: false, 38 | UniqueExecutionID: "unique", 39 | CheckExpectations: func(t *testing.T, runner *mocks.StratusRedTeamRunner, detonation *DetonationInfo, err error) { 40 | runner.AssertCalled(t, "WarmUp") 41 | runner.AssertNotCalled(t, "Detonate") 42 | assert.NotNil(t, err) 43 | }, 44 | }, 45 | { 46 | Name: "Detonate error", 47 | WarmUpErrors: false, 48 | DetonateErrors: true, 49 | UniqueExecutionID: "unique", 50 | CheckExpectations: func(t *testing.T, runner *mocks.StratusRedTeamRunner, detonation *DetonationInfo, err error) { 51 | runner.AssertCalled(t, "WarmUp") 52 | runner.AssertCalled(t, "Detonate") 53 | assert.NotNil(t, err) 54 | 55 | // For now, this doesn't result in an automated cleanup 56 | runner.AssertNotCalled(t, "CleanUp") 57 | }, 58 | }, 59 | } 60 | 61 | for i := range scenario { 62 | t.Run(scenario[i].Name, func(t *testing.T) { 63 | runner := new(mocks.StratusRedTeamRunner) 64 | detonator := StratusRedTeamDetonator{ 65 | AttackTechnique: &stratus.AttackTechnique{ID: "foo"}, 66 | StratusRunner: runner, 67 | } 68 | 69 | var warmupErr error 70 | if scenario[i].WarmUpErrors { 71 | warmupErr = errors.New("warmup") 72 | } 73 | runner.On("WarmUp").Return(map[string]string{}, warmupErr) 74 | 75 | var detonateErr error 76 | if scenario[i].DetonateErrors { 77 | detonateErr = errors.New("detonate") 78 | } 79 | runner.On("Detonate").Return(detonateErr) 80 | 81 | runner.On("GetUniqueExecutionId").Return(scenario[i].UniqueExecutionID) 82 | 83 | detonation, err := detonator.Detonate() 84 | scenario[i].CheckExpectations(t, runner, detonation, err) 85 | }) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /pkg/grimoire/detonators/mocks/StratusRedTeamRunner.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.44.1. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import ( 6 | mock "github.com/stretchr/testify/mock" 7 | 8 | stratus "github.com/datadog/stratus-red-team/v2/pkg/stratus" 9 | ) 10 | 11 | // StratusRedTeamRunner is an autogenerated mock type for the Runner type 12 | type StratusRedTeamRunner struct { 13 | mock.Mock 14 | } 15 | 16 | // CleanUp provides a mock function with given fields: 17 | func (_m *StratusRedTeamRunner) CleanUp() error { 18 | ret := _m.Called() 19 | 20 | if len(ret) == 0 { 21 | panic("no return value specified for CleanUp") 22 | } 23 | 24 | var r0 error 25 | if rf, ok := ret.Get(0).(func() error); ok { 26 | r0 = rf() 27 | } else { 28 | r0 = ret.Error(0) 29 | } 30 | 31 | return r0 32 | } 33 | 34 | // Detonate provides a mock function with given fields: 35 | func (_m *StratusRedTeamRunner) Detonate() error { 36 | ret := _m.Called() 37 | 38 | if len(ret) == 0 { 39 | panic("no return value specified for Detonate") 40 | } 41 | 42 | var r0 error 43 | if rf, ok := ret.Get(0).(func() error); ok { 44 | r0 = rf() 45 | } else { 46 | r0 = ret.Error(0) 47 | } 48 | 49 | return r0 50 | } 51 | 52 | // GetState provides a mock function with given fields: 53 | func (_m *StratusRedTeamRunner) GetState() stratus.AttackTechniqueState { 54 | ret := _m.Called() 55 | 56 | if len(ret) == 0 { 57 | panic("no return value specified for GetState") 58 | } 59 | 60 | var r0 stratus.AttackTechniqueState 61 | if rf, ok := ret.Get(0).(func() stratus.AttackTechniqueState); ok { 62 | r0 = rf() 63 | } else { 64 | r0 = ret.Get(0).(stratus.AttackTechniqueState) 65 | } 66 | 67 | return r0 68 | } 69 | 70 | // GetUniqueExecutionId provides a mock function with given fields: 71 | func (_m *StratusRedTeamRunner) GetUniqueExecutionId() string { 72 | ret := _m.Called() 73 | 74 | if len(ret) == 0 { 75 | panic("no return value specified for GetUniqueExecutionId") 76 | } 77 | 78 | var r0 string 79 | if rf, ok := ret.Get(0).(func() string); ok { 80 | r0 = rf() 81 | } else { 82 | r0 = ret.Get(0).(string) 83 | } 84 | 85 | return r0 86 | } 87 | 88 | // Revert provides a mock function with given fields: 89 | func (_m *StratusRedTeamRunner) Revert() error { 90 | ret := _m.Called() 91 | 92 | if len(ret) == 0 { 93 | panic("no return value specified for Revert") 94 | } 95 | 96 | var r0 error 97 | if rf, ok := ret.Get(0).(func() error); ok { 98 | r0 = rf() 99 | } else { 100 | r0 = ret.Error(0) 101 | } 102 | 103 | return r0 104 | } 105 | 106 | // WarmUp provides a mock function with given fields: 107 | func (_m *StratusRedTeamRunner) WarmUp() (map[string]string, error) { 108 | ret := _m.Called() 109 | 110 | if len(ret) == 0 { 111 | panic("no return value specified for WarmUp") 112 | } 113 | 114 | var r0 map[string]string 115 | var r1 error 116 | if rf, ok := ret.Get(0).(func() (map[string]string, error)); ok { 117 | return rf() 118 | } 119 | if rf, ok := ret.Get(0).(func() map[string]string); ok { 120 | r0 = rf() 121 | } else { 122 | if ret.Get(0) != nil { 123 | r0 = ret.Get(0).(map[string]string) 124 | } 125 | } 126 | 127 | if rf, ok := ret.Get(1).(func() error); ok { 128 | r1 = rf() 129 | } else { 130 | r1 = ret.Error(1) 131 | } 132 | 133 | return r0, r1 134 | } 135 | 136 | // NewStratusRedTeamRunner creates a new instance of StratusRedTeamRunner. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 137 | // The first argument is typically a *testing.T value. 138 | func NewStratusRedTeamRunner(t interface { 139 | mock.TestingT 140 | Cleanup(func()) 141 | }) *StratusRedTeamRunner { 142 | mock := &StratusRedTeamRunner{} 143 | mock.Mock.Test(t) 144 | 145 | t.Cleanup(func() { mock.AssertExpectations(t) }) 146 | 147 | return mock 148 | } 149 | -------------------------------------------------------------------------------- /cmd/grimoire/shell.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "github.com/aws/aws-sdk-go-v2/aws" 8 | "github.com/aws/aws-sdk-go-v2/config" 9 | "github.com/aws/aws-sdk-go-v2/service/cloudtrail" 10 | "github.com/aws/aws-sdk-go-v2/service/sts" 11 | "github.com/datadog/grimoire/pkg/grimoire/detonators" 12 | "github.com/datadog/grimoire/pkg/grimoire/logs" 13 | utils "github.com/datadog/grimoire/pkg/grimoire/utils" 14 | log "github.com/sirupsen/logrus" 15 | "github.com/spf13/cobra" 16 | "os" 17 | "os/exec" 18 | "os/signal" 19 | "syscall" 20 | "time" 21 | ) 22 | 23 | type ShellCommand struct { 24 | OutputFile string 25 | CommandToRun string 26 | ScriptToRun string 27 | } 28 | 29 | func NewShellCommand() *cobra.Command { 30 | var outputFile string 31 | var commandToRun string 32 | var scriptToRun string 33 | 34 | shellCmd := &cobra.Command{ 35 | Use: "shell", 36 | SilenceUsage: true, 37 | Example: "Run an interactive shell. Grimoire will inject a unique identifier to your HTTP user agent when using the AWS CLI.", 38 | RunE: func(cmd *cobra.Command, args []string) error { 39 | command := ShellCommand{ 40 | OutputFile: outputFile, 41 | CommandToRun: commandToRun, 42 | ScriptToRun: scriptToRun, 43 | } 44 | if err := command.Validate(); err != nil { 45 | return err 46 | } 47 | return command.Do() 48 | }, 49 | } 50 | 51 | initLookupFlags(shellCmd) 52 | shellCmd.Flags().StringVarP(&outputFile, "output", "o", "", "Output file to write CloudTrail events to. Grimoire will overwrite the file if it exists, and create otherwise.") 53 | shellCmd.Flags().StringVarP(&commandToRun, "command", "c", "", "Command to execute in the shell (instead of running an interactive shell)") 54 | shellCmd.Flags().StringVarP(&scriptToRun, "script", "", "", "Path to a script to execute in the shell (instead of running an interactive shell)") 55 | 56 | return shellCmd 57 | } 58 | 59 | func (m *ShellCommand) Validate() error { 60 | if m.CommandToRun != "" && m.ScriptToRun != "" { 61 | return fmt.Errorf("only one of 'command' or 'script' can be specified") 62 | } 63 | return nil 64 | } 65 | func (m *ShellCommand) Do() error { 66 | ctx, cancel := context.WithCancel(context.Background()) 67 | sigChan := make(chan os.Signal, 1) 68 | signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) 69 | go func() { 70 | select { 71 | case <-sigChan: 72 | cancel() 73 | close(sigChan) 74 | log.Info("Exiting Grimoire.") 75 | os.Exit(0) 76 | case <-ctx.Done(): 77 | } 78 | }() 79 | 80 | if err := utils.CreateOrTruncateJSONFile(m.OutputFile); err != nil { 81 | return err 82 | } 83 | 84 | detonationUuid := utils.NewDetonationID() 85 | awsConfig, _ := config.LoadDefaultConfig(context.Background()) 86 | 87 | // Ensure that the user is already authenticated to AWS 88 | m.ensureAuthenticatedToAws(awsConfig) 89 | 90 | if m.isInteractiveMode() { 91 | log.Info("Grimoire will now run your shell and automatically inject a unique identifier to your HTTP user agent when using the AWS CLI") 92 | log.Info("You can use the AWS CLI as usual. Press Ctrl+D or type 'exit' to return to Grimoire.") 93 | log.Info("When you exit the shell, Grimoire will look for the CloudTrail events that your commands have generated.") 94 | log.Info("Press ENTER to continue") 95 | if _, err := fmt.Scanln(); err != nil { 96 | return err 97 | } 98 | } else if m.CommandToRun != "" { 99 | log.Infof("Running detonation command: %s", m.CommandToRun) 100 | } else if m.ScriptToRun != "" { 101 | log.Infof("Running detonation script: %s", m.ScriptToRun) 102 | } 103 | 104 | startTime := time.Now() 105 | grimoireUserAgent := fmt.Sprintf("grimoire_%s", detonationUuid) 106 | commandToRun, args := m.getCommandToRun() 107 | log.Debugf("Running command: %s %v", commandToRun, args) 108 | cmd := exec.CommandContext(ctx, commandToRun, args...) 109 | cmd.Stdin = os.Stdin 110 | cmd.Stdout = os.Stdout 111 | cmd.Stderr = os.Stderr 112 | cmd.Env = append( 113 | os.Environ(), 114 | fmt.Sprintf("AWS_EXECUTION_ENV=%s", grimoireUserAgent), 115 | fmt.Sprintf("GRIMOIRE_DETONATION_ID=%s", detonationUuid), // generic environment variable to allow the user to pass it further if needed 116 | ) 117 | if err := cmd.Run(); err != nil && m.isExecutionError(err) { 118 | return fmt.Errorf("unable to run shell: %v", err) 119 | } 120 | endTime := time.Now() 121 | 122 | if m.isInteractiveMode() { 123 | log.Infof("Welcome back to Grimoire!") 124 | } 125 | 126 | cloudtrailLogs := &logs.CloudTrailEventsFinder{ 127 | CloudtrailClient: cloudtrail.NewFromConfig(awsConfig), 128 | Options: &logs.CloudTrailEventLookupOptions{ 129 | Timeout: timeout, 130 | LookupInterval: lookupInterval, 131 | IncludeEvents: includeEvents, 132 | ExcludeEvents: excludeEvents, 133 | MaxEvents: maxEvents, 134 | WriteEventsOnly: writeEventsOnly, 135 | ExtendTimeWindow: extendSearchWindow, 136 | UserAgentMatchType: logs.UserAgentMatchTypePartial, 137 | }, 138 | } 139 | 140 | detonationInfo := &detonators.DetonationInfo{ 141 | DetonationID: detonationUuid, 142 | StartTime: startTime, 143 | EndTime: endTime, 144 | } 145 | 146 | log.Info("Searching for CloudTrail events...") 147 | eventsChannel, err := cloudtrailLogs.FindLogs(context.Background(), detonationInfo) 148 | if err != nil { 149 | return fmt.Errorf("unable to search for CloudTrail events: %v", err) 150 | } 151 | 152 | for evt := range eventsChannel { 153 | if evt.Error != nil { 154 | log.Errorf("Error while searching for CloudTrail events: %v", evt.Error) 155 | os.Exit(1) 156 | } 157 | 158 | log.Infof("Found event: %s", utils.GetCloudTrailEventFullName(evt.CloudTrailEvent)) 159 | if err := utils.AppendToJsonFileArray(m.OutputFile, *evt.CloudTrailEvent); err != nil { 160 | log.Errorf("unable to append CloudTrail event to output file: %v", err) 161 | } 162 | } 163 | 164 | return nil 165 | } 166 | 167 | func (m *ShellCommand) ensureAuthenticatedToAws(awsConfig aws.Config) { 168 | log.Debug("Checking AWS authentication using sts:GetCallerIdentity") 169 | stsClient := sts.NewFromConfig(awsConfig) 170 | _, err := stsClient.GetCallerIdentity(context.Background(), &sts.GetCallerIdentityInput{}) 171 | if err != nil { 172 | log.Errorf("It looks like you are not authenticated to AWS. Please authenticate before running Grimoire.") 173 | os.Exit(1) 174 | } 175 | } 176 | 177 | func (m *ShellCommand) isExecutionError(err error) bool { 178 | var exitError *exec.ExitError 179 | if errors.As(err, &exitError) { 180 | if _, ok := exitError.Sys().(syscall.WaitStatus); ok { 181 | return false 182 | } 183 | } 184 | 185 | return true 186 | } 187 | 188 | func (m *ShellCommand) getCommandToRun() (string, []string) { 189 | shell := os.Getenv("SHELL") 190 | if m.CommandToRun != "" { 191 | return shell, []string{"-c", m.CommandToRun} 192 | } else if m.ScriptToRun != "" { 193 | return shell, []string{"-x", m.ScriptToRun} 194 | } else { 195 | return shell, []string{} 196 | } 197 | } 198 | 199 | func (m *ShellCommand) isInteractiveMode() bool { 200 | return m.CommandToRun == "" && m.ScriptToRun == "" 201 | } 202 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/datadog/grimoire 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.22.1 6 | 7 | require ( 8 | github.com/aws/aws-sdk-go-v2 v1.30.3 9 | github.com/aws/aws-sdk-go-v2/config v1.27.24 10 | github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.42.1 11 | github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 12 | github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be 13 | github.com/datadog/stratus-red-team/v2 v2.16.0 14 | github.com/google/uuid v1.6.0 15 | github.com/sirupsen/logrus v1.4.1 16 | github.com/spf13/cobra v1.6.0 17 | github.com/stretchr/testify v1.9.0 18 | ) 19 | 20 | require ( 21 | cloud.google.com/go/auth v0.6.1 // indirect 22 | cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect 23 | cloud.google.com/go/compute v1.27.2 // indirect 24 | cloud.google.com/go/compute/metadata v0.4.0 // indirect 25 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 // indirect 26 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 // indirect 27 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.1 // indirect 28 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v1.0.0 // indirect 29 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 // indirect 30 | github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect 31 | github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect 32 | github.com/aws/aws-sdk-go-v2/credentials v1.17.24 // indirect 33 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 // indirect 34 | github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.5 // indirect 35 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 // indirect 36 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 // indirect 37 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect 38 | github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.13 // indirect 39 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.168.0 // indirect 40 | github.com/aws/aws-sdk-go-v2/service/ec2instanceconnect v1.25.1 // indirect 41 | github.com/aws/aws-sdk-go-v2/service/eks v1.46.0 // indirect 42 | github.com/aws/aws-sdk-go-v2/service/iam v1.34.1 // indirect 43 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect 44 | github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.15 // indirect 45 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 // indirect 46 | github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.13 // indirect 47 | github.com/aws/aws-sdk-go-v2/service/lambda v1.56.1 // indirect 48 | github.com/aws/aws-sdk-go-v2/service/organizations v1.30.0 // indirect 49 | github.com/aws/aws-sdk-go-v2/service/rds v1.81.2 // indirect 50 | github.com/aws/aws-sdk-go-v2/service/rolesanywhere v1.13.1 // indirect 51 | github.com/aws/aws-sdk-go-v2/service/route53resolver v1.30.1 // indirect 52 | github.com/aws/aws-sdk-go-v2/service/s3 v1.58.0 // indirect 53 | github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.1 // indirect 54 | github.com/aws/aws-sdk-go-v2/service/ses v1.25.0 // indirect 55 | github.com/aws/aws-sdk-go-v2/service/ssm v1.52.1 // indirect 56 | github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 // indirect 57 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 // indirect 58 | github.com/aws/smithy-go v1.20.3 // indirect 59 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 60 | github.com/davecgh/go-spew v1.1.1 // indirect 61 | github.com/emicklei/go-restful/v3 v3.12.1 // indirect 62 | github.com/felixge/httpsnoop v1.0.4 // indirect 63 | github.com/go-logr/logr v1.4.2 // indirect 64 | github.com/go-logr/stdr v1.2.2 // indirect 65 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 66 | github.com/go-openapi/jsonreference v0.21.0 // indirect 67 | github.com/go-openapi/swag v0.23.0 // indirect 68 | github.com/gogo/protobuf v1.3.2 // indirect 69 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect 70 | github.com/golang-jwt/jwt/v5 v5.2.1 // indirect 71 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 72 | github.com/golang/protobuf v1.5.4 // indirect 73 | github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect 74 | github.com/google/gofuzz v1.2.0 // indirect 75 | github.com/google/s2a-go v0.1.7 // indirect 76 | github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect 77 | github.com/googleapis/gax-go/v2 v2.12.5 // indirect 78 | github.com/gorilla/websocket v1.5.3 // indirect 79 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 80 | github.com/hashicorp/go-version v1.6.0 // indirect 81 | github.com/hashicorp/hc-install v0.4.0 // indirect 82 | github.com/hashicorp/terraform-exec v0.17.3 // indirect 83 | github.com/hashicorp/terraform-json v0.14.0 // indirect 84 | github.com/imdario/mergo v0.3.16 // indirect 85 | github.com/inconshreveable/mousetrap v1.0.1 // indirect 86 | github.com/jmespath/go-jmespath v0.4.0 // indirect 87 | github.com/josharian/intern v1.0.0 // indirect 88 | github.com/json-iterator/go v1.1.12 // indirect 89 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect 90 | github.com/kylelemons/godebug v1.1.0 // indirect 91 | github.com/mailru/easyjson v0.7.7 // indirect 92 | github.com/moby/spdystream v0.4.0 // indirect 93 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 94 | github.com/modern-go/reflect2 v1.0.2 // indirect 95 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 96 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect 97 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect 98 | github.com/pmezard/go-difflib v1.0.0 // indirect 99 | github.com/spf13/pflag v1.0.5 // indirect 100 | github.com/stretchr/objx v0.5.2 // indirect 101 | github.com/zclconf/go-cty v1.11.0 // indirect 102 | go.opencensus.io v0.24.0 // indirect 103 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect 104 | go.opentelemetry.io/otel v1.28.0 // indirect 105 | go.opentelemetry.io/otel/metric v1.28.0 // indirect 106 | go.opentelemetry.io/otel/trace v1.28.0 // indirect 107 | golang.org/x/crypto v0.25.0 // indirect 108 | golang.org/x/net v0.27.0 // indirect 109 | golang.org/x/oauth2 v0.21.0 // indirect 110 | golang.org/x/sys v0.22.0 // indirect 111 | golang.org/x/term v0.22.0 // indirect 112 | golang.org/x/text v0.16.0 // indirect 113 | golang.org/x/time v0.5.0 // indirect 114 | golang.org/x/tools v0.23.0 // indirect 115 | google.golang.org/api v0.187.0 // indirect 116 | google.golang.org/genproto v0.0.0-20240708141625-4ad9e859172b // indirect 117 | google.golang.org/genproto/googleapis/api v0.0.0-20240708141625-4ad9e859172b // indirect 118 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b // indirect 119 | google.golang.org/grpc v1.65.0 // indirect 120 | google.golang.org/protobuf v1.34.2 // indirect 121 | gopkg.in/inf.v0 v0.9.1 // indirect 122 | gopkg.in/yaml.v2 v2.4.0 // indirect 123 | gopkg.in/yaml.v3 v3.0.1 // indirect 124 | k8s.io/api v0.30.2 // indirect 125 | k8s.io/apimachinery v0.30.2 // indirect 126 | k8s.io/client-go v0.30.2 // indirect 127 | k8s.io/klog/v2 v2.130.1 // indirect 128 | k8s.io/kube-openapi v0.0.0-20240709000822-3c01b740850f // indirect 129 | k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 // indirect 130 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 131 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 132 | sigs.k8s.io/yaml v1.4.0 // indirect 133 | ) 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grimoire 2 | 3 |

4 | logo 5 |

6 | 7 | Grimoire is a "REPL for detection engineering" that allows you to generate datasets of cloud audit logs for common attack techniques. It currently supports AWS. 8 | 9 |

10 | 11 | Terminal screenshot 12 | 13 |

14 | 15 | 16 |

17 | 18 | Terminal screenshot 19 | 20 |

21 | 22 | ## How it works 23 | 24 | First, Grimoire detonates an attack. It injects a unique user agent containing a UUID. Then, it polls CloudTrail to retrieve the audit logs caused by the detonation, and streams the resulting logs to an output file or to your terminal. 25 | 26 | Supported detonators: 27 | - [Stratus Red Team](https://github.com/DataDog/stratus-red-team) 28 | - AWS CLI interactive shell 29 | 30 | Supported logs backend: 31 | - AWS CloudTrail (through the `LookupEvents` API) 32 | 33 | ## Installation 34 | 35 | ### Direct install 36 | 37 | Requires Go 1.22+ 38 | 39 | ```bash 40 | go install -v github.com/datadog/grimoire/cmd/grimoire@latest 41 | ``` 42 | 43 | ### Homebrew 44 | 45 | ```bash 46 | brew tap datadog/grimoire https://github.com/DataDog/grimoire 47 | brew install datadog/grimoire/grimoire 48 | ``` 49 | 50 | ### Pre-built binaries 51 | 52 | Download one of the pre-built binaries from the [releases page](https://github.com/DataDog/grimoire/releases). 53 | ## Getting started 54 | 55 | Make sure you're [authenticated against AWS](https://docs.aws.amazon.com/signin/latest/userguide/command-line-sign-in.html) and have `AWS_REGION` set before running Grimoire: 56 | 57 | ``` 58 | export AWS_REGION=us-east-1 59 | ``` 60 | 61 | ## Usage 62 | 63 | Grimoire has two main commands: 64 | - [`grimoire stratus-red-team`](#detonate-an-attack-technique-with-stratus-red-team) 65 | - [`grimoire shell`](#detonate-an-attack-manually-in-an-interactive-shell) 66 | 67 | ### Detonate an attack technique with [Stratus Red Team](https://github.com/DataDog/stratus-red-team): 68 | 69 | ```bash 70 | $ grimoire stratus-red-team -o /tmp/logs --attack-technique aws.credential-access.ssm-retrieve-securestring-parameters 71 | INFO[0000] Warming up Stratus Red Team attack technique aws.credential-access.ssm-retrieve-securestring-parameters 72 | INFO[0000] Detonating Stratus Red Team attack technique aws.credential-access.ssm-retrieve-securestring-parameters 73 | INFO[0003] Stratus Red Team attack technique successfully detonated 74 | INFO[0003] Searching for CloudTrail events... 75 | INFO[0009] Found new CloudTrail event generated on 2024-07-30T20:58:43Z UTC: DescribeParameters 76 | INFO[0009] Found new CloudTrail event generated on 2024-07-30T20:58:42Z UTC: DescribeParameters 77 | ``` 78 | 79 | In another terminal, you can tail `/tmp/logs` to see the logs as they're discovered in CloudTrail. Alternatively, you can use `-o -` to print the logs in your terminal as they are found. You can safely use Ctrl+C to exit. 80 | 81 | Keep in mind that some Stratus Red Team attack techniques may take some time to complete. These are marked with a `Slow` badge on their documentation page, such as [Steal EC2 Instance Credentials](https://stratus-red-team.cloud/attack-techniques/AWS/aws.credential-access.ec2-steal-instance-credentials/). 82 | 83 | ### Detonate an attack manually in an interactive shell 84 | 85 | You can also detonate an attack manually in an interactive shell. In that case, Grimoire will spin up a new $SHELL for you, and inject the `AWS_EXECUTION_ENV` environment variable to ensure that the AWS CLI commands you run generate logs that Grimoire captures. 86 | 87 | ```bash 88 | $ grimoire shell -o /tmp/logs 89 | INFO[0000] Grimoire will now run your shell and automatically inject a unique identifier to your HTTP user agent when using the AWS CLI 90 | INFO[0000] You can use the AWS CLI as usual. Press Ctrl+D or type 'exit' to return to Grimoire. 91 | INFO[0000] When you exit the shell, Grimoire will look for the CloudTrail events that your commands have generated. 92 | INFO[0000] Press ENTER to continue 93 | 94 | # We're now in a "Grimoire-instrumented" shell 95 | $ aws sts get-caller-identity 96 | $ aws ec2 describe-instances 97 | $ exit 98 | INFO[0040] Welcome back to Grimoire! 99 | INFO[0040] Searching for CloudTrail events... 100 | INFO[0090] Found event: DescribeInstances 101 | INFO[0090] Found event: GetCallerIdentity 102 | ``` 103 | 104 | In the shell that Grimoire spawns, you can use the `$GRIMOIRE_DETONATION_ID` environment variable if you eed to propagate the user agent to other tools. For instance: 105 | 106 | ``` 107 | $ grimoire shell -o /tmp/logs 108 | $ awscurl -H "User-Agent: $GRIMOIRE_DETONATION_ID" --service ec2 \ 109 | 'https://ec2.amazonaws.com?Action=DescribeRegions&Version=2013-10-15' 110 | ``` 111 | 112 | ... will allow Grimoire to identify CloudTrail events generated by `awscurl` as well. 113 | 114 | ### Detonate an attack manually by specifying commands 115 | 116 | You can use `grimoire shell` in non-interactive mode, passing it a command or a script to run instead: 117 | 118 | ``` 119 | $ grimoire shell --command 'aws sts get-caller-identity' 120 | INFO[0000] Running detonation command: aws sts get-caller-identity 121 | { 122 | "UserId": "AIDAEXAMPLE", 123 | "Account": "012345678901", 124 | "Arn": "arn:aws:iam::012345678901:user/christophe" 125 | } 126 | INFO[0002] Searching for CloudTrail events... 127 | INFO[0140] Found event: GetCallerIdentity 128 | ``` 129 | 130 | Using a script: 131 | 132 | ``` 133 | $ cat /tmp/script.sh 134 | aws sts get-caller-identity 135 | aws iam create-user --user-name foobar 136 | aws iam create-access-key --user-name foobar 137 | 138 | $ grimoire shell --script /tmp/script.sh 139 | INFO[0000] Running detonation script: /tmp/script.sh 140 | +/tmp/script.sh:1> aws sts get-caller-identity 141 | +/tmp/script.sh:2> aws iam create-user --user-name foobar 142 | +/tmp/script.sh:3> aws iam create-access-key --user-name foobar 143 | INFO[0005] Searching for CloudTrail events... 144 | ``` 145 | 146 | ### Advanced usage 147 | 148 | You can use `--timeout`, `--max-events`, `--include-events`, `--exclude-events` and `--only-write-events` to control how long Grimoire should poll CloudTrail, how many events to retrieve, and which events to include or exclude. 149 | 150 | ```bash 151 | # Wait for a single sts:GetCallerIdentity event and exit 152 | grimoire shell --command 'aws sts get-caller-identity' --include-events 'sts:GetCallerIdentity' --max-events 1 153 | 154 | # Only keep iam:* events and exit after 5 minutes or 2 events (whichever comes first) 155 | grimoire stratus-red-team --attack-technique aws.persistence.iam-create-admin-user --include-events 'iam:*' --max-events 2 --timeout 3m 156 | 157 | # Only keep IAM write events 158 | grimoire shell --script /tmp/attack.sh --include-events 'iam:*' --only-write-events 159 | 160 | # Exclude sts:AssumeRole events 161 | grimoire shell --script /tmp/attack.sh --exclude-events 'sts:GetCallerIdentity' 162 | 163 | # Wait for at least one IAM or EC2 write event and exit. Fail if the logs aren't available within 10 minutes. 164 | grimoire shell --script /tmp/attack.sh --only-write-events --include-events 'iam:*,ec2:*' --max-events 1 --timeout 10m 165 | ``` 166 | ## Development 167 | 168 | Running locally: 169 | 170 | ```bash 171 | alias grimoire='go run cmd/grimoire/*.go' 172 | grimoire --help 173 | ``` 174 | 175 | Use `--debug` for verbose logging. 176 | 177 | Building binaries: 178 | 179 | ```bash 180 | make 181 | ``` 182 | 183 | Running the tests: 184 | 185 | ```bash 186 | make test 187 | ``` 188 | 189 | ## Disclaimer 190 | 191 | When detonating attacks with Stratus Red Team, it's best to avoid interrupting execution with Ctrl+C while the technique is being detonated. While we take a great deal of care to clean up after ourselves, it may happen that some resources are left behind, specifically because the [terraform-exec](https://github.com/hashicorp/terraform-exec) wrapper used by Stratus Red Team panics on Ctrl+c and doesn't offer an option to exit cleanly. 192 | 193 | ## FAQ 194 | 195 | ### Why are CloudTrail events slow to arrive? 196 | 197 | Delivery of CloudTrail events can take up to 15 minutes. If you don't see events immediately, wait a few minutes and try again. In the majority of cases, though, it's expected that CloudTrail events are made available within 5 minutes. 198 | 199 | For more information, see the [AWS documentation](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/get-and-view-cloudtrail-log-files.html) and [How fast is CloudTrail today?](https://tracebit.com/blog/how-fast-is-cloudtrail-today-investigating-cloudtrail-delays-using-athena). 200 | 201 | ### Why isn't Grimoire part of Stratus Red Team? 202 | 203 | We chose to separate Grimoire from Stratus Red Team because we feel that Grimoire should support other ways of detonating attack techniques. 204 | 205 | That said, Stratus Red Team has a dedicated section `Detonation logs` for every AWS attack technique. These logs have been generated using Grimoire and can be used to validate the detection rules you've written. 206 | 207 | ### What about CloudTrail Data Events? 208 | 209 | Data events are not supported for now. If you're interested, please upvote [this issue](https://github.com/DataDog/grimoire/issues/1). 210 | -------------------------------------------------------------------------------- /pkg/grimoire/logs/cloudtrail.go: -------------------------------------------------------------------------------- 1 | package logs 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "github.com/aws/aws-sdk-go-v2/service/cloudtrail" 9 | "github.com/datadog/grimoire/pkg/grimoire/detonators" 10 | grimoire "github.com/datadog/grimoire/pkg/grimoire/utils" 11 | log "github.com/sirupsen/logrus" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | type UserAgentMatchType int 17 | 18 | const ( 19 | UserAgentMatchTypeExact UserAgentMatchType = iota 20 | UserAgentMatchTypePartial 21 | ) 22 | 23 | type CloudTrailEventsFinder struct { 24 | CloudtrailClient *cloudtrail.Client 25 | Options *CloudTrailEventLookupOptions 26 | } 27 | 28 | type CloudTrailEventLookupOptions struct { 29 | // Timeout to find the *first* CloudTrail event 30 | Timeout time.Duration 31 | 32 | // Wait for at most this number of events to be found 33 | MaxEvents int 34 | 35 | // Issue a new CloudTrail lookup query every LookupInterval 36 | LookupInterval time.Duration 37 | 38 | // Exclude specific CloudTrail events from the search 39 | // NOTE: Only one of IncludeEvents and ExcludeEvents can be used simultaneously, not both 40 | // Event names should be in the format "[service]:[eventName]", e.g. "sts:GetCallerIdentity" and are case-insensitive 41 | ExcludeEvents []string 42 | 43 | // Only include specific CloudTrail events from the search 44 | // NOTE: Only one of IncludeEvents and ExcludeEvents can be used simultaneously, not both 45 | // Event names should be in the format "[service]:[eventName]", e.g. "sts:GetCallerIdentity" and are case-insensitive 46 | IncludeEvents []string 47 | 48 | // Only keep write events (i.e., non-read-only events) 49 | // see https://docs.aws.amazon.com/awscloudtrail/latest/userguide/logging-management-events-with-cloudtrail.html#read-write-events-mgmt 50 | // this condition is evaluated in addition (and honoring) the IncludeEvents/ExcludeEvents lists 51 | WriteEventsOnly bool 52 | 53 | // UserAgentMatchType is the type of match to use when filtering by UserAgent 54 | UserAgentMatchType UserAgentMatchType 55 | 56 | // in some cases, the time logged in the CloudTrail event is a few seconds "late" in comparison to when the detonation happens 57 | // ExtendTimeWindow allows to extend as appropriate the end time of the CloudTrail lookup 58 | ExtendTimeWindow time.Duration 59 | } 60 | 61 | type CloudTrailResult struct { 62 | CloudTrailEvent *map[string]interface{} 63 | Error error 64 | } 65 | 66 | func (m *CloudTrailEventsFinder) FindLogs(ctx context.Context, detonation *detonators.DetonationInfo) (chan *CloudTrailResult, error) { 67 | if len(m.Options.IncludeEvents) > 0 && len(m.Options.ExcludeEvents) > 0 { 68 | return nil, errors.New("only zero or one of IncludeEvents and ExcludeEvents can be specified") 69 | } 70 | return m.findEventsWithCloudTrail(ctx, detonation) 71 | } 72 | 73 | func (m *CloudTrailEventsFinder) findEventsWithCloudTrail(ctx context.Context, detonation *detonators.DetonationInfo) (chan *CloudTrailResult, error) { 74 | results := make(chan *CloudTrailResult) 75 | resultsInternal := make(chan *CloudTrailResult) 76 | 77 | // findEventsWithCloudTrailAsync has a long-running for loop that will be stopped when the context is cancelled 78 | // To achieve this, we "proxy" the results through another channel, and close it when the context is cancelled 79 | // This allows to quickly abort whenever the parent context is cancelled (for instance on a Ctrl+C) 80 | // Otherwise we'd have to check inside the loop and there could be a delay of several seconds 81 | 82 | go m.findEventsWithCloudTrailAsync(ctx, detonation, resultsInternal) 83 | 84 | go func() { 85 | defer close(results) 86 | for { 87 | select { 88 | case <-ctx.Done(): 89 | // Parent context cancelled 90 | log.Debug("CloudTrailEventFinder identified that the parent context was cancelled, returning") 91 | results <- &CloudTrailResult{Error: fmt.Errorf("parent context was cancelled: %w", ctx.Err())} 92 | return 93 | 94 | case result, ok := <-resultsInternal: 95 | // We got a result, forward it to the parent channel 96 | if !ok { 97 | return // no more results 98 | } 99 | results <- result 100 | } 101 | } 102 | }() 103 | 104 | return results, nil 105 | } 106 | 107 | func (m *CloudTrailEventsFinder) findEventsWithCloudTrailAsync(ctx context.Context, detonation *detonators.DetonationInfo, results chan *CloudTrailResult) { 108 | defer close(results) 109 | 110 | var allEvents = []map[string]interface{}{} 111 | now := time.Now() 112 | deadline := now.Add(m.Options.Timeout) 113 | 114 | log.Debugf("Deadline for finding CloudTrail events is %s", deadline) 115 | 116 | // We look for events as long as we didn't reach the deadline 117 | for time.Now().Before(deadline) { 118 | events, err := m.lookupEvents(ctx, detonation) 119 | if err != nil { 120 | results <- &CloudTrailResult{Error: fmt.Errorf("unable to run CloudTrail LookupEvents: %w", err)} 121 | return 122 | } 123 | if len(events) > 0 { 124 | // Add the events we found to our current set of events, removing any duplicates 125 | var newEventsFound []*map[string]interface{} 126 | allEvents, newEventsFound = dedupeAndAppend(allEvents, events) 127 | 128 | if len(newEventsFound) > 0 { 129 | log.Debugf("Found %d new CloudTrail events", len(newEventsFound)) 130 | for _, newEvent := range newEventsFound { 131 | log.Debug("Publishing new event to asynchronous channel") 132 | results <- &CloudTrailResult{CloudTrailEvent: newEvent} 133 | } 134 | 135 | // If we reached the max number of events to wait for, return as soon as possible 136 | if m.Options.MaxEvents > 0 && len(allEvents) >= m.Options.MaxEvents { 137 | log.Debugf("Reached %d events, stopping search", m.Options.MaxEvents) 138 | return 139 | } 140 | } else { 141 | log.Debug("Some CloudTrail events were returned, but no previously-unseen events were found") 142 | } 143 | } 144 | log.Debugf("Sleeping for LookupInterval=%f seconds", m.Options.LookupInterval.Seconds()) 145 | time.Sleep(m.Options.LookupInterval) 146 | } 147 | 148 | if len(allEvents) == 0 { 149 | results <- &CloudTrailResult{Error: fmt.Errorf("timed out after %f seconds waiting for CloudTrail events", m.Options.Timeout.Seconds())} 150 | return 151 | } 152 | } 153 | 154 | func (m *CloudTrailEventsFinder) lookupEvents(ctx context.Context, detonation *detonators.DetonationInfo) ([]map[string]interface{}, error) { 155 | // Check if the parent context was cancelled to avoid awkwardly continuing the search when the program is exiting 156 | if err := ctx.Err(); err != nil && errors.Is(err, context.Canceled) { 157 | return nil, context.Canceled 158 | } 159 | 160 | // in some cases, the time logged in the CloudTrail event is a few seconds "late" in comparison to when the detonation happens 161 | endTime := detonation.EndTime.Add(m.Options.ExtendTimeWindow) 162 | paginator := cloudtrail.NewLookupEventsPaginator(m.CloudtrailClient, &cloudtrail.LookupEventsInput{ 163 | StartTime: &detonation.StartTime, 164 | EndTime: &endTime, 165 | }) 166 | 167 | log.WithField("start_time", detonation.StartTime). 168 | WithField("end_time", detonation.EndTime). 169 | Debugf("Looking for CloudTrail events with using LookupEvents and detonation ID %s", detonation.DetonationID) 170 | 171 | events := []map[string]interface{}{} 172 | 173 | for paginator.HasMorePages() { 174 | logs, err := paginator.NextPage(ctx) 175 | if err != nil { 176 | return nil, fmt.Errorf("unable to retrieve CloudTrail events: %w", err) 177 | } 178 | if len(logs.Events) > 0 { 179 | log.Debugf("Found %d CloudTrail events", len(logs.Events)) 180 | for i := range logs.Events { 181 | event := logs.Events[i].CloudTrailEvent 182 | var parsed map[string]interface{} 183 | json.Unmarshal([]byte(*event), &parsed) 184 | eventName := parsed["eventName"].(string) 185 | if m.eventsMatchesDetonation(parsed, detonation) { 186 | if m.shouldKeepEvent(&parsed) { 187 | log.Debugf("Found CloudTrail event %s matching detonation UID", eventName) 188 | events = append(events, parsed) 189 | } else { 190 | log.Debugf("Found CloudTrail event %s matching detonation UID, but ignoring as it's on the exclude list or not in the include list", eventName) 191 | } 192 | } 193 | } 194 | } 195 | } 196 | return events, nil 197 | } 198 | 199 | func (m *CloudTrailEventsFinder) shouldKeepEvent(event *map[string]interface{}) bool { 200 | // note: we know (precondition) that zero or one of IncludeEvents and ExcludeEvents is set, not both 201 | 202 | fullEventName := grimoire.GetCloudTrailEventFullName(event) 203 | isReadOnly := (*event)["readOnly"].(bool) 204 | 205 | if m.Options.WriteEventsOnly && isReadOnly { 206 | log.Debugf("Ignoring event %s as it's read-only and we only want write events", fullEventName) 207 | return false 208 | } 209 | 210 | // If an exclusion list is set, we exclude events that are in the list 211 | if len(m.Options.ExcludeEvents) > 0 { 212 | for i := range m.Options.ExcludeEvents { 213 | if grimoire.StringMatches(fullEventName, m.Options.ExcludeEvents[i]) { 214 | log.Debugf("Excluding event %s as it's on the exclude list", fullEventName) 215 | return false 216 | } 217 | } 218 | return true 219 | } 220 | 221 | // If an inclusion list is set, we only include events that are in the list 222 | if len(m.Options.IncludeEvents) > 0 { 223 | for i := range m.Options.IncludeEvents { 224 | if grimoire.StringMatches(fullEventName, m.Options.IncludeEvents[i]) { 225 | log.Debugf("Including event %s as it's on the include list", fullEventName) 226 | return true 227 | } 228 | } 229 | return false 230 | } 231 | 232 | return true // no exclude nor include list, we keep everything 233 | } 234 | 235 | func (m *CloudTrailEventsFinder) eventsMatchesDetonation(event map[string]interface{}, detonation *detonators.DetonationInfo) bool { 236 | userAgent := event["userAgent"].(string) 237 | 238 | switch m.Options.UserAgentMatchType { 239 | case UserAgentMatchTypeExact: 240 | return strings.EqualFold(userAgent, detonation.DetonationID) 241 | case UserAgentMatchTypePartial: 242 | return strings.Contains(userAgent, detonation.DetonationID) 243 | default: 244 | return false 245 | } 246 | } 247 | 248 | func dedupeAndAppend(allEvents []map[string]interface{}, newEvents []map[string]interface{}) ([]map[string]interface{}, []*map[string]interface{}) { 249 | // Build a set of event IDs 250 | //TODO don't rebuild every time 251 | eventIDs := map[string]bool{} 252 | const EventIDKey = "eventID" 253 | for _, event := range allEvents { 254 | eventID := event[EventIDKey].(string) 255 | eventIDs[eventID] = true 256 | } 257 | 258 | // Add events we don't have yet 259 | actualNewEvents := []*map[string]interface{}{} 260 | for _, newEvent := range newEvents { 261 | eventID := newEvent[EventIDKey].(string) 262 | if _, eventExists := eventIDs[eventID]; !eventExists { 263 | allEvents = append(allEvents, newEvent) 264 | actualNewEvents = append(actualNewEvents, &newEvent) 265 | } 266 | } 267 | 268 | return allEvents, actualNewEvents 269 | } 270 | -------------------------------------------------------------------------------- /cmd/grimoire/stratus-red-team.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "github.com/aws/aws-sdk-go-v2/config" 8 | "github.com/aws/aws-sdk-go-v2/service/cloudtrail" 9 | "github.com/datadog/grimoire/pkg/grimoire/detonators" 10 | "github.com/datadog/grimoire/pkg/grimoire/logs" 11 | utils "github.com/datadog/grimoire/pkg/grimoire/utils" 12 | log "github.com/sirupsen/logrus" 13 | "github.com/spf13/cobra" 14 | "os" 15 | "os/signal" 16 | "strings" 17 | "sync" 18 | "sync/atomic" 19 | "syscall" 20 | ) 21 | 22 | type StratusRedTeamCommand struct { 23 | StratusRedTeamDetonator *detonators.StratusRedTeamDetonator 24 | OutputFile string 25 | cleanupWg sync.WaitGroup 26 | cleanupMutex sync.Mutex 27 | cleanupRunning atomic.Bool 28 | cleanupSucceeded atomic.Bool 29 | wasCtrlCPressed atomic.Bool 30 | ctx context.Context 31 | cancel func() 32 | sigChan chan os.Signal 33 | } 34 | 35 | func NewStratusRedTeamCommand() *cobra.Command { 36 | var stratusRedTeamAttackTechnique string 37 | var outputFile string 38 | 39 | stratusRedTeamCommand := &cobra.Command{ 40 | Use: "stratus-red-team", 41 | SilenceUsage: true, 42 | Example: "Detonate a Stratus Red Team attack technique", 43 | RunE: func(cmd *cobra.Command, args []string) error { 44 | if stratusRedTeamAttackTechnique == "" { 45 | return errors.New("missing Stratus Red Team attack technique") 46 | } 47 | detonator, err := detonators.NewStratusRedTeamDetonator(stratusRedTeamAttackTechnique) 48 | if err != nil { 49 | return err 50 | } 51 | command := StratusRedTeamCommand{ 52 | StratusRedTeamDetonator: detonator, 53 | OutputFile: outputFile, 54 | } 55 | return command.Do() 56 | }, 57 | } 58 | 59 | stratusRedTeamCommand.Flags().StringVarP(&stratusRedTeamAttackTechnique, "attack-technique", "", "", "Stratus Red Team attack technique to detonate. Use 'stratus list' to list available attack techniques or browse https://stratus-red-team.cloud/attack-techniques/list/.") 60 | stratusRedTeamCommand.Flags().StringVarP(&outputFile, "output", "o", "", "Output file to write CloudTrail events to. Grimoire will overwrite the file if it exists, and create otherwise.") 61 | initLookupFlags(stratusRedTeamCommand) 62 | 63 | return stratusRedTeamCommand 64 | } 65 | 66 | func (m *StratusRedTeamCommand) Do() error { 67 | awsConfig, _ := config.LoadDefaultConfig(context.Background()) 68 | cloudtrailLogs := &logs.CloudTrailEventsFinder{ 69 | CloudtrailClient: cloudtrail.NewFromConfig(awsConfig), 70 | Options: &logs.CloudTrailEventLookupOptions{ 71 | Timeout: timeout, 72 | LookupInterval: lookupInterval, 73 | IncludeEvents: includeEvents, 74 | ExcludeEvents: excludeEvents, 75 | MaxEvents: maxEvents, 76 | WriteEventsOnly: writeEventsOnly, 77 | ExtendTimeWindow: extendSearchWindow, 78 | UserAgentMatchType: logs.UserAgentMatchTypeExact, 79 | }, 80 | } 81 | 82 | if err := utils.CreateOrTruncateJSONFile(m.OutputFile); err != nil { 83 | return err 84 | } 85 | 86 | m.ctx, m.cancel = context.WithCancel(context.Background()) 87 | defer m.cancel() 88 | 89 | m.handleCtrlC() 90 | 91 | detonation, detonationErr := m.detonateStratusRedTeam() 92 | 93 | // The attack has been detonated (whether successfully or not) 94 | // We can already start cleaning up, in parallel of looking for the logs 95 | // A mutex makes sure the main program doesn't exit while we're cleaning up 96 | // NOTE: we intentionally start this routine before checking for detonation errors, because 97 | // the Stratus Red Team detonate doesn't clean up failed detonations (by design) 98 | go m.cleanupRoutineAsync() 99 | defer m.shutDown() 100 | 101 | if detonationErr != nil { 102 | return fmt.Errorf("unable to detonate Stratus Red Team attack technique %s: %w", m.StratusRedTeamDetonator.AttackTechnique, detonationErr) 103 | } 104 | 105 | log.Info("Stratus Red Team attack technique successfully detonated") 106 | 107 | log.Info("Searching for CloudTrail events...") 108 | results, detonationErr := cloudtrailLogs.FindLogs(m.ctx, detonation) 109 | if detonationErr != nil { 110 | return detonationErr 111 | } 112 | 113 | errorChan := make(chan error) 114 | 115 | // Main processing loop that consumes events and streams them out 116 | go m.processingLoop(results, errorChan) 117 | 118 | // Wait for event processing to be done, either due to a Ctrl+C either due to normal exit conditions 119 | log.Debugf("Waiting for event processing to be done") 120 | err := <-errorChan 121 | log.Debugf("Event processing done, received a result from the error channel") 122 | if err != nil { 123 | return err 124 | } 125 | 126 | return nil 127 | } 128 | 129 | func (m *StratusRedTeamCommand) handleNewEvent(event *map[string]interface{}) error { 130 | log.Printf("Found new CloudTrail event generated on %s UTC: %s", (*event)["eventTime"], utils.GetCloudTrailEventFullName(event)) 131 | err := utils.AppendToJsonFileArray(m.OutputFile, *event) 132 | if err != nil { 133 | return fmt.Errorf("unable to write CloudTrail event to %s: %v", m.OutputFile, err) 134 | } 135 | return nil 136 | } 137 | 138 | func (m *StratusRedTeamCommand) CleanupDetonation() error { 139 | m.cleanupMutex.Lock() 140 | defer m.cleanupMutex.Unlock() 141 | m.cleanupWg.Add(1) 142 | defer m.cleanupWg.Done() 143 | m.cleanupRunning.Store(true) 144 | defer m.cleanupRunning.Store(false) 145 | 146 | err := m.StratusRedTeamDetonator.CleanUp() 147 | if err == nil { 148 | m.cleanupSucceeded.Store(true) 149 | } 150 | 151 | log.Debug("Clean-up routine completed") 152 | return err 153 | } 154 | 155 | func (m *StratusRedTeamCommand) handleCtrlC() { 156 | m.sigChan = make(chan os.Signal, 1) 157 | signal.Notify(m.sigChan, os.Interrupt, syscall.SIGTERM) 158 | // Handle CTRL+C gracefully 159 | go func() { 160 | for range m.sigChan { 161 | if m.wasCtrlCPressed.CompareAndSwap(false, true) { 162 | log.Info("Exiting Grimoire cleanly, don't press Ctrl+C again") 163 | m.cancel() 164 | // NOTE: We don't clean up here again, because the cleanup starts asynchronously after the attack is detonated 165 | // and the program waits for it to be completed before exiting in any case. 166 | // In the future, if we have generic, non-Stratus Red Team related clean-up tasks, we should add them here. 167 | } else { 168 | log.Info("You already pressed Ctrl+C, please wait for Grimoire to exit") 169 | } 170 | } 171 | }() 172 | } 173 | 174 | func (m *StratusRedTeamCommand) detonateStratusRedTeam() (*detonators.DetonationInfo, error) { 175 | // Detonate Stratus Red Team attack technique, honoring context cancellation 176 | type StratusRedTeamDetonation struct { 177 | detonation *detonators.DetonationInfo 178 | err error 179 | } 180 | detonationChan := make(chan *StratusRedTeamDetonation) 181 | go func() { 182 | detonation, err := m.StratusRedTeamDetonator.Detonate() 183 | detonationChan <- &StratusRedTeamDetonation{detonation, err} 184 | }() 185 | select { 186 | case <-m.ctx.Done(): 187 | log.Infof("You pressed Ctrl+C during the Stratus Red Team detonation, which could leave some cloud resources in your account. Attempting to clean it up...") 188 | if err := m.CleanupDetonation(); err != nil { 189 | log.Debugf("Unable to clean-up Stratus Red Team attack technique %s: %v", m.StratusRedTeamDetonator.AttackTechnique, err) 190 | log.Debugf("Attack technique status is %s", m.StratusRedTeamDetonator.GetAttackTechniqueState()) 191 | log.Warnf("Unable to clean-up Stratus Red Team attack technique. You might want to manually clean it up by running 'stratus cleanup %s' to avoid leaving resources in your account", m.StratusRedTeamDetonator.AttackTechnique) 192 | } 193 | return nil, context.Canceled 194 | case result := <-detonationChan: 195 | return result.detonation, result.err 196 | } 197 | } 198 | 199 | func (m *StratusRedTeamCommand) cleanupRoutineAsync() { 200 | log.Info("Cleaning up Stratus Red Team detonation in the background") 201 | if err := m.CleanupDetonation(); err != nil { 202 | // Note: Stratus Red Team Cleanup function calls the Terraform Go Wrapper, which unfortunately 203 | // catches Ctrl+C signals. This means that if the user presses Ctrl+C at "the wrong time", the cleanup 204 | // will fail because the Terraform Wrapper will panic and exit 205 | 206 | // Consequently, we have some logic baked in later in this function to retry the cleanup at the end if 207 | // the asynchronous cleanup failed for this specific reason 208 | log.Debug("Asynchronous cleanup failed, will retry at the end of the program") 209 | if strings.Contains(err.Error(), "interrupts received") { 210 | log.Debug("The failure was caused by the Go terraform wrapper catching signals and panicking") 211 | } 212 | } 213 | } 214 | 215 | func (m *StratusRedTeamCommand) processingLoop(results <-chan *logs.CloudTrailResult, errorChan chan error) { 216 | for { 217 | select { 218 | 219 | // Case 1: New CloudTrail result found 220 | case evt, ok := <-results: 221 | if !ok { 222 | log.Debugf("channel closed") 223 | errorChan <- nil 224 | return // Channel closed, exit the processing loop 225 | } 226 | 227 | // If it's an error, we exit the processing loop and ultimately exit 228 | if evt.Error != nil { 229 | log.Printf("Error processing event: %v", evt.Error) 230 | m.cancel() 231 | errorChan <- evt.Error 232 | return 233 | } 234 | 235 | // Otherwise, we call handleNewEvent which will stream the newly-found event appropriately 236 | if err := m.handleNewEvent(evt.CloudTrailEvent); err != nil { 237 | // If processing of this event fails, we abort execution 238 | log.Errorf(err.Error()) 239 | m.cancel() 240 | errorChan <- err 241 | return 242 | } 243 | 244 | // We don't return here, this is the happy path where the processing loop continues 245 | 246 | case <-m.ctx.Done(): 247 | log.Debug("Stopping event processing due to context cancellation") 248 | errorChan <- nil 249 | return 250 | } 251 | } 252 | } 253 | 254 | func (m *StratusRedTeamCommand) shutDown() { 255 | // Make sure we wait until cleanup is finished before exiting 256 | if m.cleanupRunning.Load() { 257 | log.Info("Waiting for Stratus Red Team attack technique clean-up to complete...") 258 | } 259 | m.cleanupWg.Wait() 260 | if !m.cleanupSucceeded.Load() { 261 | // Note: Stratus Red Team Cleanup function calls the Terraform Go Wrapper, which unfortunately 262 | // catches Ctrl+C signals. This means that if the user presses Ctrl+C at "the wrong time", the cleanup 263 | // will fail because the Terraform Wrapper will panic and exit 264 | 265 | // Consequently, we have some logic baked in later in this function to retry the cleanup at the end if 266 | // the asynchronous cleanup failed for this specific reason 267 | log.Info("Asynchronous cleanup of the Stratus Red Team detonation failed, retrying one last time... don't press Ctrl+C") 268 | if err := m.CleanupDetonation(); err != nil { 269 | log.Warnf("unable to cleanup Stratus Red Team attack technique %s: %v", m.StratusRedTeamDetonator.AttackTechnique, err) 270 | log.Warnf("You might want to manually clean it up by running 'stratus cleanup %s'", m.StratusRedTeamDetonator.AttackTechnique) 271 | } else { 272 | log.Info("Cleanup of the Stratus Red Team attack technique succeeded") 273 | } 274 | } 275 | log.Debug("Cleanup finished, exiting") 276 | } 277 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-3rdparty.csv: -------------------------------------------------------------------------------- 1 | cloud.google.com/go/auth,https://github.com/googleapis/google-cloud-go/blob/auth/v0.6.1/auth/LICENSE,Apache-2.0 2 | cloud.google.com/go/auth/oauth2adapt,https://github.com/googleapis/google-cloud-go/blob/auth/oauth2adapt/v0.2.2/auth/oauth2adapt/LICENSE,Apache-2.0 3 | cloud.google.com/go/compute/apiv1,https://github.com/googleapis/google-cloud-go/blob/compute/v1.27.2/compute/apiv1/license_codes_client.go,Apache-2.0 4 | cloud.google.com/go/compute/internal,https://github.com/googleapis/google-cloud-go/blob/compute/v1.27.2/compute/LICENSE,Apache-2.0 5 | cloud.google.com/go/compute/metadata,https://github.com/googleapis/google-cloud-go/blob/compute/metadata/v0.4.0/compute/metadata/LICENSE,Apache-2.0 6 | github.com/Azure/azure-sdk-for-go/sdk/azcore,https://github.com/Azure/azure-sdk-for-go/blob/sdk/azcore/v1.12.0/sdk/azcore/LICENSE.txt,MIT 7 | github.com/Azure/azure-sdk-for-go/sdk/azidentity,https://github.com/Azure/azure-sdk-for-go/blob/sdk/azidentity/v1.7.0/sdk/azidentity/LICENSE.txt,MIT 8 | github.com/Azure/azure-sdk-for-go/sdk/internal,https://github.com/Azure/azure-sdk-for-go/blob/sdk/internal/v1.9.1/sdk/internal/LICENSE.txt,MIT 9 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute,https://github.com/Azure/azure-sdk-for-go/blob/sdk/resourcemanager/compute/armcompute/v1.0.0/sdk/resourcemanager/compute/armcompute/LICENSE.txt,MIT 10 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources,https://github.com/Azure/azure-sdk-for-go/blob/sdk/resourcemanager/resources/armresources/v1.2.0/sdk/resourcemanager/resources/armresources/LICENSE.txt,MIT 11 | github.com/AzureAD/microsoft-authentication-library-for-go/apps,https://github.com/AzureAD/microsoft-authentication-library-for-go/blob/v1.2.2/LICENSE,MIT 12 | github.com/aws/aws-sdk-go-v2,https://github.com/aws/aws-sdk-go-v2/blob/v1.30.3/LICENSE.txt,Apache-2.0 13 | github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream,https://github.com/aws/aws-sdk-go-v2/blob/aws/protocol/eventstream/v1.6.3/aws/protocol/eventstream/LICENSE.txt,Apache-2.0 14 | github.com/aws/aws-sdk-go-v2/config,https://github.com/aws/aws-sdk-go-v2/blob/config/v1.27.24/config/LICENSE.txt,Apache-2.0 15 | github.com/aws/aws-sdk-go-v2/credentials,https://github.com/aws/aws-sdk-go-v2/blob/credentials/v1.17.24/credentials/LICENSE.txt,Apache-2.0 16 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds,https://github.com/aws/aws-sdk-go-v2/blob/feature/ec2/imds/v1.16.9/feature/ec2/imds/LICENSE.txt,Apache-2.0 17 | github.com/aws/aws-sdk-go-v2/feature/s3/manager,https://github.com/aws/aws-sdk-go-v2/blob/feature/s3/manager/v1.17.5/feature/s3/manager/LICENSE.txt,Apache-2.0 18 | github.com/aws/aws-sdk-go-v2/internal/configsources,https://github.com/aws/aws-sdk-go-v2/blob/internal/configsources/v1.3.13/internal/configsources/LICENSE.txt,Apache-2.0 19 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2,https://github.com/aws/aws-sdk-go-v2/blob/internal/endpoints/v2.6.13/internal/endpoints/v2/LICENSE.txt,Apache-2.0 20 | github.com/aws/aws-sdk-go-v2/internal/ini,https://github.com/aws/aws-sdk-go-v2/blob/internal/ini/v1.8.0/internal/ini/LICENSE.txt,Apache-2.0 21 | github.com/aws/aws-sdk-go-v2/internal/sync/singleflight,https://github.com/aws/aws-sdk-go-v2/blob/v1.30.3/internal/sync/singleflight/LICENSE,BSD-3-Clause 22 | github.com/aws/aws-sdk-go-v2/internal/v4a,https://github.com/aws/aws-sdk-go-v2/blob/internal/v4a/v1.3.13/internal/v4a/LICENSE.txt,Apache-2.0 23 | github.com/aws/aws-sdk-go-v2/service/cloudtrail,https://github.com/aws/aws-sdk-go-v2/blob/service/cloudtrail/v1.42.1/service/cloudtrail/LICENSE.txt,Apache-2.0 24 | github.com/aws/aws-sdk-go-v2/service/ec2,https://github.com/aws/aws-sdk-go-v2/blob/service/ec2/v1.168.0/service/ec2/LICENSE.txt,Apache-2.0 25 | github.com/aws/aws-sdk-go-v2/service/ec2instanceconnect,https://github.com/aws/aws-sdk-go-v2/blob/service/ec2instanceconnect/v1.25.1/service/ec2instanceconnect/LICENSE.txt,Apache-2.0 26 | github.com/aws/aws-sdk-go-v2/service/iam,https://github.com/aws/aws-sdk-go-v2/blob/service/iam/v1.34.1/service/iam/LICENSE.txt,Apache-2.0 27 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding,https://github.com/aws/aws-sdk-go-v2/blob/service/internal/accept-encoding/v1.11.3/service/internal/accept-encoding/LICENSE.txt,Apache-2.0 28 | github.com/aws/aws-sdk-go-v2/service/internal/checksum,https://github.com/aws/aws-sdk-go-v2/blob/service/internal/checksum/v1.3.15/service/internal/checksum/LICENSE.txt,Apache-2.0 29 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url,https://github.com/aws/aws-sdk-go-v2/blob/service/internal/presigned-url/v1.11.15/service/internal/presigned-url/LICENSE.txt,Apache-2.0 30 | github.com/aws/aws-sdk-go-v2/service/internal/s3shared,https://github.com/aws/aws-sdk-go-v2/blob/service/internal/s3shared/v1.17.13/service/internal/s3shared/LICENSE.txt,Apache-2.0 31 | github.com/aws/aws-sdk-go-v2/service/lambda,https://github.com/aws/aws-sdk-go-v2/blob/service/lambda/v1.56.1/service/lambda/LICENSE.txt,Apache-2.0 32 | github.com/aws/aws-sdk-go-v2/service/organizations,https://github.com/aws/aws-sdk-go-v2/blob/service/organizations/v1.30.0/service/organizations/LICENSE.txt,Apache-2.0 33 | github.com/aws/aws-sdk-go-v2/service/rds,https://github.com/aws/aws-sdk-go-v2/blob/service/rds/v1.81.2/service/rds/LICENSE.txt,Apache-2.0 34 | github.com/aws/aws-sdk-go-v2/service/rolesanywhere,https://github.com/aws/aws-sdk-go-v2/blob/service/rolesanywhere/v1.13.1/service/rolesanywhere/LICENSE.txt,Apache-2.0 35 | github.com/aws/aws-sdk-go-v2/service/route53resolver,https://github.com/aws/aws-sdk-go-v2/blob/service/route53resolver/v1.30.1/service/route53resolver/LICENSE.txt,Apache-2.0 36 | github.com/aws/aws-sdk-go-v2/service/s3,https://github.com/aws/aws-sdk-go-v2/blob/service/s3/v1.58.0/service/s3/LICENSE.txt,Apache-2.0 37 | github.com/aws/aws-sdk-go-v2/service/secretsmanager,https://github.com/aws/aws-sdk-go-v2/blob/service/secretsmanager/v1.32.1/service/secretsmanager/LICENSE.txt,Apache-2.0 38 | github.com/aws/aws-sdk-go-v2/service/ses,https://github.com/aws/aws-sdk-go-v2/blob/service/ses/v1.25.0/service/ses/LICENSE.txt,Apache-2.0 39 | github.com/aws/aws-sdk-go-v2/service/ssm,https://github.com/aws/aws-sdk-go-v2/blob/service/ssm/v1.52.1/service/ssm/LICENSE.txt,Apache-2.0 40 | github.com/aws/aws-sdk-go-v2/service/sso,https://github.com/aws/aws-sdk-go-v2/blob/service/sso/v1.22.1/service/sso/LICENSE.txt,Apache-2.0 41 | github.com/aws/aws-sdk-go-v2/service/ssooidc,https://github.com/aws/aws-sdk-go-v2/blob/service/ssooidc/v1.26.2/service/ssooidc/LICENSE.txt,Apache-2.0 42 | github.com/aws/aws-sdk-go-v2/service/sts,https://github.com/aws/aws-sdk-go-v2/blob/service/sts/v1.30.1/service/sts/LICENSE.txt,Apache-2.0 43 | github.com/aws/smithy-go,https://github.com/aws/smithy-go/blob/v1.20.3/LICENSE,Apache-2.0 44 | github.com/aws/smithy-go/internal/sync/singleflight,https://github.com/aws/smithy-go/blob/v1.20.3/internal/sync/singleflight/LICENSE,BSD-3-Clause 45 | github.com/cenkalti/backoff/v4,https://github.com/cenkalti/backoff/blob/v4.3.0/LICENSE,MIT 46 | github.com/common-nighthawk/go-figure,https://github.com/common-nighthawk/go-figure/blob/734e95fb86be/LICENSE,MIT 47 | github.com/datadog/grimoire,https://github.com/datadog/grimoire/blob/HEAD/LICENSE,Apache-2.0 48 | github.com/datadog/stratus-red-team/v2,https://github.com/datadog/stratus-red-team/blob/v2.15.0/v2/LICENSE,Apache-2.0 49 | github.com/davecgh/go-spew/spew,https://github.com/davecgh/go-spew/blob/v1.1.1/LICENSE,ISC 50 | github.com/emicklei/go-restful/v3,https://github.com/emicklei/go-restful/blob/v3.12.1/LICENSE,MIT 51 | github.com/felixge/httpsnoop,https://github.com/felixge/httpsnoop/blob/v1.0.4/LICENSE.txt,MIT 52 | github.com/go-logr/logr,https://github.com/go-logr/logr/blob/v1.4.2/LICENSE,Apache-2.0 53 | github.com/go-logr/stdr,https://github.com/go-logr/stdr/blob/v1.2.2/LICENSE,Apache-2.0 54 | github.com/go-openapi/jsonpointer,https://github.com/go-openapi/jsonpointer/blob/v0.21.0/LICENSE,Apache-2.0 55 | github.com/go-openapi/jsonreference,https://github.com/go-openapi/jsonreference/blob/v0.21.0/LICENSE,Apache-2.0 56 | github.com/go-openapi/swag,https://github.com/go-openapi/swag/blob/v0.23.0/LICENSE,Apache-2.0 57 | github.com/gogo/protobuf,https://github.com/gogo/protobuf/blob/v1.3.2/LICENSE,BSD-3-Clause 58 | github.com/golang-jwt/jwt,https://github.com/golang-jwt/jwt/blob/v3.2.2/LICENSE,MIT 59 | github.com/golang-jwt/jwt/v5,https://github.com/golang-jwt/jwt/blob/v5.2.1/LICENSE,MIT 60 | github.com/golang/groupcache/lru,https://github.com/golang/groupcache/blob/41bb18bfe9da/LICENSE,Apache-2.0 61 | github.com/golang/protobuf/proto,https://github.com/golang/protobuf/blob/v1.5.4/LICENSE,BSD-3-Clause 62 | github.com/google/gnostic-models,https://github.com/google/gnostic-models/blob/c7be7c783f49/LICENSE,Apache-2.0 63 | github.com/google/gofuzz,https://github.com/google/gofuzz/blob/v1.2.0/LICENSE,Apache-2.0 64 | github.com/google/s2a-go,https://github.com/google/s2a-go/blob/v0.1.7/LICENSE.md,Apache-2.0 65 | github.com/google/uuid,https://github.com/google/uuid/blob/v1.6.0/LICENSE,BSD-3-Clause 66 | github.com/googleapis/enterprise-certificate-proxy/client,https://github.com/googleapis/enterprise-certificate-proxy/blob/v0.3.2/LICENSE,Apache-2.0 67 | github.com/googleapis/gax-go/v2,https://github.com/googleapis/gax-go/blob/v2.12.5/v2/LICENSE,BSD-3-Clause 68 | github.com/gorilla/websocket,https://github.com/gorilla/websocket/blob/v1.5.3/LICENSE,BSD-2-Clause 69 | github.com/hashicorp/go-cleanhttp,https://github.com/hashicorp/go-cleanhttp/blob/v0.5.2/LICENSE,MPL-2.0 70 | github.com/hashicorp/go-version,https://github.com/hashicorp/go-version/blob/v1.6.0/LICENSE,MPL-2.0 71 | github.com/hashicorp/hc-install,https://github.com/hashicorp/hc-install/blob/v0.4.0/LICENSE,MPL-2.0 72 | github.com/hashicorp/terraform-exec,https://github.com/hashicorp/terraform-exec/blob/v0.17.3/LICENSE,MPL-2.0 73 | github.com/hashicorp/terraform-json,https://github.com/hashicorp/terraform-json/blob/v0.14.0/LICENSE,MPL-2.0 74 | github.com/imdario/mergo,https://github.com/imdario/mergo/blob/v0.3.16/LICENSE,BSD-3-Clause 75 | github.com/jmespath/go-jmespath,https://github.com/jmespath/go-jmespath/blob/v0.4.0/LICENSE,Apache-2.0 76 | github.com/josharian/intern,https://github.com/josharian/intern/blob/v1.0.0/license.md,MIT 77 | github.com/json-iterator/go,https://github.com/json-iterator/go/blob/v1.1.12/LICENSE,MIT 78 | github.com/kylelemons/godebug,https://github.com/kylelemons/godebug/blob/v1.1.0/LICENSE,Apache-2.0 79 | github.com/mailru/easyjson,https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE,MIT 80 | github.com/moby/spdystream,https://github.com/moby/spdystream/blob/v0.4.0/LICENSE,Apache-2.0 81 | github.com/modern-go/concurrent,https://github.com/modern-go/concurrent/blob/bacd9c7ef1dd/LICENSE,Apache-2.0 82 | github.com/modern-go/reflect2,https://github.com/modern-go/reflect2/blob/v1.0.2/LICENSE,Apache-2.0 83 | github.com/munnerz/goautoneg,https://github.com/munnerz/goautoneg/blob/a7dc8b61c822/LICENSE,BSD-3-Clause 84 | github.com/mxk/go-flowrate/flowrate,https://github.com/mxk/go-flowrate/blob/cca7078d478f/LICENSE,BSD-3-Clause 85 | github.com/pkg/browser,https://github.com/pkg/browser/blob/5ac0b6a4141c/LICENSE,BSD-2-Clause 86 | github.com/sirupsen/logrus,https://github.com/sirupsen/logrus/blob/v1.4.1/LICENSE,MIT 87 | github.com/spf13/cobra,https://github.com/spf13/cobra/blob/v1.6.0/LICENSE.txt,Apache-2.0 88 | github.com/spf13/pflag,https://github.com/spf13/pflag/blob/v1.0.5/LICENSE,BSD-3-Clause 89 | github.com/zclconf/go-cty/cty,https://github.com/zclconf/go-cty/blob/v1.11.0/LICENSE,MIT 90 | go.opencensus.io,https://github.com/census-instrumentation/opencensus-go/blob/v0.24.0/LICENSE,Apache-2.0 91 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp,https://github.com/open-telemetry/opentelemetry-go-contrib/blob/instrumentation/net/http/otelhttp/v0.53.0/instrumentation/net/http/otelhttp/LICENSE,Apache-2.0 92 | go.opentelemetry.io/otel,https://github.com/open-telemetry/opentelemetry-go/blob/v1.28.0/LICENSE,Apache-2.0 93 | go.opentelemetry.io/otel/metric,https://github.com/open-telemetry/opentelemetry-go/blob/metric/v1.28.0/metric/LICENSE,Apache-2.0 94 | go.opentelemetry.io/otel/trace,https://github.com/open-telemetry/opentelemetry-go/blob/trace/v1.28.0/trace/LICENSE,Apache-2.0 95 | golang.org/x/crypto,https://cs.opensource.google/go/x/crypto/+/v0.25.0:LICENSE,BSD-3-Clause 96 | golang.org/x/net,https://cs.opensource.google/go/x/net/+/v0.27.0:LICENSE,BSD-3-Clause 97 | golang.org/x/oauth2,https://cs.opensource.google/go/x/oauth2/+/v0.21.0:LICENSE,BSD-3-Clause 98 | golang.org/x/sys/unix,https://cs.opensource.google/go/x/sys/+/v0.22.0:LICENSE,BSD-3-Clause 99 | golang.org/x/term,https://cs.opensource.google/go/x/term/+/v0.22.0:LICENSE,BSD-3-Clause 100 | golang.org/x/text,https://cs.opensource.google/go/x/text/+/v0.16.0:LICENSE,BSD-3-Clause 101 | golang.org/x/time/rate,https://cs.opensource.google/go/x/time/+/v0.5.0:LICENSE,BSD-3-Clause 102 | google.golang.org/api,https://github.com/googleapis/google-api-go-client/blob/v0.187.0/LICENSE,BSD-3-Clause 103 | google.golang.org/api/internal/third_party/uritemplates,https://github.com/googleapis/google-api-go-client/blob/v0.187.0/internal/third_party/uritemplates/LICENSE,BSD-3-Clause 104 | google.golang.org/genproto/googleapis/api,https://github.com/googleapis/go-genproto/blob/4ad9e859172b/googleapis/api/LICENSE,Apache-2.0 105 | google.golang.org/genproto/googleapis/cloud,https://github.com/googleapis/go-genproto/blob/4ad9e859172b/LICENSE,Apache-2.0 106 | google.golang.org/genproto/googleapis/rpc,https://github.com/googleapis/go-genproto/blob/4ad9e859172b/googleapis/rpc/LICENSE,Apache-2.0 107 | google.golang.org/grpc,https://github.com/grpc/grpc-go/blob/v1.65.0/LICENSE,Apache-2.0 108 | google.golang.org/protobuf,https://github.com/protocolbuffers/protobuf-go/blob/v1.34.2/LICENSE,BSD-3-Clause 109 | gopkg.in/inf.v0,https://github.com/go-inf/inf/blob/v0.9.1/LICENSE,BSD-3-Clause 110 | gopkg.in/yaml.v2,https://github.com/go-yaml/yaml/blob/v2.4.0/LICENSE,Apache-2.0 111 | gopkg.in/yaml.v3,https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE,MIT 112 | k8s.io/api,https://github.com/kubernetes/api/blob/v0.30.2/LICENSE,Apache-2.0 113 | k8s.io/apimachinery/pkg,https://github.com/kubernetes/apimachinery/blob/v0.30.2/LICENSE,Apache-2.0 114 | k8s.io/apimachinery/third_party/forked/golang,https://github.com/kubernetes/apimachinery/blob/v0.30.2/third_party/forked/golang/LICENSE,BSD-3-Clause 115 | k8s.io/client-go,https://github.com/kubernetes/client-go/blob/v0.30.2/LICENSE,Apache-2.0 116 | k8s.io/klog/v2,https://github.com/kubernetes/klog/blob/v2.130.1/LICENSE,Apache-2.0 117 | k8s.io/kube-openapi/pkg,https://github.com/kubernetes/kube-openapi/blob/3c01b740850f/LICENSE,Apache-2.0 118 | k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json,https://github.com/kubernetes/kube-openapi/blob/3c01b740850f/pkg/internal/third_party/go-json-experiment/json/LICENSE,BSD-3-Clause 119 | k8s.io/kube-openapi/pkg/validation/spec,https://github.com/kubernetes/kube-openapi/blob/3c01b740850f/pkg/validation/spec/LICENSE,Apache-2.0 120 | k8s.io/utils,https://github.com/kubernetes/utils/blob/fe8a2dddb1d0/LICENSE,Apache-2.0 121 | k8s.io/utils/internal/third_party/forked/golang/net,https://github.com/kubernetes/utils/blob/fe8a2dddb1d0/internal/third_party/forked/golang/LICENSE,BSD-3-Clause 122 | sigs.k8s.io/json,https://github.com/kubernetes-sigs/json/blob/bc3834ca7abd/LICENSE,Apache-2.0 123 | sigs.k8s.io/structured-merge-diff/v4,https://github.com/kubernetes-sigs/structured-merge-diff/blob/v4.4.1/LICENSE,Apache-2.0 124 | sigs.k8s.io/yaml,https://github.com/kubernetes-sigs/yaml/blob/v1.4.0/LICENSE,Apache-2.0 125 | sigs.k8s.io/yaml/goyaml.v2,https://github.com/kubernetes-sigs/yaml/blob/v1.4.0/goyaml.v2/LICENSE,Apache-2.0 126 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= 3 | cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= 4 | cloud.google.com/go/auth v0.6.1 h1:T0Zw1XM5c1GlpN2HYr2s+m3vr1p2wy+8VN+Z1FKxW38= 5 | cloud.google.com/go/auth v0.6.1/go.mod h1:eFHG7zDzbXHKmjJddFG/rBlcGp6t25SwRUiEQSlO4x4= 6 | cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= 7 | cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= 8 | cloud.google.com/go/compute v1.27.2 h1:5cE5hdrwJV/92ravlwIFRGnyH9CpLGhh4N0ZDVTU+BA= 9 | cloud.google.com/go/compute v1.27.2/go.mod h1:YQuHkNEwP3bIz4LBYQqf4DIMfFtTDtnEgnwG0mJQQ9I= 10 | cloud.google.com/go/compute/metadata v0.4.0 h1:vHzJCWaM4g8XIcm8kopr3XmDA4Gy/lblD3EhhSux05c= 11 | cloud.google.com/go/compute/metadata v0.4.0/go.mod h1:SIQh1Kkb4ZJ8zJ874fqVkslA29PRXuleyj6vOzlbK7M= 12 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 h1:1nGuui+4POelzDwI7RG56yfQJHCnKvwfMoU7VsEp+Zg= 13 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0/go.mod h1:99EvauvlcJ1U06amZiksfYz/3aFGyIhWGHVyiZXtBAI= 14 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= 15 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= 16 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.1 h1:Xy/qV1DyOhhqsU/z0PyFMJfYCxnzna+vBEUtFW0ksQo= 17 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.1/go.mod h1:oib6iWdC+sILvNUoJbbBn3xv7TXow7mEp/WRcsYvmow= 18 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v1.0.0 h1:/Di3vB4sNeQ+7A8efjUVENvyB945Wruvstucqp7ZArg= 19 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v1.0.0/go.mod h1:gM3K25LQlsET3QR+4V74zxCsFAy0r6xMNN9n80SZn+4= 20 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.0.0 h1:lMW1lD/17LUA5z1XTURo7LcVG2ICBPlyMHjIUrcFZNQ= 21 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.0.0/go.mod h1:ceIuwmxDWptoW3eCqSXlnPsZFKh4X+R38dWPv7GS9Vs= 22 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0 h1:PTFGRSlMKCQelWwxUyYVEUqseBJVemLyqWJjvMyt0do= 23 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0/go.mod h1:LRr2FzBTQlONPPa5HREE5+RjSCTXl7BwOvYOaWTqCaI= 24 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managementgroups/armmanagementgroups v1.0.0 h1:pPvTJ1dY0sA35JOeFq6TsY2xj6Z85Yo23Pj4wCCvu4o= 25 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managementgroups/armmanagementgroups v1.0.0/go.mod h1:mLfWfj8v3jfWKsL9G4eoBoXVcsqcIUTapmdKy7uGOp0= 26 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.0.0 h1:nBy98uKOIfun5z6wx6jwWLrULcM0+cjBalBFZlEZ7CA= 27 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.0.0/go.mod h1:243D9iHbcQXoFUtgHJwL7gl2zx1aDuDMjvBZVGr2uW0= 28 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM= 29 | github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE= 30 | github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= 31 | github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= 32 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 33 | github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= 34 | github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= 35 | github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU= 36 | github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= 37 | github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= 38 | github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= 39 | github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= 40 | github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= 41 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= 42 | github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= 43 | github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= 44 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 45 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 46 | github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= 47 | github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= 48 | github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg= 49 | github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM= 50 | github.com/aws/aws-sdk-go-v2/config v1.27.24 h1:NM9XicZ5o1CBU/MZaHwFtimRpWx9ohAUAqkG6AqSqPo= 51 | github.com/aws/aws-sdk-go-v2/config v1.27.24/go.mod h1:aXzi6QJTuQRVVusAO8/NxpdTeTyr/wRcybdDtfUwJSs= 52 | github.com/aws/aws-sdk-go-v2/credentials v1.17.24 h1:YclAsrnb1/GTQNt2nzv+756Iw4mF8AOzcDfweWwwm/M= 53 | github.com/aws/aws-sdk-go-v2/credentials v1.17.24/go.mod h1:Hld7tmnAkoBQdTMNYZGzztzKRdA4fCdn9L83LOoigac= 54 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 h1:Aznqksmd6Rfv2HQN9cpqIV/lQRMaIpJkLLaJ1ZI76no= 55 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9/go.mod h1:WQr3MY7AxGNxaqAtsDWn+fBxmd4XvLkzeqQ8P1VM0/w= 56 | github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.5 h1:qkipTyOc+ElVS+TgGJCf/6gqu0CL5+ii19W/eMQfY94= 57 | github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.5/go.mod h1:UjB35RXl+ESpnVtyaKqdw11NhMxm90lF9o2zqJNbi14= 58 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 h1:5SAoZ4jYpGH4721ZNoS1znQrhOfZinOhc4XuTXx/nVc= 59 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13/go.mod h1:+rdA6ZLpaSeM7tSg/B0IEDinCIBJGmW8rKDFkYpP04g= 60 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 h1:WIijqeaAO7TYFLbhsZmi2rgLEAtWOC1LhxCAVTJlSKw= 61 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13/go.mod h1:i+kbfa76PQbWw/ULoWnp51EYVWH4ENln76fLQE3lXT8= 62 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= 63 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= 64 | github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.13 h1:THZJJ6TU/FOiM7DZFnisYV9d49oxXWUzsVIMTuf3VNU= 65 | github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.13/go.mod h1:VISUTg6n+uBaYIWPBaIG0jk7mbBxm7DUqBtU2cUDDWI= 66 | github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.42.1 h1:/gUEverppQJMUtU8hQC4Zx8NY445jOEO0CVZWxFDQRg= 67 | github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.42.1/go.mod h1:TBznJbgNAX89kg01mXYIQP9xiCN4ydoyoSgPIh+XfZo= 68 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.168.0 h1:xOPq0agGC1WMZvFpSZCKEjDVAQnLPZJZGvjuPVF2t9M= 69 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.168.0/go.mod h1:CtLD6CPq9z9dyMxV+H6/M5d9+/ea3dO80um029GXqV0= 70 | github.com/aws/aws-sdk-go-v2/service/ec2instanceconnect v1.25.1 h1:W+NOMh1ETsWgqXPjLVoD1mObnamxDIPkOSOPQWSDrDM= 71 | github.com/aws/aws-sdk-go-v2/service/ec2instanceconnect v1.25.1/go.mod h1:qaboM4VqzpuB+d8V7EGUGohXDR6dpCR7qwRSvKeCOnQ= 72 | github.com/aws/aws-sdk-go-v2/service/eks v1.46.0 h1:ZPhHHZtAjVohIGIVjXECPfljcPOQ+hjZ1IpgvjPTJ50= 73 | github.com/aws/aws-sdk-go-v2/service/eks v1.46.0/go.mod h1:p4Yk0zfWEoLvvQ4V6XZrTmAAPzcevNnEsbUR82NAY0w= 74 | github.com/aws/aws-sdk-go-v2/service/iam v1.34.1 h1:BzAfH/XAECH4P7toscHvBbyw9zuaEMT8gzEo40BaLDs= 75 | github.com/aws/aws-sdk-go-v2/service/iam v1.34.1/go.mod h1:gCfCySFdW8/FaTC6jzPwmML5bOUGty9Eq/+SU2PFv0M= 76 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= 77 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= 78 | github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.15 h1:2jyRZ9rVIMisyQRnhSS/SqlckveoxXneIumECVFP91Y= 79 | github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.15/go.mod h1:bDRG3m382v1KJBk1cKz7wIajg87/61EiiymEyfLvAe0= 80 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 h1:I9zMeF107l0rJrpnHpjEiiTSCKYAIw8mALiXcPsGBiA= 81 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15/go.mod h1:9xWJ3Q/S6Ojusz1UIkfycgD1mGirJfLLKqq3LPT7WN8= 82 | github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.13 h1:Eq2THzHt6P41mpjS2sUzz/3dJYFRqdWZ+vQaEMm98EM= 83 | github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.13/go.mod h1:FgwTca6puegxgCInYwGjmd4tB9195Dd6LCuA+8MjpWw= 84 | github.com/aws/aws-sdk-go-v2/service/lambda v1.56.1 h1:d8ff+JrsS+nSjQK1/F8xPgBl/DeVIOzKbT4ElArntlA= 85 | github.com/aws/aws-sdk-go-v2/service/lambda v1.56.1/go.mod h1:+DUS8jDnu671W48h4+Hl6xnNeRiz+TuycnxGz2RCTGg= 86 | github.com/aws/aws-sdk-go-v2/service/organizations v1.30.0 h1:UBZxMASlLL9NmxzeqvMFYgavnXR8dqY8PQPnBURc//0= 87 | github.com/aws/aws-sdk-go-v2/service/organizations v1.30.0/go.mod h1:ZEeCMh1nNfDZyRfCCoeAvqPNSQSF2bcWK5sOgmclQTM= 88 | github.com/aws/aws-sdk-go-v2/service/rds v1.81.2 h1:8hS1TW26euZk4OZtZEYFDLGJ+8MDFRjtKLUBdLFbB64= 89 | github.com/aws/aws-sdk-go-v2/service/rds v1.81.2/go.mod h1:EuF7sZqyUlv+NPw1x2hz0XZDrKfqnSh0qHA84xJ3Unw= 90 | github.com/aws/aws-sdk-go-v2/service/rolesanywhere v1.13.1 h1:2p65lTZ1OGnAGdDsMGFolNT8v0RAr2pF5eAo0jhgSlA= 91 | github.com/aws/aws-sdk-go-v2/service/rolesanywhere v1.13.1/go.mod h1:43wn4yPVFL3PHXixCOGzLb8LwWJovqlFQz3qGOAkcYY= 92 | github.com/aws/aws-sdk-go-v2/service/route53resolver v1.30.1 h1:ePMC/GW/pjgfbdoweRf9OgCtD6RlRILHfhF6NAQRNhk= 93 | github.com/aws/aws-sdk-go-v2/service/route53resolver v1.30.1/go.mod h1:PNuCbPYu0tyIwcTzXfDOg5IJfJNOGca2dOptibO/ccs= 94 | github.com/aws/aws-sdk-go-v2/service/s3 v1.58.0 h1:4rhV0Hn+bf8IAIUphRX1moBcEvKJipCPmswMCl6Q5mw= 95 | github.com/aws/aws-sdk-go-v2/service/s3 v1.58.0/go.mod h1:hdV0NTYd0RwV4FvNKhKUNbPLZoq9CTr/lke+3I7aCAI= 96 | github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.1 h1:ZoYRD8IJqPkzjBnpokiMNO6L/DQprtpVpD6k0YSaF5U= 97 | github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.1/go.mod h1:GlRarZzIMl9VDi0mLQt+qQOuEkVFPnTkkjyugV1uVa8= 98 | github.com/aws/aws-sdk-go-v2/service/ses v1.25.0 h1:A83o8Hw+S84rok4yKGulPy6PRtn3UThFGjVUYC6cxjI= 99 | github.com/aws/aws-sdk-go-v2/service/ses v1.25.0/go.mod h1:C/10O+XZpn60R+YV/3EPdM+S21dwqoeJXclEtUyu4aw= 100 | github.com/aws/aws-sdk-go-v2/service/ssm v1.52.1 h1:zeWJA3f0Td70984ZoSocVAEwVtZBGQu+Q0p/pA7dNoE= 101 | github.com/aws/aws-sdk-go-v2/service/ssm v1.52.1/go.mod h1:xvWzNAXicm5A+1iOiH4sqMLwYHEbiQqpRSe6hvHdQrE= 102 | github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 h1:p1GahKIjyMDZtiKoIn0/jAj/TkMzfzndDv5+zi2Mhgc= 103 | github.com/aws/aws-sdk-go-v2/service/sso v1.22.1/go.mod h1:/vWdhoIoYA5hYoPZ6fm7Sv4d8701PiG5VKe8/pPJL60= 104 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 h1:ORnrOK0C4WmYV/uYt3koHEWBLYsRDwk2Np+eEoyV4Z0= 105 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2/go.mod h1:xyFHA4zGxgYkdD73VeezHt3vSKEG9EmFnGwoKlP00u4= 106 | github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 h1:+woJ607dllHJQtsnJLi52ycuqHMwlW+Wqm2Ppsfp4nQ= 107 | github.com/aws/aws-sdk-go-v2/service/sts v1.30.1/go.mod h1:jiNR3JqT15Dm+QWq2SRgh0x0bCNSRP2L25+CqPNpJlQ= 108 | github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= 109 | github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= 110 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 111 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 112 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 113 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 114 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 115 | github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ= 116 | github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= 117 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 118 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 119 | github.com/datadog/stratus-red-team/v2 v2.16.0 h1:fqLC8oNlB0oUOOiGXhIw2kie3G9cqRm/lTOu9wy63TU= 120 | github.com/datadog/stratus-red-team/v2 v2.16.0/go.mod h1:l7GMy17s6SMoZLAlQsudsKBsarRH7gp939fy/JVJnMI= 121 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 122 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 123 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 124 | github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= 125 | github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 126 | github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= 127 | github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= 128 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 129 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 130 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 131 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 132 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 133 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 134 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= 135 | github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= 136 | github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= 137 | github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= 138 | github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= 139 | github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= 140 | github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= 141 | github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= 142 | github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= 143 | github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= 144 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 145 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 146 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 147 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 148 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 149 | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 150 | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 151 | github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= 152 | github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= 153 | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 154 | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 155 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 156 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 157 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 158 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 159 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 160 | github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= 161 | github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= 162 | github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= 163 | github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 164 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 165 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 166 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 167 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 168 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 169 | github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 170 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 171 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 172 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 173 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 174 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 175 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 176 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 177 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 178 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 179 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 180 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 181 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 182 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 183 | github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= 184 | github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= 185 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 186 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 187 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 188 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 189 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 190 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 191 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 192 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 193 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 194 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 195 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 196 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 197 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 198 | github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= 199 | github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= 200 | github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= 201 | github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= 202 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 203 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 204 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 205 | github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= 206 | github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= 207 | github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA= 208 | github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= 209 | github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= 210 | github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 211 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 212 | github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= 213 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 214 | github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 215 | github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 216 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 217 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 218 | github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 219 | github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= 220 | github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 221 | github.com/hashicorp/hc-install v0.4.0 h1:cZkRFr1WVa0Ty6x5fTvL1TuO1flul231rWkGH92oYYk= 222 | github.com/hashicorp/hc-install v0.4.0/go.mod h1:5d155H8EC5ewegao9A4PUTMNPZaq+TbOzkJJZ4vrXeI= 223 | github.com/hashicorp/terraform-exec v0.17.3 h1:MX14Kvnka/oWGmIkyuyvL6POx25ZmKrjlaclkx3eErU= 224 | github.com/hashicorp/terraform-exec v0.17.3/go.mod h1:+NELG0EqQekJzhvikkeQsOAZpsw0cv/03rbeQJqscAI= 225 | github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e17dKDpqV7s= 226 | github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM= 227 | github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 228 | github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= 229 | github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= 230 | github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= 231 | github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 232 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 233 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 234 | github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= 235 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 236 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 237 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 238 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 239 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 240 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 241 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 242 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 243 | github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= 244 | github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 245 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 246 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 247 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 248 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 249 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 250 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 251 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 252 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 253 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 254 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 255 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 256 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 257 | github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= 258 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 259 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 260 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 261 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 262 | github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= 263 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 264 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 265 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 266 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 267 | github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= 268 | github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= 269 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 270 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 271 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 272 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 273 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 274 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 275 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 276 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= 277 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 278 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 279 | github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= 280 | github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= 281 | github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= 282 | github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= 283 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= 284 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= 285 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 286 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 287 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 288 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 289 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 290 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 291 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 292 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 293 | github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= 294 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 295 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= 296 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 297 | github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= 298 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 299 | github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= 300 | github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= 301 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 302 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 303 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 304 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 305 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 306 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 307 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 308 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 309 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 310 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 311 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 312 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 313 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 314 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 315 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 316 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 317 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 318 | github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= 319 | github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= 320 | github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= 321 | github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= 322 | github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= 323 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 324 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 325 | github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= 326 | github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= 327 | github.com/zclconf/go-cty v1.11.0 h1:726SxLdi2SDnjY+BStqB9J1hNp4+2WlzyXLuimibIe0= 328 | github.com/zclconf/go-cty v1.11.0/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= 329 | github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= 330 | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= 331 | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 332 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= 333 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= 334 | go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= 335 | go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= 336 | go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= 337 | go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= 338 | go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= 339 | go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= 340 | go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= 341 | go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= 342 | golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 343 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 344 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 345 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 346 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 347 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 348 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 349 | golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= 350 | golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= 351 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 352 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 353 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 354 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 355 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 356 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 357 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 358 | golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 359 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 360 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 361 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 362 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 363 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 364 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 365 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 366 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 367 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 368 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 369 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 370 | golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= 371 | golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= 372 | golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= 373 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 374 | golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= 375 | golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 376 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 377 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 378 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 379 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 380 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 381 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 382 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 383 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 384 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 385 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 386 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 387 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 388 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 389 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 390 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 391 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 392 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 393 | golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 394 | golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 395 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 396 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 397 | golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 398 | golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 399 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 400 | golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= 401 | golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= 402 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 403 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 404 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 405 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 406 | golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= 407 | golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 408 | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= 409 | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 410 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 411 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 412 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 413 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 414 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 415 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 416 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 417 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 418 | golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= 419 | golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= 420 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 421 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 422 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 423 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 424 | google.golang.org/api v0.187.0 h1:Mxs7VATVC2v7CY+7Xwm4ndkX71hpElcvx0D1Ji/p1eo= 425 | google.golang.org/api v0.187.0/go.mod h1:KIHlTc4x7N7gKKuVsdmfBXN13yEEWXWFURWY6SBp2gk= 426 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 427 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 428 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 429 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 430 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 431 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 432 | google.golang.org/genproto v0.0.0-20240708141625-4ad9e859172b h1:dSTjko30weBaMj3eERKc0ZVXW4GudCswM3m+P++ukU0= 433 | google.golang.org/genproto v0.0.0-20240708141625-4ad9e859172b/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY= 434 | google.golang.org/genproto/googleapis/api v0.0.0-20240708141625-4ad9e859172b h1:y/kpOWeX2pWERnbsvh/hF+Zmo69wVmjyZhstreXQQeA= 435 | google.golang.org/genproto/googleapis/api v0.0.0-20240708141625-4ad9e859172b/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= 436 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b h1:04+jVzTs2XBnOZcPsLnmrTGqltqJbZQ1Ey26hjYdQQ0= 437 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= 438 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 439 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 440 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 441 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 442 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 443 | google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= 444 | google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= 445 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 446 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 447 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 448 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 449 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 450 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 451 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 452 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 453 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 454 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 455 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 456 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 457 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 458 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 459 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 460 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 461 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 462 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 463 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 464 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 465 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 466 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 467 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 468 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 469 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 470 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 471 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 472 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 473 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 474 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 475 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 476 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 477 | k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI= 478 | k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI= 479 | k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg= 480 | k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= 481 | k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50= 482 | k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs= 483 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 484 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 485 | k8s.io/kube-openapi v0.0.0-20240709000822-3c01b740850f h1:2sXuKesAYbRHxL3aE2PN6zX/gcJr22cjrsej+W784Tc= 486 | k8s.io/kube-openapi v0.0.0-20240709000822-3c01b740850f/go.mod h1:UxDHUPsUwTOOxSU+oXURfFBcAS6JwiRXTYqYwfuGowc= 487 | k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= 488 | k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 489 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 490 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 491 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= 492 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= 493 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 494 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 495 | --------------------------------------------------------------------------------