mirror of
https://github.com/BishopFox/cloudfox.git
synced 2026-11-07 17:05:53 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75a446764d | ||
|
|
9be3792c97 | ||
|
|
27a511073e | ||
|
|
a9a0c7ae30 | ||
|
|
2ed079686d | ||
|
|
5e1ed54a28 | ||
|
|
ff38b2cf65 | ||
|
|
1819b11c5f | ||
|
|
f51c1e9f49 | ||
|
|
8f5a571ef0 | ||
|
|
7b95a05215 | ||
|
|
e0f0627de8 | ||
|
|
d505bc4e3e | ||
|
|
6a6f7586b3 | ||
|
|
06344b9421 | ||
|
|
54f784903a | ||
|
|
09f33f0908 | ||
|
|
2dc20eaa52 | ||
|
|
7656267367 | ||
|
|
b43d6ad074 | ||
|
|
1215a27e19 | ||
|
|
7d3a61afe2 | ||
|
|
909590860d | ||
|
|
4ee0da839d | ||
|
|
57d9e911f7 | ||
|
|
dad2277942 | ||
|
|
50493e6624 | ||
|
|
818c3a88f4 | ||
|
|
bcfa318b31 | ||
|
|
f9567c7fc2 | ||
|
|
187299ae82 | ||
|
|
8d145b135e | ||
|
|
729c59e198 | ||
|
|
6d1b47128e | ||
|
|
217ac7a018 | ||
|
|
4b0238c362 | ||
|
|
f78f1490d2 | ||
|
|
0159638ab7 | ||
|
|
1ff35a72ad | ||
|
|
29bfd2c863 | ||
|
|
40e0c92ef1 | ||
|
|
9f8ae08262 | ||
|
|
c85aa5de89 | ||
|
|
42d7cfef1e | ||
|
|
a5bce5ad8c | ||
|
|
8221b5ff6d | ||
|
|
aa42c8b808 | ||
|
|
5aab3b4bea | ||
|
|
01d320d64d | ||
|
|
5132622a13 | ||
|
|
08b5cd17af | ||
|
|
102b91e2c7 | ||
|
|
3a19fdb2fe | ||
|
|
d94fafe2b4 | ||
|
|
00ed8a7336 | ||
|
|
60daff386c | ||
|
|
cef1aa3a82 | ||
|
|
e88cb77394 | ||
|
|
fc52e01192 | ||
|
|
659b2e7248 | ||
|
|
06a7e7ec28 | ||
|
|
602332a0c5 | ||
|
|
471da59da2 |
No files matched your search
@@ -15,8 +15,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
|
||||
- name: Go 1.20
|
||||
uses: actions/setup-go@v2
|
||||
- name: Go 1.21
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ^1.20
|
||||
id: go
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
gpg --list-secret-keys --keyid-format LONG
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# This Action uses minimal steps to run in ~5 seconds to rapidly:
|
||||
# Looks for typos in the codebase using codespell
|
||||
# https://github.com/codespell-project/codespell#readme
|
||||
name: codespell
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
jobs:
|
||||
codespell:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: pip install --user codespell
|
||||
- run: codespell --ignore-words-list="aks" # --skip="*.css,*.js,*.lock,*.po"
|
||||
+2
-1
@@ -69,4 +69,5 @@ cloudfox
|
||||
*.txt
|
||||
*.json
|
||||
*.csv
|
||||
*.log
|
||||
*.log
|
||||
dist/
|
||||
@@ -1,20 +1,20 @@
|
||||
run:
|
||||
/home/marauder/Downloads/go/bin/go run main.go
|
||||
go run main.go
|
||||
|
||||
windows:
|
||||
GOOS=windows GOARCH=386 /home/marauder/Downloads/go/bin/go build
|
||||
GOOS=windows GOARCH=386 go build
|
||||
sha1sum cloudfox.exe > sha1sum-win.txt
|
||||
zip cloudfox-win.zip cloudfox.exe sha1sum-win.txt
|
||||
rm cloudfox.exe sha1sum-win.txt
|
||||
|
||||
linux:
|
||||
GOOS=linux GOARCH=386 /home/marauder/Downloads/go/bin/go build
|
||||
GOOS=linux GOARCH=386 go build
|
||||
sha1sum cloudfox > sha1sum-linux.txt
|
||||
zip cloudfox-linux.zip cloudfox sha1sum-linux.txt
|
||||
rm cloudfox sha1sum-linux.txt
|
||||
|
||||
macos:
|
||||
GOOS=darwin GOARCH=amd64 /home/marauder/Downloads/go/bin/go build
|
||||
GOOS=darwin GOARCH=amd64 go build
|
||||
sha1sum cloudfox > sha1sum-mac.txt
|
||||
zip cloudfox-mac.zip cloudfox sha1sum-mac.txt
|
||||
rm cloudfox sha1sum-mac.txt
|
||||
@@ -25,21 +25,21 @@ all: windows linux macos
|
||||
release: clean
|
||||
mkdir -p ./cloudfox
|
||||
mkdir -p ./cloudfox
|
||||
GOOS=windows GOARCH=amd64 /home/marauder/Downloads/go/bin/go build -o ./cloudfox/cloudfox.exe .
|
||||
GOOS=windows GOARCH=amd64 go build -o ./cloudfox/cloudfox.exe .
|
||||
zip ./cloudfox/cloudfox-windows-amd64.zip ./cloudfox/cloudfox.exe
|
||||
rm -rf ./cloudfox/cloudfox.exe
|
||||
|
||||
GOOS=linux GOARCH=amd64 /home/marauder/Downloads/go/bin/go build -o ./cloudfox/cloudfox .
|
||||
GOOS=linux GOARCH=amd64 go build -o ./cloudfox/cloudfox .
|
||||
zip ./cloudfox/cloudfox-linux-amd64.zip ./cloudfox/cloudfox .
|
||||
rm -rf ./cloudfox/cloudfox
|
||||
|
||||
GOOS=darwin GOARCH=amd64 /home/marauder/Downloads/go/bin/go build -o ./cloudfox/cloudfox .
|
||||
GOOS=darwin GOARCH=amd64 go build -o ./cloudfox/cloudfox .
|
||||
zip ./cloudfox/cloudfox-macos-amd64.zip ./cloudfox/cloudfox
|
||||
rm -rf ./cloudfox/cloudfox
|
||||
|
||||
GOOS=darwin GOARCH=arm64 /home/marauder/Downloads/go/bin/go build -o ./cloudfox/cloudfox .
|
||||
GOOS=darwin GOARCH=arm64 go build -o ./cloudfox/cloudfox .
|
||||
zip ./cloudfox/cloudfox-macos-arm64.zip ./cloudfox/cloudfox
|
||||
rm -rf ./cloudfox/cloudfox
|
||||
|
||||
clean:
|
||||
rm -rf ./cloudfox
|
||||
rm -rf ./cloudfox
|
||||
@@ -14,8 +14,12 @@ CloudFox helps you gain situational awareness in unfamiliar cloud environments.
|
||||
|
||||
## Demos, Examples, Walkthroughs
|
||||
* [Blog - Introducing: CloudFox](https://bishopfox.com/blog/introducing-cloudfox)
|
||||
* [Video - Penetration Testing with CloudFox](https://www.youtube.com/watch?v=Ljt_JUp5HbM)
|
||||
* [Video - CloudFox Intro Demos](https://www.youtube.com/watch?v=ReWoUgpUuiQ)
|
||||
* [Video - Tool Talk: CloudFox AWS sub-command walkthroughs](https://youtu.be/KKsYfL5uVU4?t=360)
|
||||
* [Video - Tool Talk: CloudFox AWS sub-command walkthroughs](https://youtu.be/KKsYfL5uVU4?t=360)
|
||||
|
||||
## Intentionally Vulnerable Playground
|
||||
* [CloudFoxable - A Gamified Cloud Hacking Sandbox](https://cloudfoxable.bishopfox.com/)
|
||||
|
||||
## Quick Start
|
||||
CloudFox is modular (you can run one command at a time), but there is an `aws all-checks` command that will run the other aws commands for you with sane defaults:
|
||||
@@ -41,7 +45,7 @@ For the full documentation please refer to our [wiki](https://github.com/BishopF
|
||||
| - | - |
|
||||
| AWS | 30 |
|
||||
| Azure | 4 |
|
||||
| GCP | 4 |
|
||||
| GCP | Support Planned |
|
||||
| Kubernetes | Support Planned |
|
||||
|
||||
|
||||
@@ -49,11 +53,18 @@ For the full documentation please refer to our [wiki](https://github.com/BishopF
|
||||
|
||||
**Option 1:** Download the [latest binary release](https://github.com/BishopFox/cloudfox/releases) for your platform.
|
||||
|
||||
**Option 2:** [Install Go](https://golang.org/doc/install), clone the CloudFox repository and compile from source
|
||||
**Option 2:** If you use homebrew: `brew install cloudfox`
|
||||
|
||||
**Option 3:** [Install Go](https://golang.org/doc/install), use `go install github.com/BishopFox/cloudfox@latest` to install from the remote source
|
||||
|
||||
**Option 4:** Developer mode:
|
||||
|
||||
[Install Go](https://golang.org/doc/install), clone the CloudFox repository and compile from source
|
||||
```
|
||||
# git clone https://github.com/BishopFox/cloudfox.git
|
||||
...omitted for brevity...
|
||||
# cd ./cloudfox
|
||||
# Make any changes necessary
|
||||
# go build .
|
||||
# ./cloudfox
|
||||
```
|
||||
@@ -64,7 +75,7 @@ For the full documentation please refer to our [wiki](https://github.com/BishopF
|
||||
### AWS
|
||||
* AWS CLI installed
|
||||
* Supports AWS profiles, AWS environment variables, or metadata retrieval (on an ec2 instance)
|
||||
* To run commands on multiple profiles at once, you can specify the path to a file with a list of profile names seperated by a new line using the `-l` flag or pass all stored profiles with the `-a` flag.
|
||||
* To run commands on multiple profiles at once, you can specify the path to a file with a list of profile names separated by a new line using the `-l` flag or pass all stored profiles with the `-a` flag.
|
||||
* A principal with one recommended policies attached (described below)
|
||||
* Recommended attached policies: **`SecurityAudit` + [CloudFox custom policy](./misc/aws/cloudfox-policy.json)**
|
||||
|
||||
@@ -79,12 +90,7 @@ Additional policy notes (as of 09/2022):
|
||||
| `arn:aws:iam::aws:policy/AdministratorAccess` | This will work just fine with CloudFox, but if you were handed this level of access as a penetration tester, that should probably be a finding in itself :) |
|
||||
|
||||
### Azure
|
||||
* Viewer or similar permissions applied.
|
||||
|
||||
### GCP
|
||||
* `gcloud` CLI installed
|
||||
* Proper SDK credentials available (`gcloud auth application-default login`)
|
||||
* Viewer or similar permissions applied.
|
||||
* Viewer or similar permissions applied.
|
||||
|
||||
# AWS Commands
|
||||
| Provider | Command Name | Description
|
||||
@@ -127,14 +133,6 @@ Additional policy notes (as of 09/2022):
|
||||
| Azure | [rbac](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#rbac) | Lists Azure RBAC role assignments at subscription or tenant level |
|
||||
| Azure | [storage](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#storage) | The storage command is still under development. Currently it only displays limited data about the storage accounts |
|
||||
|
||||
# GCP Commands
|
||||
| Provider | Command Name | Description
|
||||
| - | - | - |
|
||||
| GCP | [access-tokens](https://github.com/BishopFox/cloudfox/wiki/GCP-Commands#access-tokens) | Displays handy information about existing GCP credentials on your local machine |
|
||||
| GCP | [hierarchy](https://github.com/BishopFox/cloudfox/wiki/GCP-Commands#hierarchy) Displays information on the projects available to your current Gcloud SDK credentials. This is useful to provide situation awareness on what organizations, folders and projects to use with the other sub commands.|
|
||||
| GCP | [inventory](https://github.com/BishopFox/cloudfox/wiki/GCP-Commands#inventory) Gain a rough understanding of size of the available organizations |
|
||||
| GCP | [buckets](https://github.com/BishopFox/cloudfox/wiki/GCP-Commands#buckets) Lists the buckets in the available organizations and gives you handy commands for inspecting them further. |
|
||||
|
||||
# Authors
|
||||
* [Carlos Vendramini](https://github.com/carlosvendramini-bf)
|
||||
* [Seth Art (@sethsec](https://twitter.com/sethsec))
|
||||
@@ -151,7 +149,7 @@ CloudFox doesn't create any alerts or findings, and doesn't check your environme
|
||||
|
||||
**Why do I see errors in some CloudFox commands?**
|
||||
|
||||
* Services that don't exist in all regions - CloudFox tries a few ways to figure out what services are supported in each region. However some services don't support the methods CloudFox uses, so CloudFox defaults to just asking every region about the service. Regions that don't suppor the service will return errors.
|
||||
* Services that don't exist in all regions - CloudFox tries a few ways to figure out what services are supported in each region. However some services don't support the methods CloudFox uses, so CloudFox defaults to just asking every region about the service. Regions that don't support the service will return errors.
|
||||
* You don't have permission - Another reason you might see errors if you don't have permissions to make calls that CloudFox is making. Either because the policy doesn't allow it (e.g., SecurityAudit doesn't allow all of the permissions CloudFox needs. Or, it might be an SCP that is blocking you.
|
||||
|
||||
You can always look in the ~/.cloudfox/cloudfox-error.log file to get more information on errors.
|
||||
|
||||
+37
-9
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
@@ -18,9 +19,10 @@ type AccessKeysModule struct {
|
||||
IAMClient sdk.AWSIAMClientInterface
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSProfile string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
WrapTable bool
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
CommandCounter internal.CommandCounter
|
||||
|
||||
// Main module data
|
||||
@@ -36,8 +38,8 @@ type UserKeys struct {
|
||||
Key string
|
||||
}
|
||||
|
||||
func (m *AccessKeysModule) PrintAccessKeys(filter string, outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These stuct values are used by the output module
|
||||
func (m *AccessKeysModule) PrintAccessKeys(filter string, outputDirectory string, verbosity int) {
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "access-keys"
|
||||
@@ -55,6 +57,7 @@ func (m *AccessKeysModule) PrintAccessKeys(filter string, outputFormat string, o
|
||||
|
||||
// Variables used to draw table output
|
||||
m.output.Headers = []string{
|
||||
"Account",
|
||||
"User Name",
|
||||
"Access Key ID",
|
||||
}
|
||||
@@ -65,6 +68,7 @@ func (m *AccessKeysModule) PrintAccessKeys(filter string, outputFormat string, o
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
key.Username,
|
||||
key.Key,
|
||||
},
|
||||
@@ -79,9 +83,6 @@ func (m *AccessKeysModule) PrintAccessKeys(filter string, outputFormat string, o
|
||||
//fmt.Printf("[%s][%s] Preparing output.\n\n")
|
||||
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//m.output.OutputSelector(outputFormat)
|
||||
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
|
||||
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -89,10 +90,37 @@ func (m *AccessKeysModule) PrintAccessKeys(filter string, outputFormat string, o
|
||||
Wrap: m.WrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"User Name",
|
||||
"Access Key ID",
|
||||
}
|
||||
// Otherwise, use the default columns.
|
||||
} else {
|
||||
tableCols = []string{
|
||||
"User Name",
|
||||
"Access Key ID",
|
||||
}
|
||||
}
|
||||
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
|
||||
+91
-45
@@ -20,24 +20,26 @@ import (
|
||||
type BucketsModule struct {
|
||||
// General configuration data
|
||||
//BucketsS3Client CloudFoxS3Client
|
||||
S3Client sdk.AWSS3ClientInterface
|
||||
AWSRegions []string
|
||||
AWSProfile string
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
CheckBucketPolicies bool
|
||||
S3Client sdk.AWSS3ClientInterface
|
||||
AWSRegions []string
|
||||
AWSProfile string
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSTableCols string
|
||||
AWSOutputType string
|
||||
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
WrapTable bool
|
||||
Goroutines int
|
||||
WrapTable bool
|
||||
|
||||
// Main module data
|
||||
Buckets []Bucket
|
||||
Buckets []BucketRow
|
||||
CommandCounter internal.CommandCounter
|
||||
// Used to store output data for pretty printing
|
||||
output internal.OutputData2
|
||||
modLog *logrus.Entry
|
||||
}
|
||||
|
||||
type Bucket struct {
|
||||
type BucketRow struct {
|
||||
Arn string
|
||||
AWSService string
|
||||
Region string
|
||||
@@ -53,8 +55,8 @@ type Bucket struct {
|
||||
ResourcePolicySummary string
|
||||
}
|
||||
|
||||
func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These stuct values are used by the output module
|
||||
func (m *BucketsModule) PrintBuckets(outputDirectory string, verbosity int) {
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "buckets"
|
||||
@@ -78,7 +80,7 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
|
||||
go internal.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "tasks")
|
||||
|
||||
//create a channel to receive the objects
|
||||
dataReceiver := make(chan Bucket)
|
||||
dataReceiver := make(chan BucketRow)
|
||||
|
||||
// Create a channel to signal to stop
|
||||
receiverDone := make(chan bool)
|
||||
@@ -100,6 +102,7 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
|
||||
|
||||
// add - if struct is not empty do this. otherwise, dont write anything.
|
||||
m.output.Headers = []string{
|
||||
"Account",
|
||||
"Name",
|
||||
"Region",
|
||||
"Public?",
|
||||
@@ -111,17 +114,16 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
m.Buckets[i].Name,
|
||||
m.Buckets[i].Region,
|
||||
m.Buckets[i].IsPublic,
|
||||
m.Buckets[i].ResourcePolicySummary,
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
if len(m.output.Body) > 0 {
|
||||
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
//m.writeLoot(m.output.FilePath, verbosity, m.AWSProfile)
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -129,10 +131,47 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
|
||||
Wrap: m.WrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"Name",
|
||||
"Region",
|
||||
"Public?",
|
||||
"Resource Policy Summary",
|
||||
}
|
||||
// Otherwise, use the default columns for this module (brief)
|
||||
} else {
|
||||
tableCols = []string{
|
||||
"Name",
|
||||
"Region",
|
||||
"Public?",
|
||||
"Resource Policy Summary",
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the Public? and Resource Policy Summary columns if the user did not specify CheckBucketPolicies
|
||||
if !m.CheckBucketPolicies {
|
||||
tableCols = removeStringFromSlice(tableCols, "Public?")
|
||||
tableCols = removeStringFromSlice(tableCols, "Resource Policy Summary")
|
||||
}
|
||||
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
TableCols: tableCols,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
@@ -148,7 +187,7 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
|
||||
fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
|
||||
}
|
||||
|
||||
func (m *BucketsModule) Receiver(receiver chan Bucket, receiverDone chan bool) {
|
||||
func (m *BucketsModule) Receiver(receiver chan BucketRow, receiverDone chan bool) {
|
||||
defer close(receiverDone)
|
||||
for {
|
||||
select {
|
||||
@@ -161,7 +200,7 @@ func (m *BucketsModule) Receiver(receiver chan Bucket, receiverDone chan bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *BucketsModule) executeChecks(wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Bucket) {
|
||||
func (m *BucketsModule) executeChecks(wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan BucketRow) {
|
||||
defer wg.Done()
|
||||
|
||||
m.CommandCounter.Total++
|
||||
@@ -215,7 +254,7 @@ func (m *BucketsModule) writeLoot(outputDirectory string, verbosity int, profile
|
||||
|
||||
}
|
||||
|
||||
func (m *BucketsModule) createBucketsRows(verbosity int, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Bucket) {
|
||||
func (m *BucketsModule) createBucketsRows(verbosity int, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan BucketRow) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
@@ -237,42 +276,49 @@ func (m *BucketsModule) createBucketsRows(verbosity int, wg *sync.WaitGroup, sem
|
||||
}
|
||||
|
||||
for _, b := range ListBuckets {
|
||||
bucket := &Bucket{
|
||||
bucket := &BucketRow{
|
||||
Name: aws.ToString(b.Name),
|
||||
AWSService: "S3",
|
||||
}
|
||||
region, err = sdk.CachedGetBucketLocation(m.S3Client, aws.ToString(m.Caller.Account), aws.ToString(b.Name))
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
region = "Unknown"
|
||||
|
||||
}
|
||||
bucket.Region = region
|
||||
|
||||
policyJSON, err := sdk.CachedGetBucketPolicy(m.S3Client, aws.ToString(m.Caller.Account), region, aws.ToString(b.Name))
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
} else {
|
||||
bucket.PolicyJSON = policyJSON
|
||||
}
|
||||
if m.CheckBucketPolicies {
|
||||
|
||||
policy, err := policy.ParseJSONPolicy([]byte(policyJSON))
|
||||
if err != nil {
|
||||
m.modLog.Error(fmt.Sprintf("parsing bucket access policy (%s) as JSON: %s", name, err))
|
||||
} else {
|
||||
bucket.Policy = policy
|
||||
}
|
||||
policyJSON, err := sdk.CachedGetBucketPolicy(m.S3Client, aws.ToString(m.Caller.Account), region, aws.ToString(b.Name))
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
} else {
|
||||
bucket.PolicyJSON = policyJSON
|
||||
}
|
||||
|
||||
bucket.IsPublic = "No"
|
||||
if !bucket.Policy.IsEmpty() {
|
||||
m.analyseBucketPolicy(bucket, dataReceiver)
|
||||
policy, err := policy.ParseJSONPolicy([]byte(policyJSON))
|
||||
if err != nil {
|
||||
m.modLog.Error(fmt.Sprintf("parsing bucket access policy (%s) as JSON: %s", name, err))
|
||||
} else {
|
||||
bucket.Policy = policy
|
||||
}
|
||||
|
||||
bucket.IsPublic = "No"
|
||||
if !bucket.Policy.IsEmpty() {
|
||||
m.analyseBucketPolicy(bucket, dataReceiver)
|
||||
} else {
|
||||
bucket.Access = "No resource policy"
|
||||
dataReceiver <- *bucket
|
||||
}
|
||||
} else {
|
||||
bucket.Access = "No resource policy"
|
||||
|
||||
// Send Bucket object through the channel to the receiver
|
||||
bucket.Access = "Skipped"
|
||||
bucket.IsPublic = "Skipped"
|
||||
dataReceiver <- *bucket
|
||||
}
|
||||
|
||||
// Send Bucket object through the channel to the receiver
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *BucketsModule) isPublicAccessBlocked(bucketName string, r string) bool {
|
||||
@@ -284,7 +330,7 @@ func (m *BucketsModule) isPublicAccessBlocked(bucketName string, r string) bool
|
||||
|
||||
}
|
||||
|
||||
func (m *BucketsModule) analyseBucketPolicy(bucket *Bucket, dataReceiver chan Bucket) {
|
||||
func (m *BucketsModule) analyseBucketPolicy(bucket *BucketRow, dataReceiver chan BucketRow) {
|
||||
m.storeAccessPolicy(bucket)
|
||||
|
||||
if bucket.Policy.IsPublic() && !bucket.Policy.IsConditionallyPublic() && !m.isPublicAccessBlocked(bucket.Name, bucket.Region) {
|
||||
@@ -299,14 +345,14 @@ func (m *BucketsModule) analyseBucketPolicy(bucket *Bucket, dataReceiver chan Bu
|
||||
} else {
|
||||
bucket.ResourcePolicySummary = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
|
||||
}
|
||||
bucket.ResourcePolicySummary = strings.TrimSuffix(bucket.ResourcePolicySummary, "\n")
|
||||
//bucket.ResourcePolicySummary = strings.TrimSuffix(bucket.ResourcePolicySummary, "\n")
|
||||
|
||||
}
|
||||
dataReceiver <- *bucket
|
||||
|
||||
}
|
||||
|
||||
func (m *BucketsModule) storeAccessPolicy(bucket *Bucket) {
|
||||
func (m *BucketsModule) storeAccessPolicy(bucket *BucketRow) {
|
||||
f := filepath.Join(m.getLootDir(), fmt.Sprintf("%s.json", bucket.Name))
|
||||
|
||||
if err := m.storeFile(f, bucket.PolicyJSON); err != nil {
|
||||
|
||||
+9
-7
@@ -81,9 +81,11 @@ func TestListBuckets(t *testing.T) {
|
||||
Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests"),
|
||||
Account: aws.String("123456789012"),
|
||||
},
|
||||
AWSRegions: []string{"us-east-1", "us-west-1", "us-west-2"},
|
||||
AWSProfile: "unittesting",
|
||||
Goroutines: 3,
|
||||
AWSRegions: []string{"us-east-1", "us-west-1", "us-west-2"},
|
||||
AWSProfile: "unittesting",
|
||||
AWSOutputType: "",
|
||||
CheckBucketPolicies: true,
|
||||
Goroutines: 3,
|
||||
}
|
||||
|
||||
subtests := []struct {
|
||||
@@ -91,14 +93,14 @@ func TestListBuckets(t *testing.T) {
|
||||
outputDirectory string
|
||||
verbosity int
|
||||
testModule BucketsModule
|
||||
expectedResult []Bucket
|
||||
expectedResult []BucketRow
|
||||
}{
|
||||
{
|
||||
name: "test1",
|
||||
outputDirectory: ".",
|
||||
verbosity: 2,
|
||||
testModule: m,
|
||||
expectedResult: []Bucket{{
|
||||
expectedResult: []BucketRow{{
|
||||
Name: "mockBucket123",
|
||||
}},
|
||||
},
|
||||
@@ -110,9 +112,9 @@ func TestListBuckets(t *testing.T) {
|
||||
|
||||
for _, subtest := range subtests {
|
||||
t.Run(subtest.name, func(t *testing.T) {
|
||||
subtest.testModule.PrintBuckets(subtest.testModule.OutputFormat, subtest.outputDirectory, subtest.verbosity)
|
||||
subtest.testModule.PrintBuckets(subtest.outputDirectory, subtest.verbosity)
|
||||
for index, expectedBucket := range subtest.expectedResult {
|
||||
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/123456789012-unittesting/table/buckets.txt")
|
||||
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/unittesting-123456789012/table/buckets.txt")
|
||||
resultsFile, err := afero.ReadFile(fs, resultsFilePath)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot read output file at %s: %s", resultsFilePath, err)
|
||||
|
||||
+36
-24
@@ -7,9 +7,11 @@ import (
|
||||
"github.com/aws/aws-sdk-go-v2/service/ecr"
|
||||
"github.com/aws/aws-sdk-go-v2/service/efs"
|
||||
"github.com/aws/aws-sdk-go-v2/service/fsx"
|
||||
"github.com/aws/aws-sdk-go-v2/service/glue"
|
||||
"github.com/aws/aws-sdk-go-v2/service/lambda"
|
||||
"github.com/aws/aws-sdk-go-v2/service/organizations"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sns"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sqs"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
@@ -28,13 +30,13 @@ func initIAMSimClient(iamSimPPClient sdk.AWSIAMClientInterface, caller sts.GetCa
|
||||
|
||||
}
|
||||
|
||||
func InitCloudFoxSNSClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSWrapTable bool) SNSModule {
|
||||
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
|
||||
func InitCloudFoxSNSClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSWrapTable bool, AWSMFAToken string) SNSModule {
|
||||
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
|
||||
cloudFoxSNSClient := SNSModule{
|
||||
SNSClient: sns.NewFromConfig(AWSConfig),
|
||||
Caller: caller,
|
||||
AWSProfile: AWSProfile,
|
||||
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
|
||||
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
|
||||
Goroutines: Goroutines,
|
||||
WrapTable: AWSWrapTable,
|
||||
}
|
||||
@@ -42,25 +44,25 @@ func InitCloudFoxSNSClient(caller sts.GetCallerIdentityOutput, AWSProfile string
|
||||
|
||||
}
|
||||
|
||||
func initCloudFoxS3Client(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string) BucketsModule {
|
||||
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
|
||||
func initCloudFoxS3Client(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, AWSMFAToken string) BucketsModule {
|
||||
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
|
||||
cloudFoxS3Client := BucketsModule{
|
||||
S3Client: s3.NewFromConfig(AWSConfig),
|
||||
Caller: caller,
|
||||
AWSProfile: AWSProfile,
|
||||
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
|
||||
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
|
||||
}
|
||||
return cloudFoxS3Client
|
||||
|
||||
}
|
||||
|
||||
func InitSQSClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) SQSModule {
|
||||
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
|
||||
func InitSQSClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) SQSModule {
|
||||
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
|
||||
sqsClient := SQSModule{
|
||||
SQSClient: sqs.NewFromConfig(AWSConfig),
|
||||
|
||||
Caller: caller,
|
||||
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
|
||||
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
|
||||
AWSProfile: AWSProfile,
|
||||
Goroutines: Goroutines,
|
||||
}
|
||||
@@ -69,58 +71,68 @@ func InitSQSClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVers
|
||||
|
||||
}
|
||||
|
||||
func InitLambdaClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) LambdasModule {
|
||||
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
|
||||
func InitLambdaClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) LambdasModule {
|
||||
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
|
||||
lambdaClient := LambdasModule{
|
||||
LambdaClient: lambda.NewFromConfig(AWSConfig),
|
||||
Caller: caller,
|
||||
AWSProfile: AWSProfile,
|
||||
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
|
||||
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
|
||||
}
|
||||
return lambdaClient
|
||||
}
|
||||
|
||||
func InitCodeBuildClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) CodeBuildModule {
|
||||
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
|
||||
func InitCodeBuildClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) CodeBuildModule {
|
||||
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
|
||||
codeBuildClient := CodeBuildModule{
|
||||
CodeBuildClient: codebuild.NewFromConfig(AWSConfig),
|
||||
Caller: caller,
|
||||
AWSProfile: AWSProfile,
|
||||
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
|
||||
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
|
||||
}
|
||||
return codeBuildClient
|
||||
}
|
||||
|
||||
func InitECRClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) ECRModule {
|
||||
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
|
||||
func InitECRClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) ECRModule {
|
||||
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
|
||||
ecrClient := ECRModule{
|
||||
ECRClient: ecr.NewFromConfig(AWSConfig),
|
||||
Caller: caller,
|
||||
AWSProfile: AWSProfile,
|
||||
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
|
||||
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
|
||||
}
|
||||
return ecrClient
|
||||
}
|
||||
|
||||
func InitFileSystemsClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) FilesystemsModule {
|
||||
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
|
||||
func InitFileSystemsClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) FilesystemsModule {
|
||||
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
|
||||
fileSystemsClient := FilesystemsModule{
|
||||
EFSClient: efs.NewFromConfig(AWSConfig),
|
||||
FSxClient: fsx.NewFromConfig(AWSConfig),
|
||||
Caller: caller,
|
||||
AWSProfile: AWSProfile,
|
||||
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
|
||||
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
|
||||
}
|
||||
return fileSystemsClient
|
||||
}
|
||||
|
||||
func InitOrgClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int) OrgModule {
|
||||
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion)
|
||||
func InitOrgClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) OrgModule {
|
||||
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
|
||||
orgClient := OrgModule{
|
||||
OrganizationsClient: organizations.NewFromConfig(AWSConfig),
|
||||
Caller: caller,
|
||||
AWSProfile: AWSProfile,
|
||||
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion),
|
||||
AWSRegions: internal.GetEnabledRegions(AWSProfile, cfVersion, AWSMFAToken),
|
||||
}
|
||||
return orgClient
|
||||
}
|
||||
|
||||
func InitSecretsManagerClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) *secretsmanager.Client {
|
||||
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
|
||||
return secretsmanager.NewFromConfig(AWSConfig)
|
||||
}
|
||||
|
||||
func InitGlueClient(caller sts.GetCallerIdentityOutput, AWSProfile string, cfVersion string, Goroutines int, AWSMFAToken string) *glue.Client {
|
||||
var AWSConfig = internal.AWSConfigFileLoader(AWSProfile, cfVersion, AWSMFAToken)
|
||||
return glue.NewFromConfig(AWSConfig)
|
||||
}
|
||||
+64
-32
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
@@ -20,12 +21,13 @@ type CloudformationModule struct {
|
||||
// General configuration data
|
||||
CloudFormationClient sdk.CloudFormationClientInterface
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
// Main module data
|
||||
CFStacks []CFStack
|
||||
@@ -45,8 +47,8 @@ type CFStack struct {
|
||||
Template string
|
||||
}
|
||||
|
||||
func (m *CloudformationModule) PrintCloudformationStacks(outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These stuct values are used by the output module
|
||||
func (m *CloudformationModule) PrintCloudformationStacks(outputDirectory string, verbosity int) {
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "cloudformation"
|
||||
@@ -93,48 +95,45 @@ func (m *CloudformationModule) PrintCloudformationStacks(outputFormat string, ou
|
||||
|
||||
// add - if struct is not empty do this. otherwise, dont write anything.
|
||||
m.output.Headers = []string{
|
||||
"Account",
|
||||
"Service",
|
||||
"Region",
|
||||
"Name",
|
||||
"Role",
|
||||
// "Parameters",
|
||||
// "Outputs",
|
||||
"Parameters",
|
||||
"Outputs",
|
||||
}
|
||||
|
||||
// Table rows
|
||||
for i := range m.CFStacks {
|
||||
// var isParameters string
|
||||
// var isOutputs string
|
||||
// if m.CFStacks[i].Parameters != nil {
|
||||
// isParameters = "Y"
|
||||
// } else {
|
||||
// isParameters = "N"
|
||||
// }
|
||||
// if m.CFStacks[i].Outputs != nil {
|
||||
// isOutputs = "Y"
|
||||
// } else {
|
||||
// isOutputs = "N"
|
||||
// }
|
||||
var hasParameters string
|
||||
var hasOutputs string
|
||||
if m.CFStacks[i].Parameters != nil {
|
||||
hasParameters = "Y"
|
||||
} else {
|
||||
hasParameters = "N"
|
||||
}
|
||||
if m.CFStacks[i].Outputs != nil {
|
||||
hasOutputs = "Y"
|
||||
} else {
|
||||
hasOutputs = "N"
|
||||
}
|
||||
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
m.CFStacks[i].AWSService,
|
||||
m.CFStacks[i].Region,
|
||||
m.CFStacks[i].Name,
|
||||
m.CFStacks[i].Role,
|
||||
// isParameters,
|
||||
// isOutputs,
|
||||
hasParameters,
|
||||
hasOutputs,
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
if len(m.output.Body) > 0 {
|
||||
// m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account),m.AWSProfile))
|
||||
//m.output.OutputSelector(outputFormat)
|
||||
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
|
||||
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -142,10 +141,43 @@ func (m *CloudformationModule) PrintCloudformationStacks(outputFormat string, ou
|
||||
Wrap: m.WrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"Service",
|
||||
"Region",
|
||||
"Name",
|
||||
"Role",
|
||||
"Parameters",
|
||||
"Outputs",
|
||||
}
|
||||
// Otherwise, use the default columns.
|
||||
} else {
|
||||
tableCols = []string{
|
||||
"Service",
|
||||
"Region",
|
||||
"Name",
|
||||
"Role",
|
||||
}
|
||||
}
|
||||
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
|
||||
@@ -1,81 +1,15 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/cloudformation"
|
||||
"github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
)
|
||||
|
||||
const DESCRIBE_STACKS_TEST_FILE = "./test-data/cloudformation-describestacks.json"
|
||||
const TEMPLATE_BODY_TEST_FILE = "./test-data/cloudformation-getTemplate.json"
|
||||
|
||||
type Stacks struct {
|
||||
Stacks []struct {
|
||||
StackID string `json:"StackId"`
|
||||
Description string `json:"Description"`
|
||||
Tags []interface{} `json:"Tags"`
|
||||
Outputs []types.Output `json:"Outputs"`
|
||||
Parameters []types.Parameter `json:"Parameters"`
|
||||
StackStatusReason interface{} `json:"StackStatusReason"`
|
||||
CreationTime time.Time `json:"CreationTime"`
|
||||
Capabilities []interface{} `json:"Capabilities"`
|
||||
StackName string `json:"StackName"`
|
||||
RoleArn string `json:"RoleArn"`
|
||||
StackStatus string `json:"StackStatus"`
|
||||
DisableRollback bool `json:"DisableRollback"`
|
||||
} `json:"Stacks"`
|
||||
}
|
||||
|
||||
type TemplateBody struct {
|
||||
TemplateBody string `json:"TemplateBody"`
|
||||
}
|
||||
|
||||
type MockedCloudformationClient struct {
|
||||
describeStacks Stacks
|
||||
getTemplateBody TemplateBody
|
||||
}
|
||||
|
||||
func (m *MockedCloudformationClient) DescribeStacks(ctx context.Context, params *cloudformation.DescribeStacksInput, optFns ...func(*cloudformation.Options)) (*cloudformation.DescribeStacksOutput, error) {
|
||||
|
||||
err := json.Unmarshal(readTestFile(DESCRIBE_STACKS_TEST_FILE), &m.describeStacks)
|
||||
if err != nil {
|
||||
log.Fatalf("can't unmarshall file %s", DESCRIBE_STACKS_TEST_FILE)
|
||||
}
|
||||
var stacks []types.Stack
|
||||
for _, stack := range m.describeStacks.Stacks {
|
||||
stacks = append(stacks, types.Stack{
|
||||
StackName: &stack.StackName,
|
||||
RoleARN: &stack.RoleArn,
|
||||
Outputs: stack.Outputs,
|
||||
Parameters: stack.Parameters,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
return &cloudformation.DescribeStacksOutput{Stacks: stacks}, nil
|
||||
}
|
||||
|
||||
func (m *MockedCloudformationClient) GetTemplate(ctx context.Context, params *cloudformation.GetTemplateInput, optFns ...func(*cloudformation.Options)) (*cloudformation.GetTemplateOutput, error) {
|
||||
err := json.Unmarshal(readTestFile(DESCRIBE_STACKS_TEST_FILE), &m.getTemplateBody)
|
||||
if err != nil {
|
||||
log.Fatalf("can't unmarshall file %s", TEMPLATE_BODY_TEST_FILE)
|
||||
}
|
||||
|
||||
return &cloudformation.GetTemplateOutput{TemplateBody: &m.getTemplateBody.TemplateBody}, nil
|
||||
}
|
||||
|
||||
func (m *MockedCloudformationClient) ListStacks(ctx context.Context, params *cloudformation.ListStacksInput, optFns ...func(*cloudformation.Options)) (*cloudformation.ListStacksOutput, error) {
|
||||
return &cloudformation.ListStacksOutput{}, nil
|
||||
}
|
||||
|
||||
func TestCloudFormation(t *testing.T) {
|
||||
subtests := []struct {
|
||||
name string
|
||||
@@ -89,9 +23,8 @@ func TestCloudFormation(t *testing.T) {
|
||||
outputDirectory: ".",
|
||||
verbosity: 2,
|
||||
testModule: CloudformationModule{
|
||||
CloudFormationClient: &MockedCloudformationClient{},
|
||||
CloudFormationClient: &sdk.MockedCloudformationClient{},
|
||||
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("test")},
|
||||
OutputFormat: "table",
|
||||
AWSProfile: "test",
|
||||
Goroutines: 30,
|
||||
AWSRegions: AWSRegions,
|
||||
@@ -105,7 +38,7 @@ func TestCloudFormation(t *testing.T) {
|
||||
internal.MockFileSystem(true)
|
||||
for _, subtest := range subtests {
|
||||
t.Run(subtest.name, func(t *testing.T) {
|
||||
subtest.testModule.PrintCloudformationStacks(subtest.testModule.OutputFormat, subtest.outputDirectory, subtest.verbosity)
|
||||
subtest.testModule.PrintCloudformationStacks(subtest.outputDirectory, subtest.verbosity)
|
||||
for index, expectedStack := range subtest.expectedResult {
|
||||
if expectedStack.Name != subtest.testModule.CFStacks[index].Name {
|
||||
log.Fatal("Stack name does not match expected name")
|
||||
|
||||
+55
-40
@@ -3,6 +3,7 @@ package aws
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
@@ -18,9 +19,11 @@ type CodeBuildModule struct {
|
||||
CodeBuildClient sdk.CodeBuildClientInterface
|
||||
IAMClient sdk.AWSIAMClientInterface
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
SkipAdminCheck bool
|
||||
@@ -46,8 +49,8 @@ type Project struct {
|
||||
CanPrivEsc string
|
||||
}
|
||||
|
||||
func (m *CodeBuildModule) PrintCodeBuildProjects(outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These stuct values are used by the output module
|
||||
func (m *CodeBuildModule) PrintCodeBuildProjects(outputDirectory string, verbosity int) {
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "codebuild"
|
||||
@@ -107,50 +110,66 @@ func (m *CodeBuildModule) PrintCodeBuildProjects(outputFormat string, outputDire
|
||||
}
|
||||
|
||||
// add - if struct is not empty do this. otherwise, dont write anything.
|
||||
if m.pmapperError == nil {
|
||||
m.output.Headers = []string{
|
||||
m.output.Headers = []string{
|
||||
"Account",
|
||||
"Region",
|
||||
"Name",
|
||||
"Role",
|
||||
"IsAdminRole?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"Region",
|
||||
"Name",
|
||||
"Role",
|
||||
"IsAdminRole?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
// Otherwise, use the default columns.
|
||||
} else {
|
||||
m.output.Headers = []string{
|
||||
tableCols = []string{
|
||||
"Region",
|
||||
"Name",
|
||||
"Role",
|
||||
"IsAdminRole?",
|
||||
//"CanPrivEscToAdmin?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the pmapper row if there is no pmapper data
|
||||
if m.pmapperError != nil {
|
||||
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
|
||||
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
|
||||
}
|
||||
|
||||
// Table rows
|
||||
|
||||
for i := range m.Projects {
|
||||
if m.pmapperError == nil {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
m.Projects[i].Region,
|
||||
m.Projects[i].Name,
|
||||
m.Projects[i].Role,
|
||||
m.Projects[i].Admin,
|
||||
m.Projects[i].CanPrivEsc,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
m.Projects[i].Region,
|
||||
m.Projects[i].Name,
|
||||
m.Projects[i].Role,
|
||||
m.Projects[i].Admin,
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
m.Projects[i].Region,
|
||||
m.Projects[i].Name,
|
||||
m.Projects[i].Role,
|
||||
m.Projects[i].Admin,
|
||||
m.Projects[i].CanPrivEsc,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
var seen []string
|
||||
@@ -162,11 +181,6 @@ func (m *CodeBuildModule) PrintCodeBuildProjects(outputFormat string, outputDire
|
||||
|
||||
if len(m.output.Body) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//m.output.OutputSelector(outputFormat)
|
||||
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
|
||||
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
//m.writeLoot(m.output.FilePath, verbosity)
|
||||
//fmt.Printf("[%s][%s] %d projects with a total of %d node groups found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), len(seen), len(m.output.Body))
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -175,9 +189,10 @@ func (m *CodeBuildModule) PrintCodeBuildProjects(outputFormat string, outputDire
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
|
||||
@@ -124,9 +124,9 @@ func TestCodeBuildProjects(t *testing.T) {
|
||||
tmpDir := "."
|
||||
|
||||
// execute the module with verbosity set to 2
|
||||
m.PrintCodeBuildProjects("table", tmpDir, 2)
|
||||
m.PrintCodeBuildProjects(tmpDir, 2)
|
||||
|
||||
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/123456789012-unittesting/table/codebuild.txt")
|
||||
resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/unittesting-123456789012/table/codebuild.txt")
|
||||
resultsFile, err := afero.ReadFile(fs, resultsFilePath)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot read output file at %s: %s", resultsFilePath, err)
|
||||
|
||||
+58
-20
@@ -7,6 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
@@ -19,16 +20,18 @@ import (
|
||||
|
||||
type DatabasesModule struct {
|
||||
RDSClient sdk.RDSClientInterface
|
||||
RedshiftClient sdk.RedShiftClientInterface
|
||||
RedshiftClient sdk.AWSRedShiftClientInterface
|
||||
DynamoDBClient sdk.DynamoDBClientInterface
|
||||
DocDBClient sdk.DocDBClientInterface
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
|
||||
Databases []Database
|
||||
CommandCounter internal.CommandCounter
|
||||
@@ -52,8 +55,8 @@ type Database struct {
|
||||
Size string
|
||||
}
|
||||
|
||||
func (m *DatabasesModule) PrintDatabases(outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These stuct values are used by the output module
|
||||
func (m *DatabasesModule) PrintDatabases(outputDirectory string, verbosity int) {
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "databases"
|
||||
@@ -101,6 +104,7 @@ func (m *DatabasesModule) PrintDatabases(outputFormat string, outputDirectory st
|
||||
})
|
||||
|
||||
m.output.Headers = []string{
|
||||
"Account",
|
||||
"Service",
|
||||
"Engine",
|
||||
"Region",
|
||||
@@ -114,11 +118,47 @@ func (m *DatabasesModule) PrintDatabases(outputFormat string, outputDirectory st
|
||||
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"Service",
|
||||
"Engine",
|
||||
"Region",
|
||||
"Name",
|
||||
"Size",
|
||||
"UserName",
|
||||
"Endpoint",
|
||||
}
|
||||
// Otherwise, use the default columns.
|
||||
} else {
|
||||
tableCols = []string{
|
||||
"Service",
|
||||
"Engine",
|
||||
"Region",
|
||||
"Name",
|
||||
"Size",
|
||||
"UserName",
|
||||
"Endpoint",
|
||||
}
|
||||
}
|
||||
|
||||
// Table rows
|
||||
for i := range m.Databases {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
m.Databases[i].AWSService,
|
||||
m.Databases[i].Engine,
|
||||
m.Databases[i].Region,
|
||||
@@ -135,10 +175,7 @@ func (m *DatabasesModule) PrintDatabases(outputFormat string, outputDirectory st
|
||||
}
|
||||
if len(m.output.Body) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//m.output.OutputSelector(outputFormat)
|
||||
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
|
||||
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
//m.writeLoot(m.output.FilePath, verbosity)
|
||||
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -147,9 +184,10 @@ func (m *DatabasesModule) PrintDatabases(outputFormat string, outputDirectory st
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
@@ -287,7 +325,7 @@ func (m *DatabasesModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup,
|
||||
endpoint := aws.ToString(instance.Endpoint.Address)
|
||||
engine := aws.ToString(instance.Engine)
|
||||
|
||||
if instance.PubliclyAccessible {
|
||||
if aws.ToBool(instance.PubliclyAccessible) {
|
||||
public = "True"
|
||||
} else {
|
||||
public = "False"
|
||||
@@ -300,7 +338,7 @@ func (m *DatabasesModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup,
|
||||
Engine: engine,
|
||||
Endpoint: endpoint,
|
||||
UserName: aws.ToString(instance.MasterUsername),
|
||||
Port: port,
|
||||
Port: aws.ToInt32(port),
|
||||
Protocol: aws.ToString(instance.Engine),
|
||||
Public: public,
|
||||
}
|
||||
@@ -344,7 +382,7 @@ func (m *DatabasesModule) getRedshiftDatabasesPerRegion(r string, wg *sync.WaitG
|
||||
//id := workspace.Id
|
||||
endpoint := aws.ToString(cluster.Endpoint.Address)
|
||||
|
||||
if cluster.PubliclyAccessible {
|
||||
if aws.ToBool(cluster.PubliclyAccessible) {
|
||||
public = "True"
|
||||
} else {
|
||||
public = "False"
|
||||
@@ -355,7 +393,7 @@ func (m *DatabasesModule) getRedshiftDatabasesPerRegion(r string, wg *sync.WaitG
|
||||
Region: r,
|
||||
Name: name,
|
||||
Endpoint: endpoint,
|
||||
Port: port,
|
||||
Port: aws.ToInt32(port),
|
||||
Protocol: protocol,
|
||||
Public: public,
|
||||
}
|
||||
|
||||
+117
-77
@@ -21,13 +21,15 @@ import (
|
||||
|
||||
type ECRModule struct {
|
||||
// General configuration data
|
||||
ECRClient sdk.AWSECRClientInterface
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
ECRClient sdk.AWSECRClientInterface
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
|
||||
// Main module data
|
||||
Repositories []Repository
|
||||
@@ -49,8 +51,8 @@ type Repository struct {
|
||||
PolicyJSON string
|
||||
}
|
||||
|
||||
func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These stuct values are used by the output module
|
||||
func (m *ECRModule) PrintECR(outputDirectory string, verbosity int) {
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "ecr"
|
||||
@@ -95,8 +97,9 @@ func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbos
|
||||
receiverDone <- true
|
||||
<-receiverDone
|
||||
|
||||
// add - if struct is not empty do this. otherwise, dont write anything.
|
||||
// This is the complete list of potential table columns
|
||||
m.output.Headers = []string{
|
||||
"Account",
|
||||
"Service",
|
||||
"Region",
|
||||
"Name",
|
||||
@@ -106,11 +109,53 @@ func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbos
|
||||
"ImageSize",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"Service",
|
||||
"Region",
|
||||
"Name",
|
||||
"URI",
|
||||
"PushedAt",
|
||||
"ImageTags",
|
||||
"ImageSize",
|
||||
}
|
||||
// Otherwise, use the default columns.
|
||||
} else {
|
||||
tableCols = []string{
|
||||
"Service",
|
||||
"Region",
|
||||
"Name",
|
||||
"URI",
|
||||
"PushedAt",
|
||||
"ImageTags",
|
||||
"ImageSize",
|
||||
}
|
||||
}
|
||||
|
||||
// sort the table by Name
|
||||
sort.Slice(m.Repositories, func(i, j int) bool {
|
||||
return m.Repositories[i].Name < m.Repositories[j].Name
|
||||
})
|
||||
|
||||
// Table rows
|
||||
for i := range m.Repositories {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
m.Repositories[i].AWSService,
|
||||
m.Repositories[i].Region,
|
||||
m.Repositories[i].Name,
|
||||
@@ -124,10 +169,7 @@ func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbos
|
||||
}
|
||||
if len(m.output.Body) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//m.output.OutputSelector(outputFormat)
|
||||
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
|
||||
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
//m.writeLoot(m.output.FilePath, verbosity)
|
||||
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -136,9 +178,10 @@ func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbos
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
@@ -239,92 +282,89 @@ func (m *ECRModule) getECRRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
|
||||
<-semaphore
|
||||
}()
|
||||
|
||||
var allImages []types.ImageDetail
|
||||
var repoURI string
|
||||
var repoName string
|
||||
|
||||
DescribeRepositories, err := m.describeRepositories(r)
|
||||
Repositories, err := sdk.CachedECRDescribeRepositories(m.ECRClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
for _, repo := range DescribeRepositories {
|
||||
repoName = aws.ToString(repo.RepositoryName)
|
||||
repoURI = aws.ToString(repo.RepositoryUri)
|
||||
for _, repo := range Repositories {
|
||||
repoName := aws.ToString(repo.RepositoryName)
|
||||
repoURI := aws.ToString(repo.RepositoryUri)
|
||||
//created := *repo.CreatedAt
|
||||
//fmt.Printf("%s, %s, %s", repoName, repoURI, created)
|
||||
|
||||
images, err := m.describeImages(r, repoName)
|
||||
images, err := sdk.CachedECRDescribeImages(m.ECRClient, aws.ToString(m.Caller.Account), r, repoName)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
continue
|
||||
}
|
||||
allImages = append(allImages, images...)
|
||||
}
|
||||
|
||||
sort.Slice(allImages, func(i, j int) bool {
|
||||
return allImages[i].ImagePushedAt.Format("2006-01-02 15:04:05") < allImages[j].ImagePushedAt.Format("2006-01-02 15:04:05")
|
||||
})
|
||||
sort.Slice(images, func(i, j int) bool {
|
||||
return images[i].ImagePushedAt.Format("2006-01-02 15:04:05") < images[j].ImagePushedAt.Format("2006-01-02 15:04:05")
|
||||
})
|
||||
|
||||
var image types.ImageDetail
|
||||
var imageTags string
|
||||
var image types.ImageDetail
|
||||
var imageTags string
|
||||
|
||||
if len(allImages) > 1 {
|
||||
image = allImages[len(allImages)-1]
|
||||
} else if len(allImages) == 1 {
|
||||
image = allImages[0]
|
||||
} else {
|
||||
return
|
||||
}
|
||||
if len(images) > 1 {
|
||||
image = images[len(images)-1]
|
||||
} else if len(images) == 1 {
|
||||
image = images[0]
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(image.ImageTags) > 0 {
|
||||
imageTags = image.ImageTags[0]
|
||||
}
|
||||
//imageTags := image.ImageTags[0]
|
||||
pushedAt := image.ImagePushedAt.Format("2006-01-02 15:04:05")
|
||||
imageSize := aws.ToInt64(image.ImageSizeInBytes)
|
||||
pullURI := fmt.Sprintf("%s:%s", repoURI, imageTags)
|
||||
if len(image.ImageTags) > 0 {
|
||||
imageTags = image.ImageTags[0]
|
||||
} else {
|
||||
imageTags = "No tags"
|
||||
}
|
||||
|
||||
dataReceiver <- Repository{
|
||||
AWSService: "ECR",
|
||||
Name: repoName,
|
||||
Region: r,
|
||||
URI: pullURI,
|
||||
PushedAt: pushedAt,
|
||||
ImageTags: imageTags,
|
||||
ImageSize: imageSize,
|
||||
//imageTags := image.ImageTags[0]
|
||||
pushedAt := image.ImagePushedAt.Format("2006-01-02 15:04:05")
|
||||
imageSize := aws.ToInt64(image.ImageSizeInBytes)
|
||||
pullURI := fmt.Sprintf("%s:%s", repoURI, imageTags)
|
||||
|
||||
dataReceiver <- Repository{
|
||||
AWSService: "ECR",
|
||||
Name: repoName,
|
||||
Region: r,
|
||||
URI: pullURI,
|
||||
PushedAt: pushedAt,
|
||||
ImageTags: imageTags,
|
||||
ImageSize: imageSize,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *ECRModule) describeRepositories(r string) ([]types.Repository, error) {
|
||||
// func (m *ECRModule) describeRepositories(r string) ([]types.Repository, error) {
|
||||
|
||||
var repositories []types.Repository
|
||||
Repositories, err := sdk.CachedECRDescribeRepositories(m.ECRClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
m.CommandCounter.Error++
|
||||
return nil, err
|
||||
}
|
||||
// var repositories []types.Repository
|
||||
// Repositories, err := sdk.CachedECRDescribeRepositories(m.ECRClient, aws.ToString(m.Caller.Account), r)
|
||||
// if err != nil {
|
||||
// m.CommandCounter.Error++
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
repositories = append(repositories, Repositories...)
|
||||
// repositories = append(repositories, Repositories...)
|
||||
|
||||
return repositories, nil
|
||||
}
|
||||
// return repositories, nil
|
||||
// }
|
||||
|
||||
func (m *ECRModule) describeImages(r string, repoName string) ([]types.ImageDetail, error) {
|
||||
var images []types.ImageDetail
|
||||
// func (m *ECRModule) describeImages(r string, repoName string) ([]types.ImageDetail, error) {
|
||||
// var images []types.ImageDetail
|
||||
|
||||
ImageDetails, err := sdk.CachedECRDescribeImages(m.ECRClient, aws.ToString(m.Caller.Account), r, repoName)
|
||||
if err != nil {
|
||||
m.CommandCounter.Error++
|
||||
return nil, err
|
||||
}
|
||||
images = append(images, ImageDetails...)
|
||||
return images, nil
|
||||
}
|
||||
// ImageDetails, err := sdk.CachedECRDescribeImages(m.ECRClient, aws.ToString(m.Caller.Account), r, repoName)
|
||||
// if err != nil {
|
||||
// m.CommandCounter.Error++
|
||||
// return nil, err
|
||||
// }
|
||||
// images = append(images, ImageDetails...)
|
||||
// return images, nil
|
||||
// }
|
||||
|
||||
func (m *ECRModule) getECRRepositoryPolicy(r string, repository string) (policy.Policy, error) {
|
||||
var repoPolicy policy.Policy
|
||||
|
||||
+22
-51
@@ -1,52 +1,15 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ecr"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ecr/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
)
|
||||
|
||||
var AWSRegions = []string{"us-east-1", "us-east-2", "us-west-1", "us-west-2", "af-south-1", "ap-east-1", "ap-south-1", "ap-northeast-3", "ap-northeast-2", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1", "ca-central-1", "eu-central-1", "eu-west-1", "eu-west-2", "eu-south-1", "eu-west-3", "eu-north-1", "me-south-1", "sa-east-1"}
|
||||
|
||||
type MockedECRClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedECRClient) DescribeRepositories(ctx context.Context, params *ecr.DescribeRepositoriesInput, optFns ...func(*ecr.Options)) (*ecr.DescribeRepositoriesOutput, error) {
|
||||
return &ecr.DescribeRepositoriesOutput{
|
||||
Repositories: []types.Repository{
|
||||
{
|
||||
RepositoryName: aws.String("test1"),
|
||||
RepositoryUri: aws.String("testURI"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedECRClient) DescribeImages(context.Context, *ecr.DescribeImagesInput, ...func(*ecr.Options)) (*ecr.DescribeImagesOutput, error) {
|
||||
return &ecr.DescribeImagesOutput{
|
||||
ImageDetails: []types.ImageDetail{
|
||||
{
|
||||
ImagePushedAt: aws.Time(time.Now()),
|
||||
ImageSizeInBytes: aws.Int64(123456),
|
||||
ImageTags: []string{"latest"},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedECRClient) GetRepositoryPolicy(context.Context, *ecr.GetRepositoryPolicyInput, ...func(*ecr.Options)) (*ecr.GetRepositoryPolicyOutput, error) {
|
||||
return &ecr.GetRepositoryPolicyOutput{
|
||||
PolicyText: aws.String("policyText"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestDescribeRepos(t *testing.T) {
|
||||
|
||||
subtests := []struct {
|
||||
@@ -61,29 +24,37 @@ func TestDescribeRepos(t *testing.T) {
|
||||
outputDirectory: ".",
|
||||
verbosity: 2,
|
||||
testModule: ECRModule{
|
||||
ECRClient: &MockedECRClient{},
|
||||
ECRClient: &sdk.MockedECRClient{},
|
||||
Caller: sts.GetCallerIdentityOutput{
|
||||
Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests"),
|
||||
Account: aws.String("123456789012"),
|
||||
},
|
||||
OutputFormat: "table",
|
||||
AWSProfile: "test",
|
||||
Goroutines: 30,
|
||||
AWSRegions: AWSRegions,
|
||||
AWSProfile: "test",
|
||||
Goroutines: 30,
|
||||
AWSRegions: []string{"us-east-1"},
|
||||
},
|
||||
expectedResult: []Repository{{
|
||||
Name: "test1",
|
||||
URI: "testURI:latest",
|
||||
PushedAt: "2022-10-25 15:14:06",
|
||||
ImageTags: "latest",
|
||||
ImageSize: 123456,
|
||||
}},
|
||||
expectedResult: []Repository{
|
||||
{
|
||||
Name: "repo1",
|
||||
URI: "11111111111111.dkr.ecr.us-east-1.amazonaws.com/repo1",
|
||||
PushedAt: "2022-10-25 15:14:00",
|
||||
ImageTags: "customtag, tag2",
|
||||
ImageSize: 123456,
|
||||
},
|
||||
{
|
||||
Name: "repo2",
|
||||
URI: "11111111111111.dkr.ecr.us-east-1.amazonaws.com/repo2",
|
||||
PushedAt: "2021-10-15 11:14:00",
|
||||
ImageTags: "latest",
|
||||
ImageSize: 2222222,
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
internal.MockFileSystem(true)
|
||||
for _, subtest := range subtests {
|
||||
t.Run(subtest.name, func(t *testing.T) {
|
||||
subtest.testModule.PrintECR(subtest.testModule.OutputFormat, subtest.outputDirectory, subtest.verbosity)
|
||||
subtest.testModule.PrintECR(subtest.outputDirectory, subtest.verbosity)
|
||||
for index, expectedRepo := range subtest.expectedResult {
|
||||
if expectedRepo.Name != subtest.testModule.Repositories[index].Name {
|
||||
log.Fatal("Repo name does not match expected name")
|
||||
|
||||
+104
-149
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ecs"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ecs/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
@@ -21,20 +20,16 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type DescribeTasksDefinitionAPIClient interface {
|
||||
DescribeTaskDefinition(context.Context, *ecs.DescribeTaskDefinitionInput, ...func(*ecs.Options)) (*ecs.DescribeTaskDefinitionOutput, error)
|
||||
}
|
||||
type ECSTasksModule struct {
|
||||
DescribeTaskDefinitionClient DescribeTasksDefinitionAPIClient
|
||||
DescribeTasksClient ecs.DescribeTasksAPIClient
|
||||
ListTasksClient ecs.ListTasksAPIClient
|
||||
ListClustersClient ecs.ListClustersAPIClient
|
||||
DescribeNetworkInterfacesClient ec2.DescribeNetworkInterfacesAPIClient
|
||||
IAMClient sdk.AWSIAMClientInterface
|
||||
ECSClient sdk.AWSECSClientInterface
|
||||
EC2Client sdk.AWSEC2ClientInterface
|
||||
IAMClient sdk.AWSIAMClientInterface
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
AWSProfile string
|
||||
Goroutines int
|
||||
SkipAdminCheck bool
|
||||
@@ -63,7 +58,7 @@ type MappedECSTask struct {
|
||||
CanPrivEsc string
|
||||
}
|
||||
|
||||
func (m *ECSTasksModule) ECSTasks(outputFormat string, outputDirectory string, verbosity int) {
|
||||
func (m *ECSTasksModule) ECSTasks(outputDirectory string, verbosity int) {
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "ecs-tasks"
|
||||
@@ -126,7 +121,7 @@ func (m *ECSTasksModule) ECSTasks(outputFormat string, outputDirectory string, v
|
||||
receiverDone <- true
|
||||
<-receiverDone
|
||||
|
||||
m.printECSTaskData(outputFormat, outputDirectory, dataReceiver, verbosity)
|
||||
m.printECSTaskData(outputDirectory, dataReceiver, verbosity)
|
||||
|
||||
}
|
||||
|
||||
@@ -143,9 +138,36 @@ func (m *ECSTasksModule) Receiver(receiver chan MappedECSTask, receiverDone chan
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ECSTasksModule) printECSTaskData(outputFormat string, outputDirectory string, dataReceiver chan MappedECSTask, verbosity int) {
|
||||
if m.pmapperError == nil {
|
||||
m.output.Headers = []string{
|
||||
func (m *ECSTasksModule) printECSTaskData(outputDirectory string, dataReceiver chan MappedECSTask, verbosity int) {
|
||||
// This is the complete list of potential table columns
|
||||
m.output.Headers = []string{
|
||||
"Account",
|
||||
"Cluster",
|
||||
"TaskDefinition",
|
||||
"LaunchType",
|
||||
"ID",
|
||||
"External IP",
|
||||
"Internal IP",
|
||||
"RoleArn",
|
||||
"IsAdminRole?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"Cluster",
|
||||
"TaskDefinition",
|
||||
"LaunchType",
|
||||
@@ -156,61 +178,46 @@ func (m *ECSTasksModule) printECSTaskData(outputFormat string, outputDirectory s
|
||||
"IsAdminRole?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
// Otherwise, use the default columns.
|
||||
} else {
|
||||
m.output.Headers = []string{
|
||||
tableCols = []string{
|
||||
"Cluster",
|
||||
"TaskDefinition",
|
||||
"LaunchType",
|
||||
"ID",
|
||||
"External IP",
|
||||
"Internal IP",
|
||||
"RoleArn",
|
||||
"IsAdminRole?",
|
||||
//"CanPrivEscToAdmin?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
}
|
||||
|
||||
if m.pmapperError == nil {
|
||||
for _, ecsTask := range m.MappedECSTasks {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
ecsTask.Cluster,
|
||||
ecsTask.TaskDefinitionName,
|
||||
ecsTask.LaunchType,
|
||||
ecsTask.ID,
|
||||
ecsTask.ExternalIP,
|
||||
ecsTask.PrivateIP,
|
||||
ecsTask.Role,
|
||||
ecsTask.Admin,
|
||||
ecsTask.CanPrivEsc,
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
for _, ecsTask := range m.MappedECSTasks {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
ecsTask.Cluster,
|
||||
ecsTask.TaskDefinitionName,
|
||||
ecsTask.LaunchType,
|
||||
ecsTask.ID,
|
||||
ecsTask.ExternalIP,
|
||||
ecsTask.PrivateIP,
|
||||
ecsTask.Role,
|
||||
ecsTask.Admin,
|
||||
//ecsTask.CanPrivEsc,
|
||||
},
|
||||
)
|
||||
}
|
||||
// Remove the pmapper row if there is no pmapper data
|
||||
if m.pmapperError != nil {
|
||||
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
|
||||
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
|
||||
}
|
||||
|
||||
for _, ecsTask := range m.MappedECSTasks {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
ecsTask.Cluster,
|
||||
ecsTask.TaskDefinitionName,
|
||||
ecsTask.LaunchType,
|
||||
ecsTask.ID,
|
||||
ecsTask.ExternalIP,
|
||||
ecsTask.PrivateIP,
|
||||
ecsTask.Role,
|
||||
ecsTask.Admin,
|
||||
ecsTask.CanPrivEsc,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if len(m.output.Body) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
|
||||
//internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
//m.writeLoot(m.output.FilePath)
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -219,9 +226,10 @@ func (m *ECSTasksModule) printECSTaskData(outputFormat string, outputDirectory s
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
@@ -313,75 +321,37 @@ func (m *ECSTasksModule) executeChecks(r string, wg *sync.WaitGroup, dataReceive
|
||||
|
||||
func (m *ECSTasksModule) getListClusters(region string, dataReceiver chan MappedECSTask) {
|
||||
|
||||
var PaginationControl *string
|
||||
for {
|
||||
ListClusters, err := m.ListClustersClient.ListClusters(
|
||||
context.TODO(),
|
||||
&(ecs.ListClustersInput{
|
||||
NextToken: PaginationControl,
|
||||
}),
|
||||
func(o *ecs.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
for _, clusterARN := range ListClusters.ClusterArns {
|
||||
m.getListTasks(clusterARN, region, dataReceiver)
|
||||
}
|
||||
|
||||
if ListClusters.NextToken != nil {
|
||||
PaginationControl = ListClusters.NextToken
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
break
|
||||
}
|
||||
|
||||
ClusterArns, err := sdk.CachedECSListClusters(m.ECSClient, aws.ToString(m.Caller.Account), region)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
for _, clusterARN := range ClusterArns {
|
||||
m.getListTasks(clusterARN, region, dataReceiver)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *ECSTasksModule) getListTasks(clusterARN string, region string, dataReceiver chan MappedECSTask) {
|
||||
var PaginationControl *string
|
||||
for {
|
||||
|
||||
ListTasks, err := m.ListTasksClient.ListTasks(
|
||||
context.TODO(),
|
||||
&(ecs.ListTasksInput{
|
||||
Cluster: aws.String(clusterARN),
|
||||
NextToken: PaginationControl,
|
||||
}),
|
||||
func(o *ecs.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
batchSize := 100 // maximum value: https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_DescribeTasks.html#API_DescribeTasks_RequestSyntax
|
||||
for i := 0; i < len(ListTasks.TaskArns); i += batchSize {
|
||||
j := i + batchSize
|
||||
if j > len(ListTasks.TaskArns) {
|
||||
j = len(ListTasks.TaskArns)
|
||||
}
|
||||
|
||||
m.loadTasksData(clusterARN, ListTasks.TaskArns[i:j], region, dataReceiver)
|
||||
}
|
||||
|
||||
if ListTasks.NextToken != nil {
|
||||
PaginationControl = ListTasks.NextToken
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
break
|
||||
}
|
||||
|
||||
TaskArns, err := sdk.CachedECSListTasks(m.ECSClient, aws.ToString(m.Caller.Account), region, clusterARN)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
batchSize := 100 // maximum value: https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_DescribeTasks.html#API_DescribeTasks_RequestSyntax
|
||||
for i := 0; i < len(TaskArns); i += batchSize {
|
||||
j := i + batchSize
|
||||
if j > len(TaskArns) {
|
||||
j = len(TaskArns)
|
||||
}
|
||||
|
||||
m.loadTasksData(clusterARN, TaskArns[i:j], region, dataReceiver)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *ECSTasksModule) loadTasksData(clusterARN string, taskARNs []string, region string, dataReceiver chan MappedECSTask) {
|
||||
@@ -390,16 +360,7 @@ func (m *ECSTasksModule) loadTasksData(clusterARN string, taskARNs []string, reg
|
||||
return
|
||||
}
|
||||
|
||||
DescribeTasks, err := m.DescribeTasksClient.DescribeTasks(
|
||||
context.TODO(),
|
||||
&(ecs.DescribeTasksInput{
|
||||
Cluster: aws.String(clusterARN),
|
||||
Tasks: taskARNs,
|
||||
}),
|
||||
func(o *ecs.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
Tasks, err := sdk.CachedECSDescribeTasks(m.ECSClient, aws.ToString(m.Caller.Account), region, clusterARN, taskARNs)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
@@ -407,7 +368,7 @@ func (m *ECSTasksModule) loadTasksData(clusterARN string, taskARNs []string, reg
|
||||
}
|
||||
|
||||
eniIDs := []string{}
|
||||
for _, task := range DescribeTasks.Tasks {
|
||||
for _, task := range Tasks {
|
||||
eniID := getElasticNetworkInterfaceIDOfECSTask(task)
|
||||
if eniID != "" {
|
||||
eniIDs = append(eniIDs, eniID)
|
||||
@@ -420,8 +381,9 @@ func (m *ECSTasksModule) loadTasksData(clusterARN string, taskARNs []string, reg
|
||||
return
|
||||
}
|
||||
|
||||
for _, task := range DescribeTasks.Tasks {
|
||||
taskDefinition, err := m.describeTaskDefinition(aws.ToString(task.TaskDefinitionArn), region)
|
||||
for _, task := range Tasks {
|
||||
//taskDefinition, err := m.describeTaskDefinition(aws.ToString(task.TaskDefinitionArn), region)
|
||||
taskDefinition, err := sdk.CachedECSDescribeTaskDefinition(m.ECSClient, aws.ToString(m.Caller.Account), region, aws.ToString(task.TaskDefinitionArn))
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
@@ -461,7 +423,7 @@ func getTaskDefinitionContent(taskDefinition types.TaskDefinition) string {
|
||||
}
|
||||
|
||||
func (m *ECSTasksModule) describeTaskDefinition(taskDefinitionArn string, region string) (types.TaskDefinition, error) {
|
||||
DescribeTaskDefinition, err := m.DescribeTaskDefinitionClient.DescribeTaskDefinition(
|
||||
DescribeTaskDefinition, err := m.ECSClient.DescribeTaskDefinition(
|
||||
context.TODO(),
|
||||
&ecs.DescribeTaskDefinitionInput{
|
||||
TaskDefinition: &taskDefinitionArn,
|
||||
@@ -511,20 +473,13 @@ func (m *ECSTasksModule) loadPublicIPs(eniIDs []string, region string) (map[stri
|
||||
if len(eniIDs) == 0 {
|
||||
return eniPublicIPs, nil
|
||||
}
|
||||
DescribeNetworkInterfaces, err := m.DescribeNetworkInterfacesClient.DescribeNetworkInterfaces(
|
||||
context.TODO(),
|
||||
&(ec2.DescribeNetworkInterfacesInput{
|
||||
NetworkInterfaceIds: eniIDs,
|
||||
}),
|
||||
func(o *ec2.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
NetworkInterfaces, err := sdk.CachedEC2DescribeNetworkInterfaces(m.EC2Client, aws.ToString(m.Caller.Account), region)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting elastic network interfaces: %s", err)
|
||||
}
|
||||
|
||||
for _, eni := range DescribeNetworkInterfaces.NetworkInterfaces {
|
||||
for _, eni := range NetworkInterfaces {
|
||||
eniPublicIPs[aws.ToString(eni.NetworkInterfaceId)] = getPublicIPOfElasticNetworkInterface(eni)
|
||||
}
|
||||
|
||||
|
||||
+9
-247
@@ -1,249 +1,15 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
||||
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ecs"
|
||||
ecsTypes "github.com/aws/aws-sdk-go-v2/service/ecs/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
)
|
||||
|
||||
const DESCRIBE_TASKS_TEST_FILE = "./test-data/describe-tasks.json"
|
||||
const DESCRIBE_NETWORK_INTEFACES_TEST_FILE = "./test-data/describe-network-interfaces.json"
|
||||
|
||||
type ListTasks struct {
|
||||
TaskArns []string `json:"taskArns"`
|
||||
}
|
||||
|
||||
type DescribeTasks struct {
|
||||
Tasks []struct {
|
||||
Attachments []struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Details []struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
} `json:"details"`
|
||||
} `json:"attachments"`
|
||||
Attributes []struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
} `json:"attributes"`
|
||||
AvailabilityZone string `json:"availabilityZone"`
|
||||
ClusterArn string `json:"clusterArn"`
|
||||
Connectivity string `json:"connectivity"`
|
||||
ConnectivityAt string `json:"connectivityAt"`
|
||||
Containers []struct {
|
||||
ContainerArn string `json:"containerArn"`
|
||||
TaskArn string `json:"taskArn"`
|
||||
Name string `json:"name"`
|
||||
Image string `json:"image"`
|
||||
RuntimeID string `json:"runtimeId"`
|
||||
LastStatus string `json:"lastStatus"`
|
||||
NetworkBindings []interface{} `json:"networkBindings"`
|
||||
NetworkInterfaces []struct {
|
||||
AttachmentID string `json:"attachmentId"`
|
||||
PrivateIpv4Address string `json:"privateIpv4Address"`
|
||||
} `json:"networkInterfaces"`
|
||||
HealthStatus string `json:"healthStatus"`
|
||||
CPU string `json:"cpu"`
|
||||
Memory string `json:"memory"`
|
||||
} `json:"containers"`
|
||||
CPU string `json:"cpu"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
DesiredStatus string `json:"desiredStatus"`
|
||||
EnableExecuteCommand bool `json:"enableExecuteCommand"`
|
||||
Group string `json:"group"`
|
||||
HealthStatus string `json:"healthStatus"`
|
||||
LastStatus string `json:"lastStatus"`
|
||||
LaunchType string `json:"launchType"`
|
||||
Memory string `json:"memory"`
|
||||
Overrides struct {
|
||||
ContainerOverrides []struct {
|
||||
Name string `json:"name"`
|
||||
} `json:"containerOverrides"`
|
||||
InferenceAcceleratorOverrides []interface{} `json:"inferenceAcceleratorOverrides"`
|
||||
} `json:"overrides"`
|
||||
PlatformVersion string `json:"platformVersion"`
|
||||
PlatformFamily string `json:"platformFamily"`
|
||||
PullStartedAt string `json:"pullStartedAt"`
|
||||
PullStoppedAt string `json:"pullStoppedAt"`
|
||||
StartedAt string `json:"startedAt"`
|
||||
StartedBy string `json:"startedBy"`
|
||||
Tags []interface{} `json:"tags"`
|
||||
TaskArn string `json:"taskArn"`
|
||||
TaskDefinitionArn string `json:"taskDefinitionArn"`
|
||||
Version int `json:"version"`
|
||||
EphemeralStorage struct {
|
||||
SizeInGiB int `json:"sizeInGiB"`
|
||||
} `json:"ephemeralStorage"`
|
||||
} `json:"tasks"`
|
||||
Failures []interface{} `json:"failures"`
|
||||
}
|
||||
|
||||
type DescribeNetworkInterfaces struct {
|
||||
NetworkInterfaces []struct {
|
||||
Status string `json:"Status"`
|
||||
MacAddress string `json:"MacAddress"`
|
||||
SourceDestCheck bool `json:"SourceDestCheck"`
|
||||
VpcID string `json:"VpcId"`
|
||||
Description string `json:"Description"`
|
||||
Association struct {
|
||||
PublicIP string `json:"PublicIp"`
|
||||
AssociationID string `json:"AssociationId"`
|
||||
PublicDNSName string `json:"PublicDnsName"`
|
||||
IPOwnerID string `json:"IpOwnerId"`
|
||||
} `json:"Association"`
|
||||
NetworkInterfaceID string `json:"NetworkInterfaceId"`
|
||||
PrivateIPAddresses []struct {
|
||||
PrivateDNSName string `json:"PrivateDnsName"`
|
||||
Association struct {
|
||||
PublicIP string `json:"PublicIp"`
|
||||
AssociationID string `json:"AssociationId"`
|
||||
PublicDNSName string `json:"PublicDnsName"`
|
||||
IPOwnerID string `json:"IpOwnerId"`
|
||||
} `json:"Association"`
|
||||
Primary bool `json:"Primary"`
|
||||
PrivateIPAddress string `json:"PrivateIpAddress"`
|
||||
} `json:"PrivateIpAddresses"`
|
||||
RequesterManaged bool `json:"RequesterManaged"`
|
||||
Ipv6Addresses []interface{} `json:"Ipv6Addresses"`
|
||||
PrivateDNSName string `json:"PrivateDnsName,omitempty"`
|
||||
AvailabilityZone string `json:"AvailabilityZone"`
|
||||
Attachment struct {
|
||||
Status string `json:"Status"`
|
||||
DeviceIndex int `json:"DeviceIndex"`
|
||||
AttachTime time.Time `json:"AttachTime"`
|
||||
InstanceID string `json:"InstanceId"`
|
||||
DeleteOnTermination bool `json:"DeleteOnTermination"`
|
||||
AttachmentID string `json:"AttachmentId"`
|
||||
InstanceOwnerID string `json:"InstanceOwnerId"`
|
||||
} `json:"Attachment"`
|
||||
Groups []struct {
|
||||
GroupName string `json:"GroupName"`
|
||||
GroupID string `json:"GroupId"`
|
||||
} `json:"Groups"`
|
||||
SubnetID string `json:"SubnetId"`
|
||||
OwnerID string `json:"OwnerId"`
|
||||
TagSet []interface{} `json:"TagSet"`
|
||||
PrivateIPAddress string `json:"PrivateIpAddress"`
|
||||
} `json:"NetworkInterfaces"`
|
||||
}
|
||||
|
||||
func readTestFile(testFile string) []byte {
|
||||
file, err := os.ReadFile(testFile)
|
||||
if err != nil {
|
||||
log.Fatalf("can't read file %s", testFile)
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
type mockedListclustersClient struct {
|
||||
}
|
||||
|
||||
func (c *mockedListclustersClient) ListClusters(context.Context, *ecs.ListClustersInput, ...func(*ecs.Options)) (*ecs.ListClustersOutput, error) {
|
||||
return &ecs.ListClustersOutput{ClusterArns: []string{
|
||||
"arn:aws:ecs:us-east-1:123456789012:cluster/MyCluster",
|
||||
"arn:aws:ecs:us-east-1:123456789012:cluster/MyCluster2",
|
||||
"arn:aws:ecs:us-east-1:123456789012:cluster/MyCluster3",
|
||||
}}, nil
|
||||
}
|
||||
|
||||
type mockedListTasksClient struct{}
|
||||
|
||||
func (c *mockedListTasksClient) ListTasks(ctx context.Context, input *ecs.ListTasksInput, f ...func(*ecs.Options)) (*ecs.ListTasksOutput, error) {
|
||||
return &ecs.ListTasksOutput{TaskArns: []string{
|
||||
"arn:aws:ecs:us-east-1:123456789012:task/MyCluster/74de0355a10a4f979ac495c14EXAMPLE",
|
||||
"arn:aws:ecs:us-east-1:123456789012:task/MyCluster/d789e94343414c25b9f6bd59eEXAMPLE",
|
||||
}}, nil
|
||||
}
|
||||
|
||||
type mockedDescribeTasksClient struct {
|
||||
describeTasks DescribeTasks
|
||||
}
|
||||
|
||||
func (c *mockedDescribeTasksClient) DescribeTasks(ctx context.Context, input *ecs.DescribeTasksInput, f ...func(*ecs.Options)) (*ecs.DescribeTasksOutput, error) {
|
||||
err := json.Unmarshal(readTestFile(DESCRIBE_TASKS_TEST_FILE), &c.describeTasks)
|
||||
if err != nil {
|
||||
log.Fatalf("can't unmarshall file %s", DESCRIBE_TASKS_TEST_FILE)
|
||||
}
|
||||
var tasks []ecsTypes.Task
|
||||
for _, mockedTask := range c.describeTasks.Tasks {
|
||||
if mockedTask.ClusterArn == aws.ToString(input.Cluster) {
|
||||
for _, inputTask := range input.Tasks {
|
||||
if mockedTask.TaskArn == inputTask {
|
||||
var attachments []ecsTypes.Attachment
|
||||
for _, a := range mockedTask.Attachments {
|
||||
var deets []ecsTypes.KeyValuePair
|
||||
for _, detail := range a.Details {
|
||||
deets = append(deets, ecsTypes.KeyValuePair{
|
||||
Name: aws.String(detail.Name),
|
||||
Value: aws.String(detail.Value)})
|
||||
}
|
||||
attachments = append(attachments, ecsTypes.Attachment{
|
||||
Type: aws.String(a.Type),
|
||||
Details: deets,
|
||||
Id: aws.String(a.ID),
|
||||
Status: aws.String(a.Status),
|
||||
})
|
||||
}
|
||||
tasks = append(tasks, ecsTypes.Task{
|
||||
ClusterArn: aws.String(mockedTask.ClusterArn),
|
||||
TaskDefinitionArn: aws.String(mockedTask.TaskDefinitionArn),
|
||||
LaunchType: ecsTypes.LaunchType(*aws.String(mockedTask.LaunchType)),
|
||||
TaskArn: aws.String(mockedTask.TaskArn),
|
||||
Attachments: attachments,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return &ecs.DescribeTasksOutput{Tasks: tasks}, nil
|
||||
}
|
||||
|
||||
type mockedDescribeNetworkInterfacesClient struct {
|
||||
describeNetworkInterfaces DescribeNetworkInterfaces
|
||||
}
|
||||
|
||||
func (c *mockedDescribeNetworkInterfacesClient) DescribeNetworkInterfaces(ctx context.Context, input *ec2.DescribeNetworkInterfacesInput, f ...func(o *ec2.Options)) (*ec2.DescribeNetworkInterfacesOutput, error) {
|
||||
var nics []ec2types.NetworkInterface
|
||||
err := json.Unmarshal(readTestFile(DESCRIBE_NETWORK_INTEFACES_TEST_FILE), &c.describeNetworkInterfaces)
|
||||
if err != nil {
|
||||
log.Fatalf("can't unmarshall file %s", DESCRIBE_NETWORK_INTEFACES_TEST_FILE)
|
||||
}
|
||||
for _, mockedNic := range c.describeNetworkInterfaces.NetworkInterfaces {
|
||||
for _, inputNicID := range input.NetworkInterfaceIds {
|
||||
if mockedNic.NetworkInterfaceID == inputNicID {
|
||||
nics = append(nics, ec2types.NetworkInterface{
|
||||
Association: &ec2types.NetworkInterfaceAssociation{
|
||||
PublicIp: aws.String(mockedNic.Association.PublicIP),
|
||||
},
|
||||
NetworkInterfaceId: aws.String(mockedNic.NetworkInterfaceID)})
|
||||
}
|
||||
}
|
||||
}
|
||||
return &ec2.DescribeNetworkInterfacesOutput{NetworkInterfaces: nics}, nil
|
||||
}
|
||||
|
||||
type mockedDescribeTaskDefinitionInterface struct {
|
||||
}
|
||||
|
||||
func (c *mockedDescribeTaskDefinitionInterface) DescribeTaskDefinition(ctx context.Context, input *ecs.DescribeTaskDefinitionInput, f ...func(o *ecs.Options)) (*ecs.DescribeTaskDefinitionOutput, error) {
|
||||
testTaskDefinition := ecsTypes.TaskDefinition{}
|
||||
testTaskDefinition.TaskRoleArn = aws.String("test123")
|
||||
return &ecs.DescribeTaskDefinitionOutput{TaskDefinition: &testTaskDefinition}, nil
|
||||
}
|
||||
|
||||
func TestECSTasks(t *testing.T) {
|
||||
subtests := []struct {
|
||||
name string
|
||||
@@ -258,17 +24,13 @@ func TestECSTasks(t *testing.T) {
|
||||
verbosity: 2,
|
||||
testModule: ECSTasksModule{
|
||||
|
||||
AWSProfile: "default",
|
||||
AWSRegions: []string{"us-east-1", "us-west-1"},
|
||||
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests")},
|
||||
SkipAdminCheck: true,
|
||||
Goroutines: 30,
|
||||
DescribeNetworkInterfacesClient: &mockedDescribeNetworkInterfacesClient{},
|
||||
DescribeTasksClient: &mockedDescribeTasksClient{},
|
||||
ListTasksClient: &mockedListTasksClient{},
|
||||
ListClustersClient: &mockedListclustersClient{},
|
||||
DescribeTaskDefinitionClient: &mockedDescribeTaskDefinitionInterface{},
|
||||
//IAMSimulatePrincipalPolicyClient: &mockedDescribeTaskDefinitionsClient{},
|
||||
AWSProfile: "default",
|
||||
AWSRegions: []string{"us-east-1", "us-west-1"},
|
||||
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests")},
|
||||
SkipAdminCheck: true,
|
||||
Goroutines: 30,
|
||||
EC2Client: &sdk.MockedEC2Client2{},
|
||||
ECSClient: &sdk.MockedECSClient{},
|
||||
},
|
||||
expectedResult: []MappedECSTask{{
|
||||
Cluster: "MyCluster",
|
||||
@@ -281,7 +43,7 @@ func TestECSTasks(t *testing.T) {
|
||||
internal.MockFileSystem(true)
|
||||
for _, subtest := range subtests {
|
||||
t.Run(subtest.name, func(t *testing.T) {
|
||||
subtest.testModule.ECSTasks(subtest.testModule.OutputFormat, subtest.outputDirectory, subtest.verbosity)
|
||||
subtest.testModule.ECSTasks(subtest.outputDirectory, subtest.verbosity)
|
||||
for index, expectedTask := range subtest.expectedResult {
|
||||
if expectedTask.Cluster != subtest.testModule.MappedECSTasks[index].Cluster {
|
||||
log.Fatal("Cluster name does not match expected value")
|
||||
|
||||
+75
-63
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
@@ -21,9 +22,11 @@ type EKSModule struct {
|
||||
EKSClient sdk.EKSClientInterface
|
||||
IAMClient sdk.AWSIAMClientInterface
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
SkipAdminCheck bool
|
||||
@@ -52,8 +55,8 @@ type Cluster struct {
|
||||
CanPrivEsc string
|
||||
}
|
||||
|
||||
func (m *EKSModule) EKS(outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These stuct values are used by the output module
|
||||
func (m *EKSModule) EKS(outputDirectory string, verbosity int) {
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "eks"
|
||||
@@ -122,10 +125,48 @@ func (m *EKSModule) EKS(outputFormat string, outputDirectory string, verbosity i
|
||||
}
|
||||
}
|
||||
|
||||
// add - if struct is not empty do this. otherwise, dont write anything.
|
||||
if m.pmapperError == nil {
|
||||
m.output.Headers = []string{
|
||||
"Service",
|
||||
// This is the complete list of potential table columns
|
||||
m.output.Headers = []string{
|
||||
"Account",
|
||||
"Region",
|
||||
"Name",
|
||||
//"Endpoint",
|
||||
"Public",
|
||||
//"OIDC",
|
||||
"NodeGroup",
|
||||
"Role",
|
||||
"IsAdminRole?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"Region",
|
||||
"Name",
|
||||
"Endpoint",
|
||||
"Public",
|
||||
"OIDC",
|
||||
"NodeGroup",
|
||||
"Role",
|
||||
"IsAdminRole?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
// Otherwise, use the default columns.
|
||||
} else {
|
||||
tableCols = []string{
|
||||
"Region",
|
||||
"Name",
|
||||
//"Endpoint",
|
||||
@@ -136,58 +177,32 @@ func (m *EKSModule) EKS(outputFormat string, outputDirectory string, verbosity i
|
||||
"IsAdminRole?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
} else {
|
||||
m.output.Headers = []string{
|
||||
"Service",
|
||||
"Region",
|
||||
"Name",
|
||||
//"Endpoint",
|
||||
"Public",
|
||||
//"OIDC",
|
||||
"NodeGroup",
|
||||
"Role",
|
||||
"IsAdminRole?",
|
||||
//"CanPrivEscToAdmin?",
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the pmapper row if there is no pmapper data
|
||||
if m.pmapperError != nil {
|
||||
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
|
||||
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
|
||||
}
|
||||
|
||||
// Table rows
|
||||
|
||||
for i := range m.Clusters {
|
||||
if m.pmapperError == nil {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
m.Clusters[i].AWSService,
|
||||
m.Clusters[i].Region,
|
||||
m.Clusters[i].Name,
|
||||
//m.Clusters[i].Endpoint,
|
||||
m.Clusters[i].Public,
|
||||
//m.Clusters[i].OIDC,
|
||||
m.Clusters[i].NodeGroup,
|
||||
m.Clusters[i].Role,
|
||||
m.Clusters[i].Admin,
|
||||
m.Clusters[i].CanPrivEsc,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
m.Clusters[i].AWSService,
|
||||
m.Clusters[i].Region,
|
||||
m.Clusters[i].Name,
|
||||
//m.Clusters[i].Endpoint,
|
||||
m.Clusters[i].Public,
|
||||
//m.Clusters[i].OIDC,
|
||||
m.Clusters[i].NodeGroup,
|
||||
m.Clusters[i].Role,
|
||||
m.Clusters[i].Admin,
|
||||
//m.Clusters[i].CanPrivEsc,
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
m.Clusters[i].Region,
|
||||
m.Clusters[i].Name,
|
||||
//m.Clusters[i].Endpoint,
|
||||
m.Clusters[i].Public,
|
||||
//m.Clusters[i].OIDC,
|
||||
m.Clusters[i].NodeGroup,
|
||||
m.Clusters[i].Role,
|
||||
m.Clusters[i].Admin,
|
||||
m.Clusters[i].CanPrivEsc,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
var seen []string
|
||||
@@ -199,10 +214,6 @@ func (m *EKSModule) EKS(outputFormat string, outputDirectory string, verbosity i
|
||||
|
||||
if len(m.output.Body) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//m.output.OutputSelector(outputFormat)
|
||||
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
|
||||
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
//m.writeLoot(m.output.FilePath, verbosity)
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -211,9 +222,10 @@ func (m *EKSModule) EKS(outputFormat string, outputDirectory string, verbosity i
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
|
||||
+1
-2
@@ -86,7 +86,6 @@ func TestEks(t *testing.T) {
|
||||
EKSClient: &MockedEKSClientInterface{},
|
||||
//IAMSimulatePrincipalPolicyClient: iam.SimulatePrincipalPolicyAPIClient,
|
||||
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("test")},
|
||||
OutputFormat: "table",
|
||||
AWSProfile: "test",
|
||||
Goroutines: 30,
|
||||
AWSRegions: AWSRegions,
|
||||
@@ -104,7 +103,7 @@ func TestEks(t *testing.T) {
|
||||
internal.MockFileSystem(true)
|
||||
for _, subtest := range subtests {
|
||||
t.Run(subtest.name, func(t *testing.T) {
|
||||
subtest.testModule.EKS(subtest.testModule.OutputFormat, subtest.outputDirectory, subtest.verbosity)
|
||||
subtest.testModule.EKS(subtest.outputDirectory, subtest.verbosity)
|
||||
for index, expectedCluster := range subtest.expectedResult {
|
||||
if expectedCluster.Name != subtest.testModule.Clusters[index].Name {
|
||||
log.Fatal("Cluster name does not match expected value")
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
"github.com/bishopfox/awsservicemap"
|
||||
@@ -18,14 +18,15 @@ import (
|
||||
)
|
||||
|
||||
type ElasticNetworkInterfacesModule struct {
|
||||
//EC2Client *ec2.Client
|
||||
DescribeNetworkInterfacesClient ec2.DescribeNetworkInterfacesAPIClient
|
||||
EC2Client sdk.AWSEC2ClientInterface
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
|
||||
MappedENIs []MappedENI
|
||||
CommandCounter internal.CommandCounter
|
||||
@@ -44,7 +45,7 @@ type MappedENI struct {
|
||||
Description string
|
||||
}
|
||||
|
||||
func (m *ElasticNetworkInterfacesModule) ElasticNetworkInterfaces(outputFormat string, outputDirectory string, verbosity int) {
|
||||
func (m *ElasticNetworkInterfacesModule) ElasticNetworkInterfaces(outputDirectory string, verbosity int) {
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "elastic-network-interfaces"
|
||||
@@ -84,7 +85,7 @@ func (m *ElasticNetworkInterfacesModule) ElasticNetworkInterfaces(outputFormat s
|
||||
receiverDone <- true
|
||||
<-receiverDone
|
||||
|
||||
m.printENIsData(outputFormat, outputDirectory, dataReceiver, verbosity)
|
||||
m.printENIsData(outputDirectory, dataReceiver, verbosity)
|
||||
|
||||
}
|
||||
|
||||
@@ -101,8 +102,9 @@ func (m *ElasticNetworkInterfacesModule) Receiver(receiver chan MappedENI, recei
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ElasticNetworkInterfacesModule) printENIsData(outputFormat string, outputDirectory string, dataReceiver chan MappedENI, verbosity int) {
|
||||
func (m *ElasticNetworkInterfacesModule) printENIsData(outputDirectory string, dataReceiver chan MappedENI, verbosity int) {
|
||||
m.output.Headers = []string{
|
||||
"Account",
|
||||
"ID",
|
||||
"Type",
|
||||
"External IP",
|
||||
@@ -111,10 +113,48 @@ func (m *ElasticNetworkInterfacesModule) printENIsData(outputFormat string, outp
|
||||
"Attached Instance",
|
||||
"Description",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"ID",
|
||||
"Type",
|
||||
"External IP",
|
||||
"Internal IP",
|
||||
"VPC ID",
|
||||
"Attached Instance",
|
||||
"Description",
|
||||
}
|
||||
// Otherwise, use the default columns.
|
||||
} else {
|
||||
tableCols = []string{
|
||||
"ID",
|
||||
"Type",
|
||||
"External IP",
|
||||
"Internal IP",
|
||||
"VPC ID",
|
||||
"Attached Instance",
|
||||
"Description",
|
||||
}
|
||||
}
|
||||
|
||||
for _, eni := range m.MappedENIs {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
eni.ID,
|
||||
eni.Type,
|
||||
eni.ExternalIP,
|
||||
@@ -127,10 +167,7 @@ func (m *ElasticNetworkInterfacesModule) printENIsData(outputFormat string, outp
|
||||
}
|
||||
if len(m.output.Body) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
|
||||
//internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
|
||||
//m.writeLoot(m.output.FilePath)
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -139,9 +176,10 @@ func (m *ElasticNetworkInterfacesModule) printENIsData(outputFormat string, outp
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
@@ -213,50 +251,45 @@ func (m *ElasticNetworkInterfacesModule) executeChecks(r string, wg *sync.WaitGr
|
||||
}
|
||||
|
||||
func (m *ElasticNetworkInterfacesModule) getDescribeNetworkInterfaces(region string, dataReceiver chan MappedENI) {
|
||||
var PaginationControl *string
|
||||
for {
|
||||
DescribeNetworkInterfaces, err := m.DescribeNetworkInterfacesClient.DescribeNetworkInterfaces(
|
||||
context.TODO(),
|
||||
&(ec2.DescribeNetworkInterfacesInput{
|
||||
NextToken: PaginationControl,
|
||||
}),
|
||||
func(o *ec2.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
for _, eni := range DescribeNetworkInterfaces.NetworkInterfaces {
|
||||
status := string(eni.Status)
|
||||
if status == "available" {
|
||||
continue // unused ENI
|
||||
}
|
||||
|
||||
mappedENI := MappedENI{
|
||||
ID: aws.ToString(eni.NetworkInterfaceId),
|
||||
Type: string(eni.InterfaceType),
|
||||
ExternalIP: getPublicIPOfElasticNetworkInterface(eni),
|
||||
PrivateIP: aws.ToString(eni.PrivateIpAddress),
|
||||
VPCID: aws.ToString(eni.VpcId),
|
||||
AttachedInstance: getAttachmentInstanceOfElasticNetworkInterface(eni),
|
||||
Description: aws.ToString(eni.Description),
|
||||
}
|
||||
|
||||
dataReceiver <- mappedENI
|
||||
}
|
||||
|
||||
if DescribeNetworkInterfaces.NextToken != nil {
|
||||
PaginationControl = DescribeNetworkInterfaces.NextToken
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
break
|
||||
}
|
||||
NetworkInterfaces, err := sdk.CachedEC2DescribeNetworkInterfaces(m.EC2Client, aws.ToString(m.Caller.Account), region)
|
||||
|
||||
// var PaginationControl *string
|
||||
// for {
|
||||
// DescribeNetworkInterfaces, err := m.DescribeNetworkInterfacesClient.DescribeNetworkInterfaces(
|
||||
// context.TODO(),
|
||||
// &(ec2.DescribeNetworkInterfacesInput{
|
||||
// NextToken: PaginationControl,
|
||||
// }),
|
||||
// func(o *ec2.Options) {
|
||||
// o.Region = region
|
||||
// },
|
||||
// )
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
for _, eni := range NetworkInterfaces {
|
||||
status := string(eni.Status)
|
||||
if status == "available" {
|
||||
continue // unused ENI
|
||||
}
|
||||
|
||||
mappedENI := MappedENI{
|
||||
ID: aws.ToString(eni.NetworkInterfaceId),
|
||||
Type: string(eni.InterfaceType),
|
||||
ExternalIP: getPublicIPOfElasticNetworkInterface(eni),
|
||||
PrivateIP: aws.ToString(eni.PrivateIpAddress),
|
||||
VPCID: aws.ToString(eni.VpcId),
|
||||
AttachedInstance: getAttachmentInstanceOfElasticNetworkInterface(eni),
|
||||
Description: aws.ToString(eni.Description),
|
||||
}
|
||||
|
||||
dataReceiver <- mappedENI
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getPublicIPOfElasticNetworkInterface(elasticNetworkInterface types.NetworkInterface) string {
|
||||
|
||||
@@ -1,48 +1,59 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
||||
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
)
|
||||
|
||||
type mockedDescribeNetworkInterfacesClient2 struct {
|
||||
describeNetworkInterfaces DescribeNetworkInterfaces
|
||||
}
|
||||
func TestElasticNetworkInterfaces(t *testing.T) {
|
||||
|
||||
func (c *mockedDescribeNetworkInterfacesClient2) DescribeNetworkInterfaces(ctx context.Context, input *ec2.DescribeNetworkInterfacesInput, f ...func(o *ec2.Options)) (*ec2.DescribeNetworkInterfacesOutput, error) {
|
||||
var nics []ec2types.NetworkInterface
|
||||
err := json.Unmarshal(readTestFile(DESCRIBE_NETWORK_INTEFACES_TEST_FILE), &c.describeNetworkInterfaces)
|
||||
if err != nil {
|
||||
log.Fatalf("can't unmarshall file %s", DESCRIBE_NETWORK_INTEFACES_TEST_FILE)
|
||||
m := ElasticNetworkInterfacesModule{
|
||||
AWSProfile: "default",
|
||||
AWSRegions: []string{"us-east-1", "us-west-1"},
|
||||
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests")},
|
||||
EC2Client: &sdk.MockedEC2Client2{},
|
||||
}
|
||||
for _, mockednic := range c.describeNetworkInterfaces.NetworkInterfaces {
|
||||
nics = append(nics, ec2types.NetworkInterface{
|
||||
Association: &ec2types.NetworkInterfaceAssociation{
|
||||
PublicIp: aws.String(mockednic.Association.PublicIP),
|
||||
|
||||
//m.ElasticNetworkInterfaces("table", ".", 3)
|
||||
subtests := []struct {
|
||||
name string
|
||||
testModule ElasticNetworkInterfacesModule
|
||||
expectedResult []MappedENI
|
||||
}{
|
||||
{
|
||||
name: "Test ElasticNetworkInterfaces",
|
||||
testModule: m,
|
||||
expectedResult: []MappedENI{
|
||||
{
|
||||
PrivateIP: "10.0.1.17",
|
||||
ExternalIP: "203.0.113.12",
|
||||
},
|
||||
{
|
||||
PrivateIP: "10.0.1.149",
|
||||
ExternalIP: "198.51.100.0",
|
||||
},
|
||||
},
|
||||
NetworkInterfaceId: aws.String(mockednic.NetworkInterfaceID),
|
||||
PrivateIpAddress: aws.String(mockednic.PrivateIPAddress),
|
||||
VpcId: aws.String(mockednic.VpcID),
|
||||
Attachment: &ec2types.NetworkInterfaceAttachment{InstanceId: aws.String(mockednic.Attachment.InstanceID)},
|
||||
Description: aws.String(mockednic.Description),
|
||||
},
|
||||
}
|
||||
internal.MockFileSystem(true)
|
||||
for _, subtest := range subtests {
|
||||
t.Run(subtest.name, func(t *testing.T) {
|
||||
subtest.testModule.ElasticNetworkInterfaces(".", 3)
|
||||
for index, expectedTask := range subtest.expectedResult {
|
||||
if expectedTask.ExternalIP != subtest.testModule.MappedENIs[index].ExternalIP {
|
||||
t.Errorf("expected %s, got %s", expectedTask.ExternalIP, subtest.testModule.MappedENIs[index].ExternalIP)
|
||||
}
|
||||
if expectedTask.PrivateIP != subtest.testModule.MappedENIs[index].PrivateIP {
|
||||
t.Errorf("expected %s, got %s", expectedTask.PrivateIP, subtest.testModule.MappedENIs[index].PrivateIP)
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
return &ec2.DescribeNetworkInterfacesOutput{NetworkInterfaces: nics}, nil
|
||||
}
|
||||
|
||||
func TestElasticNetworkInterfaces(t *testing.T) {
|
||||
m := ElasticNetworkInterfacesModule{
|
||||
AWSProfile: "default",
|
||||
AWSRegions: []string{"us-east-1", "us-west-1"},
|
||||
Caller: sts.GetCallerIdentityOutput{Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests")},
|
||||
DescribeNetworkInterfacesClient: &mockedDescribeNetworkInterfacesClient2{},
|
||||
}
|
||||
m.ElasticNetworkInterfaces("table", ".", 3)
|
||||
}
|
||||
+58
-21
@@ -55,12 +55,14 @@ type EndpointsModule struct {
|
||||
AppRunnerClient *apprunner.Client
|
||||
LightsailClient *lightsail.Client
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
|
||||
// Main module data
|
||||
Endpoints []Endpoint
|
||||
@@ -83,8 +85,8 @@ type Endpoint struct {
|
||||
|
||||
var oe *smithy.OperationError
|
||||
|
||||
func (m *EndpointsModule) PrintEndpoints(outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These stuct values are used by the output module
|
||||
func (m *EndpointsModule) PrintEndpoints(outputDirectory string, verbosity int) {
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "endpoints"
|
||||
@@ -147,6 +149,7 @@ func (m *EndpointsModule) PrintEndpoints(outputFormat string, outputDirectory st
|
||||
})
|
||||
|
||||
m.output.Headers = []string{
|
||||
"Account",
|
||||
"Service",
|
||||
"Region",
|
||||
"Name",
|
||||
@@ -156,11 +159,48 @@ func (m *EndpointsModule) PrintEndpoints(outputFormat string, outputDirectory st
|
||||
"Public",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"Service",
|
||||
"Region",
|
||||
"Name",
|
||||
"Endpoint",
|
||||
"Port",
|
||||
"Protocol",
|
||||
"Public",
|
||||
}
|
||||
// Otherwise, use the default columns.
|
||||
} else {
|
||||
tableCols = []string{
|
||||
"Service",
|
||||
"Region",
|
||||
"Name",
|
||||
"Endpoint",
|
||||
"Port",
|
||||
"Protocol",
|
||||
"Public",
|
||||
}
|
||||
}
|
||||
|
||||
// Table rows
|
||||
for i := range m.Endpoints {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
m.Endpoints[i].AWSService,
|
||||
m.Endpoints[i].Region,
|
||||
m.Endpoints[i].Name,
|
||||
@@ -174,10 +214,6 @@ func (m *EndpointsModule) PrintEndpoints(outputFormat string, outputDirectory st
|
||||
}
|
||||
if len(m.output.Body) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//m.output.OutputSelector(outputFormat)
|
||||
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
|
||||
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
//m.writeLoot(m.output.FilePath, verbosity)
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -186,9 +222,10 @@ func (m *EndpointsModule) PrintEndpoints(outputFormat string, outputDirectory st
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
@@ -545,7 +582,7 @@ func (m *EndpointsModule) getMqBrokersPerRegion(r string, wg *sync.WaitGroup, se
|
||||
m.CommandCounter.Error++
|
||||
continue
|
||||
}
|
||||
if BrokerDetails.PubliclyAccessible {
|
||||
if aws.ToBool(BrokerDetails.PubliclyAccessible) {
|
||||
public = "True"
|
||||
} else {
|
||||
public = "False"
|
||||
@@ -615,7 +652,7 @@ func (m *EndpointsModule) getOpenSearchPerRegion(r string, wg *sync.WaitGroup, s
|
||||
var endpoint string
|
||||
var kibana_endpoint string
|
||||
|
||||
// This exits thie function if an opensearch domain exists but there is no endpoint
|
||||
// This exits the function if an opensearch domain exists but there is no endpoint
|
||||
if raw_endpoint == nil {
|
||||
return
|
||||
} else {
|
||||
@@ -1386,7 +1423,7 @@ func (m *EndpointsModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup,
|
||||
endpoint := aws.ToString(instance.Endpoint.Address)
|
||||
awsService := "RDS"
|
||||
|
||||
if instance.PubliclyAccessible {
|
||||
if aws.ToBool(instance.PubliclyAccessible) {
|
||||
public = "True"
|
||||
} else {
|
||||
public = "False"
|
||||
@@ -1397,7 +1434,7 @@ func (m *EndpointsModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup,
|
||||
Region: r,
|
||||
Name: name,
|
||||
Endpoint: endpoint,
|
||||
Port: port,
|
||||
Port: aws.ToInt32(port),
|
||||
Protocol: aws.ToString(instance.Engine),
|
||||
Public: public,
|
||||
}
|
||||
@@ -1442,7 +1479,7 @@ func (m *EndpointsModule) getRedshiftEndPointsPerRegion(r string, wg *sync.WaitG
|
||||
//id := workspace.Id
|
||||
endpoint := aws.ToString(cluster.Endpoint.Address)
|
||||
|
||||
if cluster.PubliclyAccessible {
|
||||
if aws.ToBool(cluster.PubliclyAccessible) {
|
||||
public = "True"
|
||||
} else {
|
||||
public = "False"
|
||||
@@ -1455,7 +1492,7 @@ func (m *EndpointsModule) getRedshiftEndPointsPerRegion(r string, wg *sync.WaitG
|
||||
Region: r,
|
||||
Name: name,
|
||||
Endpoint: endpoint,
|
||||
Port: port,
|
||||
Port: aws.ToInt32(port),
|
||||
Protocol: protocol,
|
||||
Public: public,
|
||||
}
|
||||
|
||||
+49
-14
@@ -6,6 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
@@ -34,12 +35,14 @@ import (
|
||||
|
||||
type EnvsModule struct {
|
||||
// General configuration data
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSProfile string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
WrapTable bool
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSProfile string
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
Goroutines int
|
||||
WrapTable bool
|
||||
|
||||
// Service Clients
|
||||
ECSClient *ecs.Client
|
||||
@@ -65,8 +68,8 @@ type EnvironmentVariable struct {
|
||||
environmentVarValue string
|
||||
}
|
||||
|
||||
func (m *EnvsModule) PrintEnvs(outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These stuct values are used by the output module
|
||||
func (m *EnvsModule) PrintEnvs(outputDirectory string, verbosity int) {
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "env-vars"
|
||||
@@ -119,6 +122,7 @@ func (m *EnvsModule) PrintEnvs(outputFormat string, outputDirectory string, verb
|
||||
|
||||
// Table headers
|
||||
m.output.Headers = []string{
|
||||
"Account",
|
||||
"Service",
|
||||
"Region",
|
||||
"Name",
|
||||
@@ -126,10 +130,42 @@ func (m *EnvsModule) PrintEnvs(outputFormat string, outputDirectory string, verb
|
||||
"Value",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"Service",
|
||||
"Region",
|
||||
"Name",
|
||||
"Key",
|
||||
"Value",
|
||||
}
|
||||
} else {
|
||||
tableCols = []string{
|
||||
"Service",
|
||||
"Region",
|
||||
"Name",
|
||||
"Key",
|
||||
"Value",
|
||||
}
|
||||
}
|
||||
|
||||
//Table rows
|
||||
for _, envVar := range m.EnvironmentVariables {
|
||||
m.output.Body = append(
|
||||
m.output.Body, []string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
envVar.service,
|
||||
envVar.region,
|
||||
envVar.name,
|
||||
@@ -141,9 +177,7 @@ func (m *EnvsModule) PrintEnvs(outputFormat string, outputDirectory string, verb
|
||||
if len(m.output.Body) > 0 {
|
||||
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//m.output.OutputSelector(outputFormat)
|
||||
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
|
||||
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -152,9 +186,10 @@ func (m *EnvsModule) PrintEnvs(outputFormat string, outputDirectory string, verb
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
|
||||
+54
-17
@@ -7,6 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
@@ -27,12 +28,14 @@ type FilesystemsModule struct {
|
||||
EFSClient *efs.Client
|
||||
FSxClient *fsx.Client
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
|
||||
// Main module data
|
||||
Filesystems []FilesystemObject
|
||||
@@ -55,8 +58,8 @@ type FilesystemObject struct {
|
||||
Permissions string
|
||||
}
|
||||
|
||||
func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These stuct values are used by the output module
|
||||
func (m *FilesystemsModule) PrintFilesystems(outputDirectory string, verbosity int) {
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "filesystems"
|
||||
@@ -107,7 +110,7 @@ func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirector
|
||||
})
|
||||
|
||||
m.output.Headers = []string{
|
||||
"Service",
|
||||
"Account",
|
||||
"Region",
|
||||
"Name",
|
||||
"DNS Name",
|
||||
@@ -117,12 +120,48 @@ func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirector
|
||||
"Permissions",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"Region",
|
||||
"Name",
|
||||
"DNS Name",
|
||||
//"IP",
|
||||
"Mount Target",
|
||||
"Policy",
|
||||
"Permissions",
|
||||
}
|
||||
} else {
|
||||
tableCols = []string{
|
||||
"Region",
|
||||
"Name",
|
||||
"DNS Name",
|
||||
//"IP",
|
||||
"Mount Target",
|
||||
"Policy",
|
||||
"Permissions",
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Table rows
|
||||
for i := range m.Filesystems {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
m.Filesystems[i].AWSService,
|
||||
aws.ToString(m.Caller.Account),
|
||||
m.Filesystems[i].Region,
|
||||
m.Filesystems[i].Name,
|
||||
m.Filesystems[i].DnsName,
|
||||
@@ -137,10 +176,7 @@ func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirector
|
||||
if len(m.output.Body) > 0 {
|
||||
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//m.output.OutputSelector(outputFormat)
|
||||
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
|
||||
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
//m.writeLoot(m.output.FilePath, verbosity)
|
||||
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -149,9 +185,10 @@ func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirector
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
|
||||
+44
-16
@@ -7,6 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -20,13 +21,15 @@ import (
|
||||
|
||||
type IamSimulatorModule struct {
|
||||
// General configuration data
|
||||
IAMClient sdk.AWSIAMClientInterface
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
IAMClient sdk.AWSIAMClientInterface
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
|
||||
// Main module data
|
||||
SimulatorResults []SimulatorResult
|
||||
@@ -64,9 +67,9 @@ var (
|
||||
TxtLogger = internal.TxtLogger()
|
||||
)
|
||||
|
||||
func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string, resource string, outputFormat string, outputDirectory string, verbosity int) {
|
||||
func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string, resource string, outputDirectory string, verbosity int) {
|
||||
|
||||
// These stuct values are used by the output module
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "iam-simulator"
|
||||
@@ -149,11 +152,36 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
|
||||
|
||||
// Regardless of what options were selected, for now at least, we will always print the data using the output module (table/csv mode)
|
||||
m.output.Headers = []string{
|
||||
"Service",
|
||||
"Account",
|
||||
"Principal",
|
||||
"Query",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"Principal",
|
||||
"Query",
|
||||
}
|
||||
} else {
|
||||
tableCols = []string{
|
||||
"Principal",
|
||||
"Query",
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(m.SimulatorResults, func(i, j int) bool {
|
||||
return m.SimulatorResults[i].Query < m.SimulatorResults[j].Query
|
||||
})
|
||||
@@ -163,7 +191,7 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
m.SimulatorResults[i].AWSService,
|
||||
aws.ToString(m.Caller.Account),
|
||||
m.SimulatorResults[i].Principal,
|
||||
m.SimulatorResults[i].Query,
|
||||
},
|
||||
@@ -172,8 +200,7 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
|
||||
}
|
||||
if len(m.output.Body) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
|
||||
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -182,9 +209,10 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
|
||||
+89
-113
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/iam"
|
||||
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
|
||||
@@ -24,11 +23,13 @@ import (
|
||||
|
||||
type InstancesModule struct {
|
||||
// General configuration data
|
||||
EC2Client *ec2.Client
|
||||
IAMClient sdk.AWSIAMClientInterface
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
EC2Client sdk.AWSEC2ClientInterface
|
||||
IAMClient sdk.AWSIAMClientInterface
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
Goroutines int
|
||||
UserDataAttributesOnly bool
|
||||
AWSProfile string
|
||||
@@ -63,8 +64,8 @@ type MappedInstance struct {
|
||||
CanPrivEsc string
|
||||
}
|
||||
|
||||
func (m *InstancesModule) Instances(filter string, outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These stuct values are used by the output module
|
||||
func (m *InstancesModule) Instances(filter string, outputDirectory string, verbosity int) {
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "instances"
|
||||
@@ -147,9 +148,9 @@ func (m *InstancesModule) Instances(filter string, outputFormat string, outputDi
|
||||
// This conditional block will either dump the userData attribute content or the general instances data, depending on what you select via command line.
|
||||
//fmt.Printf("\n[*] Preparing output...\n\n")
|
||||
if m.UserDataAttributesOnly {
|
||||
m.printInstancesUserDataAttributesOnly(outputFormat, outputDirectory, dataReceiver)
|
||||
m.printInstancesUserDataAttributesOnly(outputDirectory, dataReceiver)
|
||||
} else {
|
||||
m.printGeneralInstanceData(outputFormat, outputDirectory, dataReceiver, verbosity)
|
||||
m.printGeneralInstanceData(outputDirectory, dataReceiver, verbosity)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -167,7 +168,7 @@ func (m *InstancesModule) Receiver(receiver chan MappedInstance, receiverDone ch
|
||||
}
|
||||
}
|
||||
|
||||
func (m *InstancesModule) printInstancesUserDataAttributesOnly(outputFormat string, outputDirectory string, dataReceiver chan MappedInstance) {
|
||||
func (m *InstancesModule) printInstancesUserDataAttributesOnly(outputDirectory string, dataReceiver chan MappedInstance) {
|
||||
defer func() {
|
||||
m.output.CallingModule = "instances"
|
||||
}()
|
||||
@@ -217,12 +218,39 @@ func (m *InstancesModule) printInstancesUserDataAttributesOnly(outputFormat stri
|
||||
}
|
||||
}
|
||||
|
||||
func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDirectory string, dataReceiver chan MappedInstance, verbosity int) {
|
||||
func (m *InstancesModule) printGeneralInstanceData(outputDirectory string, dataReceiver chan MappedInstance, verbosity int) {
|
||||
// Prepare Table headers
|
||||
//m.output.Headers = table.Row{
|
||||
if m.pmapperError == nil {
|
||||
m.output.Headers = []string{
|
||||
"Account",
|
||||
//"ID",
|
||||
"Name",
|
||||
//"Arn",
|
||||
"ID",
|
||||
"Zone",
|
||||
"State",
|
||||
"External IP",
|
||||
"Internal IP",
|
||||
"Role",
|
||||
"IsAdminRole?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
|
||||
m.output.Headers = []string{
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
//"ID",
|
||||
"Name",
|
||||
//"Arn",
|
||||
@@ -235,8 +263,9 @@ func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDi
|
||||
"IsAdminRole?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
// Otherwise, use the default columns.
|
||||
} else {
|
||||
m.output.Headers = []string{
|
||||
tableCols = []string{
|
||||
//"ID",
|
||||
"Name",
|
||||
//"Arn",
|
||||
@@ -247,59 +276,40 @@ func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDi
|
||||
"Internal IP",
|
||||
"Role",
|
||||
"IsAdminRole?",
|
||||
//"CanPrivEscToAdmin?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
}
|
||||
|
||||
//Table rows
|
||||
if m.pmapperError == nil {
|
||||
// Remove the pmapper row if there is no pmapper data
|
||||
if m.pmapperError != nil {
|
||||
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
|
||||
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
|
||||
}
|
||||
|
||||
for _, instance := range m.MappedInstances {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
//table.Row{
|
||||
[]string{
|
||||
//instance.ID,
|
||||
instance.Name,
|
||||
//instance.Arn,
|
||||
instance.ID,
|
||||
instance.AvailabilityZone,
|
||||
instance.State,
|
||||
instance.ExternalIP,
|
||||
instance.PrivateIP,
|
||||
instance.Role,
|
||||
instance.Admin,
|
||||
instance.CanPrivEsc,
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
for _, instance := range m.MappedInstances {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
//table.Row{
|
||||
[]string{
|
||||
//instance.ID,
|
||||
instance.Name,
|
||||
//instance.Arn,
|
||||
instance.ID,
|
||||
instance.AvailabilityZone,
|
||||
instance.State,
|
||||
instance.ExternalIP,
|
||||
instance.PrivateIP,
|
||||
instance.Role,
|
||||
instance.Admin,
|
||||
//instance.CanPrivEsc,
|
||||
},
|
||||
)
|
||||
}
|
||||
//Table rows
|
||||
for _, instance := range m.MappedInstances {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
//table.Row{
|
||||
[]string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
//instance.ID,
|
||||
instance.Name,
|
||||
//instance.Arn,
|
||||
instance.ID,
|
||||
instance.AvailabilityZone,
|
||||
instance.State,
|
||||
instance.ExternalIP,
|
||||
instance.PrivateIP,
|
||||
instance.Role,
|
||||
instance.Admin,
|
||||
instance.CanPrivEsc,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if len(m.output.Body) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
////m.output.OutputSelector(outputFormat)
|
||||
//utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
|
||||
//internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
|
||||
//m.writeLoot(m.output.FilePath)
|
||||
o := internal.OutputClient{
|
||||
@@ -310,9 +320,10 @@ func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDi
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
@@ -440,27 +451,17 @@ func (m *InstancesModule) executeChecks(instancesToSearch []string, r string, wg
|
||||
}
|
||||
|
||||
func (m *InstancesModule) getInstanceUserDataAttribute(instanceID *string, region string) (userData *string, err error) {
|
||||
|
||||
Attributes, err := m.EC2Client.DescribeInstanceAttribute(
|
||||
context.TODO(),
|
||||
&ec2.DescribeInstanceAttributeInput{
|
||||
InstanceId: instanceID,
|
||||
Attribute: types.InstanceAttributeName("userData"),
|
||||
},
|
||||
func(o *ec2.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
UserData, err := sdk.CachedEC2DescribeInstanceAttributeUserData(m.EC2Client, aws.ToString(m.Caller.Account), region, aws.ToString(instanceID))
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
return nil, err
|
||||
} else {
|
||||
if Attributes.UserData.Value == nil {
|
||||
if UserData == "" {
|
||||
return aws.String("NoUserData"), nil
|
||||
} else {
|
||||
data, _ := base64.StdEncoding.DecodeString(*Attributes.UserData.Value)
|
||||
data, _ := base64.StdEncoding.DecodeString(UserData)
|
||||
return aws.String(string(data)), nil
|
||||
}
|
||||
}
|
||||
@@ -470,47 +471,22 @@ func (m *InstancesModule) getInstanceUserDataAttribute(instanceID *string, regio
|
||||
func (m *InstancesModule) getDescribeInstances(instancesToSearch []string, region string, dataReceiver chan MappedInstance) {
|
||||
|
||||
// The "PaginationControl" value is nil when there's no more data to return.
|
||||
var PaginationControl *string
|
||||
for {
|
||||
|
||||
DescribeInstances, err := m.EC2Client.DescribeInstances(
|
||||
context.TODO(),
|
||||
&(ec2.DescribeInstancesInput{
|
||||
NextToken: PaginationControl,
|
||||
}),
|
||||
func(o *ec2.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
Instances, err := sdk.CachedEC2DescribeInstances(m.EC2Client, aws.ToString(m.Caller.Account), region)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
for _, instance := range Instances {
|
||||
if instancesToSearch[0] == "all" || internal.Contains(aws.ToString(instance.InstanceId), instancesToSearch) {
|
||||
m.loadInstanceData(instance, region, dataReceiver)
|
||||
}
|
||||
|
||||
for _, reservation := range DescribeInstances.Reservations {
|
||||
accountId := reservation.OwnerId
|
||||
|
||||
for _, instance := range reservation.Instances {
|
||||
|
||||
if instancesToSearch[0] == "all" || internal.Contains(aws.ToString(instance.InstanceId), instancesToSearch) {
|
||||
m.loadInstanceData(instance, region, accountId, dataReceiver)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The "NextToken" value is nil when there's no more data to return.
|
||||
if DescribeInstances.NextToken != nil {
|
||||
PaginationControl = DescribeInstances.NextToken
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (m *InstancesModule) loadInstanceData(instance types.Instance, region string, accountId *string, dataReceiver chan MappedInstance) {
|
||||
func (m *InstancesModule) loadInstanceData(instance types.Instance, region string, dataReceiver chan MappedInstance) {
|
||||
|
||||
var profile string
|
||||
var externalIP string
|
||||
@@ -552,7 +528,7 @@ func (m *InstancesModule) loadInstanceData(instance types.Instance, region strin
|
||||
dataReceiver <- MappedInstance{
|
||||
ID: aws.ToString(instance.InstanceId),
|
||||
Name: aws.ToString(&name),
|
||||
Arn: fmt.Sprintf("arn:aws:ec2:%s:%s:instance/%s", region, aws.ToString(accountId), aws.ToString(instance.InstanceId)),
|
||||
Arn: fmt.Sprintf("arn:aws:ec2:%s:%s:instance/%s", region, aws.ToString(m.Caller.Account), aws.ToString(instance.InstanceId)),
|
||||
AvailabilityZone: aws.ToString(instance.Placement.AvailabilityZone),
|
||||
State: string(instance.State.Name),
|
||||
ExternalIP: externalIP,
|
||||
|
||||
+1864
-614
File diff suppressed because it is too large.
Load diff
+63
-46
@@ -7,6 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
@@ -25,9 +26,11 @@ type LambdasModule struct {
|
||||
LambdaClient *lambda.Client
|
||||
IAMClient sdk.AWSIAMClientInterface
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
SkipAdminCheck bool
|
||||
@@ -49,15 +52,16 @@ type Lambda struct {
|
||||
Region string
|
||||
Type string
|
||||
Name string
|
||||
Arn string
|
||||
Role string
|
||||
Admin string
|
||||
CanPrivEsc string
|
||||
Public string
|
||||
}
|
||||
|
||||
func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string, verbosity int) {
|
||||
func (m *LambdasModule) PrintLambdas(outputDirectory string, verbosity int) {
|
||||
|
||||
// These stuct values are used by the output module
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "lambda"
|
||||
@@ -123,24 +127,52 @@ func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string
|
||||
<-receiverDone
|
||||
|
||||
// add - if struct is not empty do this. otherwise, dont write anything.
|
||||
if m.pmapperError == nil {
|
||||
m.output.Headers = []string{
|
||||
"Service",
|
||||
m.output.Headers = []string{
|
||||
"Account",
|
||||
"Region",
|
||||
"Name",
|
||||
"Arn",
|
||||
"Role",
|
||||
"IsAdminRole?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"Region",
|
||||
"Resource",
|
||||
"Arn",
|
||||
"Role",
|
||||
"IsAdminRole?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
// Otherwise, use the default columns.
|
||||
} else {
|
||||
m.output.Headers = []string{
|
||||
tableCols = []string{
|
||||
"Service",
|
||||
"Region",
|
||||
"Resource",
|
||||
"Name",
|
||||
"Role",
|
||||
"IsAdminRole?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
}
|
||||
// Remove the pmapper row if there is no pmapper data
|
||||
if m.pmapperError != nil {
|
||||
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
|
||||
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
|
||||
}
|
||||
|
||||
sort.Slice(m.Lambdas, func(i, j int) bool {
|
||||
return m.Lambdas[i].AWSService < m.Lambdas[j].AWSService
|
||||
@@ -149,41 +181,24 @@ func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string
|
||||
// Table rows
|
||||
for i := range m.Lambdas {
|
||||
|
||||
if m.pmapperError == nil {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
m.Lambdas[i].AWSService,
|
||||
m.Lambdas[i].Region,
|
||||
//m.Lambdas[i].Type,
|
||||
m.Lambdas[i].Name,
|
||||
m.Lambdas[i].Role,
|
||||
m.Lambdas[i].Admin,
|
||||
m.Lambdas[i].CanPrivEsc,
|
||||
//m.Lambdas[i].Public,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
m.Lambdas[i].AWSService,
|
||||
m.Lambdas[i].Region,
|
||||
//m.Lambdas[i].Type,
|
||||
m.Lambdas[i].Name,
|
||||
m.Lambdas[i].Role,
|
||||
m.Lambdas[i].Admin,
|
||||
//m.Lambdas[i].CanPrivEsc,
|
||||
//m.Lambdas[i].Public,
|
||||
},
|
||||
)
|
||||
}
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
m.Lambdas[i].Region,
|
||||
//m.Lambdas[i].Type,
|
||||
m.Lambdas[i].Name,
|
||||
m.Lambdas[i].Arn,
|
||||
m.Lambdas[i].Role,
|
||||
m.Lambdas[i].Admin,
|
||||
m.Lambdas[i].CanPrivEsc,
|
||||
//m.Lambdas[i].Public,
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
if len(m.output.Body) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
|
||||
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -192,9 +207,10 @@ func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
@@ -300,13 +316,14 @@ func (m *LambdasModule) getLambdasPerRegion(r string, wg *sync.WaitGroup, semaph
|
||||
}
|
||||
|
||||
for _, function := range functions {
|
||||
//arn := aws.ToString(function.FunctionArn)
|
||||
arn := aws.ToString(function.FunctionArn)
|
||||
name := aws.ToString(function.FunctionName)
|
||||
role := aws.ToString(function.Role)
|
||||
|
||||
dataReceiver <- Lambda{
|
||||
AWSService: "Lambda",
|
||||
Name: name,
|
||||
Arn: arn,
|
||||
Region: r,
|
||||
Type: "",
|
||||
Role: role,
|
||||
|
||||
+52
-20
@@ -65,13 +65,15 @@ type NetworkPortsModule struct {
|
||||
LightsailClient *lightsail.Client
|
||||
RDSClient *rds.Client
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
Verbosity int
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
Verbosity int
|
||||
|
||||
// Main module data
|
||||
IPv4_Private []NetworkService
|
||||
@@ -136,8 +138,8 @@ var naclToSG = map[string]string{
|
||||
|
||||
var validSecurityGroupProtocols = []string{"-1", "tcp", "udp"}
|
||||
|
||||
func (m *NetworkPortsModule) PrintNetworkPorts(outputFormat string, outputDirectory string) {
|
||||
// These stuct values are used by the output module
|
||||
func (m *NetworkPortsModule) PrintNetworkPorts(outputDirectory string) {
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = m.Verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "network-ports"
|
||||
@@ -184,6 +186,7 @@ func (m *NetworkPortsModule) PrintNetworkPorts(outputFormat string, outputDirect
|
||||
|
||||
// add - if struct is not empty do this. otherwise, dont write anything.
|
||||
m.output.Headers = []string{
|
||||
"Account",
|
||||
"Service",
|
||||
"Region",
|
||||
"Protocol",
|
||||
@@ -191,6 +194,36 @@ func (m *NetworkPortsModule) PrintNetworkPorts(outputFormat string, outputDirect
|
||||
"Ports",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"Service",
|
||||
"Region",
|
||||
"Protocol",
|
||||
"Host",
|
||||
"Ports",
|
||||
}
|
||||
} else {
|
||||
tableCols = []string{
|
||||
"Service",
|
||||
"Region",
|
||||
"Protocol",
|
||||
"Host",
|
||||
"Ports",
|
||||
}
|
||||
}
|
||||
|
||||
// Table rows
|
||||
for _, arr := range [][]NetworkService{m.IPv4_Private, m.IPv4_Public, m.IPv6} {
|
||||
for _, i := range arr {
|
||||
@@ -198,6 +231,7 @@ func (m *NetworkPortsModule) PrintNetworkPorts(outputFormat string, outputDirect
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
i.AWSService,
|
||||
i.Region,
|
||||
i.Protocol,
|
||||
@@ -211,9 +245,6 @@ func (m *NetworkPortsModule) PrintNetworkPorts(outputFormat string, outputDirect
|
||||
|
||||
if len(m.IPv4_Private) > 0 || len(m.IPv4_Public) > 0 || len(m.IPv6) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//m.output.OutputSelector(outputFormat)
|
||||
//internal.OutputSelector(m.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
//m.writeLoot(m.output.FilePath)
|
||||
o := internal.OutputClient{
|
||||
Verbosity: m.Verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -222,9 +253,10 @@ func (m *NetworkPortsModule) PrintNetworkPorts(outputFormat string, outputDirect
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
@@ -742,7 +774,7 @@ func (m *NetworkPortsModule) getElastiCacheNetworkPortsPerRegion(r string, dataR
|
||||
// Memcached
|
||||
if cluster.ConfigurationEndpoint != nil {
|
||||
ipv4_private = addHost(ipv4_private, aws.ToString(cluster.ConfigurationEndpoint.Address))
|
||||
tcpPortsInts = addPort(tcpPortsInts, cluster.ConfigurationEndpoint.Port)
|
||||
tcpPortsInts = addPort(tcpPortsInts, aws.ToInt32(cluster.ConfigurationEndpoint.Port))
|
||||
} else {
|
||||
replicationGroupId := aws.ToString(cluster.ReplicationGroupId)
|
||||
for _, group := range nodes {
|
||||
@@ -751,7 +783,7 @@ func (m *NetworkPortsModule) getElastiCacheNetworkPortsPerRegion(r string, dataR
|
||||
// Primary
|
||||
if g.PrimaryEndpoint != nil && !strContains(reportedClusters, aws.ToString(g.PrimaryEndpoint.Address)) {
|
||||
ipv4_private = addHost(ipv4_private, aws.ToString(g.PrimaryEndpoint.Address))
|
||||
tcpPortsInts = addPort(tcpPortsInts, g.PrimaryEndpoint.Port)
|
||||
tcpPortsInts = addPort(tcpPortsInts, aws.ToInt32(g.PrimaryEndpoint.Port))
|
||||
|
||||
reportedClusters = addHost(reportedClusters, aws.ToString(g.PrimaryEndpoint.Address))
|
||||
}
|
||||
@@ -759,7 +791,7 @@ func (m *NetworkPortsModule) getElastiCacheNetworkPortsPerRegion(r string, dataR
|
||||
// Reader
|
||||
if g.ReaderEndpoint != nil && !strContains(reportedClusters, aws.ToString(g.ReaderEndpoint.Address)) {
|
||||
ipv4_private = addHost(ipv4_private, aws.ToString(g.ReaderEndpoint.Address))
|
||||
tcpPortsInts = addPort(tcpPortsInts, g.ReaderEndpoint.Port)
|
||||
tcpPortsInts = addPort(tcpPortsInts, aws.ToInt32(g.ReaderEndpoint.Port))
|
||||
|
||||
reportedClusters = addHost(reportedClusters, aws.ToString(g.ReaderEndpoint.Address))
|
||||
}
|
||||
@@ -769,7 +801,7 @@ func (m *NetworkPortsModule) getElastiCacheNetworkPortsPerRegion(r string, dataR
|
||||
if aws.ToString(m.CacheClusterId) == aws.ToString(cluster.CacheClusterId) {
|
||||
if m.ReadEndpoint != nil {
|
||||
ipv4_private = addHost(ipv4_private, aws.ToString(m.ReadEndpoint.Address))
|
||||
tcpPortsInts = addPort(tcpPortsInts, m.ReadEndpoint.Port)
|
||||
tcpPortsInts = addPort(tcpPortsInts, aws.ToInt32(m.ReadEndpoint.Port))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1047,7 +1079,7 @@ func (m *NetworkPortsModule) getRdsNetworkPortsPerRegion(r string, dataReceiver
|
||||
for _, instance := range DBInstances {
|
||||
if aws.ToString(instance.DBInstanceStatus) == "available" {
|
||||
host := []string{aws.ToString(instance.Endpoint.Address)}
|
||||
var port int32 = instance.Endpoint.Port
|
||||
var port int32 = aws.ToInt32(instance.Endpoint.Port)
|
||||
|
||||
var groups []SecurityGroup
|
||||
for _, group := range instance.VpcSecurityGroups {
|
||||
|
||||
+49
-14
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
@@ -19,12 +20,14 @@ type OrgModule struct {
|
||||
OrganizationsClient sdk.OrganizationsClientInterface
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
SkipAdminCheck bool
|
||||
WrapTable bool
|
||||
DescribeOrgOutput *types.Organization
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
SkipAdminCheck bool
|
||||
WrapTable bool
|
||||
DescribeOrgOutput *types.Organization
|
||||
|
||||
// Main module data
|
||||
Accounts []Account
|
||||
@@ -52,8 +55,8 @@ type Account struct {
|
||||
OrgId string
|
||||
}
|
||||
|
||||
func (m *OrgModule) PrintOrgAccounts(outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These stuct values are used by the output module
|
||||
func (m *OrgModule) PrintOrgAccounts(outputDirectory string, verbosity int) {
|
||||
// These struct values are used by the output module
|
||||
var err error
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
@@ -85,6 +88,7 @@ func (m *OrgModule) PrintOrgAccounts(outputFormat string, outputDirectory string
|
||||
}
|
||||
|
||||
m.output.Headers = []string{
|
||||
"Account",
|
||||
"Name",
|
||||
"ID",
|
||||
"isManagementAccount?",
|
||||
@@ -92,12 +96,44 @@ func (m *OrgModule) PrintOrgAccounts(outputFormat string, outputDirectory string
|
||||
"Email",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"Name",
|
||||
"ID",
|
||||
"isManagementAccount?",
|
||||
"Status",
|
||||
"Email",
|
||||
}
|
||||
// Otherwise, use the default columns.
|
||||
} else {
|
||||
tableCols = []string{
|
||||
"Name",
|
||||
"ID",
|
||||
"isManagementAccount?",
|
||||
"Status",
|
||||
"Email",
|
||||
}
|
||||
}
|
||||
|
||||
// Table rows
|
||||
|
||||
for i := range m.Accounts {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
m.Accounts[i].Name,
|
||||
m.Accounts[i].Id,
|
||||
strconv.FormatBool(m.Accounts[i].isManagementAccount),
|
||||
@@ -109,8 +145,6 @@ func (m *OrgModule) PrintOrgAccounts(outputFormat string, outputDirectory string
|
||||
|
||||
if len(m.output.Body) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
//m.writeLoot(m.output.FilePath, verbosity)
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -119,9 +153,10 @@ func (m *OrgModule) PrintOrgAccounts(outputFormat string, outputDirectory string
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
@@ -272,7 +307,7 @@ func (m *OrgModule) addOrgAccount() {
|
||||
isManagementAccount: false,
|
||||
Name: "This account",
|
||||
Id: aws.ToString(m.Caller.Account),
|
||||
Email: "Unkonwn",
|
||||
Email: "Unknown",
|
||||
Arn: aws.ToString(m.Caller.Arn),
|
||||
Status: "ACTIVE",
|
||||
})
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -22,12 +23,14 @@ type OutboundAssumedRolesModule struct {
|
||||
// General configuration data
|
||||
CloudTrailClient *cloudtrail.Client
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
|
||||
// Main module data
|
||||
OutboundAssumeRoleEntries []OutboundAssumeRoleEntry
|
||||
@@ -114,8 +117,8 @@ type CloudTrailEvent struct {
|
||||
} `json:"tlsDetails"`
|
||||
}
|
||||
|
||||
func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These stuct values are used by the output module
|
||||
func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputDirectory string, verbosity int) {
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "outbound-assumed-roles"
|
||||
@@ -161,7 +164,7 @@ func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFor
|
||||
<-receiverDone
|
||||
|
||||
m.output.Headers = []string{
|
||||
"Service",
|
||||
"Account",
|
||||
"Region",
|
||||
"Type",
|
||||
//"Source Account",
|
||||
@@ -171,12 +174,48 @@ func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFor
|
||||
"Log Entry Timestamp",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"Region",
|
||||
"Type",
|
||||
//"Source Account",
|
||||
"Source Principal",
|
||||
//"Destination Account",
|
||||
"Destination Principal",
|
||||
"Log Entry Timestamp",
|
||||
}
|
||||
// Otherwise, use the default columns.
|
||||
} else {
|
||||
|
||||
tableCols = []string{
|
||||
"Region",
|
||||
"Type",
|
||||
//"Source Account",
|
||||
"Source Principal",
|
||||
//"Destination Account",
|
||||
"Destination Principal",
|
||||
"Log Entry Timestamp",
|
||||
}
|
||||
}
|
||||
|
||||
// Table rows
|
||||
for i := range m.OutboundAssumeRoleEntries {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
m.OutboundAssumeRoleEntries[i].AWSService,
|
||||
aws.ToString(m.Caller.Account),
|
||||
m.OutboundAssumeRoleEntries[i].Region,
|
||||
m.OutboundAssumeRoleEntries[i].Type,
|
||||
//m.OutboundAssumeRoleEntries[i].SourceAccount,
|
||||
@@ -191,9 +230,6 @@ func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFor
|
||||
if len(m.output.Body) > 0 {
|
||||
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//m.output.OutputSelector(outputFormat)
|
||||
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
|
||||
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -202,9 +238,10 @@ func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFor
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
|
||||
+62
-18
@@ -21,12 +21,14 @@ type IamPermissionsModule struct {
|
||||
// General configuration data
|
||||
IAMClient sdk.AWSIAMClientInterface
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
|
||||
// Main module data
|
||||
|
||||
@@ -76,14 +78,15 @@ type PermissionsRow struct {
|
||||
Arn string
|
||||
PolicyType string
|
||||
PolicyName string
|
||||
PolicyArn string
|
||||
Effect string
|
||||
Action string
|
||||
Resource string
|
||||
Condition string
|
||||
}
|
||||
|
||||
func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDirectory string, verbosity int, principal string) {
|
||||
// These stuct values are used by the output module
|
||||
func (m *IamPermissionsModule) PrintIamPermissions(outputDirectory string, verbosity int, principal string) {
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "permissions"
|
||||
@@ -105,12 +108,13 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
|
||||
m.parsePermissions(principal)
|
||||
|
||||
m.output.Headers = []string{
|
||||
//"Service",
|
||||
"Account",
|
||||
"Type",
|
||||
"Name",
|
||||
//"Arn",
|
||||
"Arn",
|
||||
"Policy",
|
||||
"Policy Name",
|
||||
"Policy Arn",
|
||||
"Effect",
|
||||
"Action",
|
||||
"Resource",
|
||||
@@ -122,12 +126,13 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
//m.Rows[i].AWSService,
|
||||
aws.ToString(m.Caller.Account),
|
||||
m.Rows[i].Type,
|
||||
m.Rows[i].Name,
|
||||
//m.Rows[i].Arn,
|
||||
m.Rows[i].Arn,
|
||||
m.Rows[i].PolicyType,
|
||||
m.Rows[i].PolicyName,
|
||||
m.Rows[i].PolicyArn,
|
||||
m.Rows[i].Effect,
|
||||
m.Rows[i].Action,
|
||||
m.Rows[i].Resource,
|
||||
@@ -139,9 +144,6 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
|
||||
|
||||
if len(m.output.Body) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//m.output.OutputSelector3(outputFormat)
|
||||
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
|
||||
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -149,10 +151,48 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
|
||||
Wrap: m.WrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"Type",
|
||||
//"Name",
|
||||
"Arn",
|
||||
"Policy",
|
||||
"Policy Name",
|
||||
"Policy Arn",
|
||||
"Effect",
|
||||
"Action",
|
||||
"Resource",
|
||||
"Condition"}
|
||||
// Otherwise, use the default columns for this module (brief)
|
||||
} else {
|
||||
tableCols = []string{
|
||||
"Type",
|
||||
"Name",
|
||||
//"Arn",
|
||||
"Policy",
|
||||
"Policy Name",
|
||||
"Effect",
|
||||
"Action",
|
||||
"Resource",
|
||||
"Condition",
|
||||
}
|
||||
}
|
||||
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
TableCols: tableCols,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
@@ -402,6 +442,7 @@ func (m *IamPermissionsModule) getPermissionsFromAttachedPolicy(arn string, atta
|
||||
Type: IAMtype,
|
||||
PolicyType: "Managed",
|
||||
PolicyName: p.Name,
|
||||
PolicyArn: p.Arn,
|
||||
Effect: effect,
|
||||
Action: action,
|
||||
Resource: resource,
|
||||
@@ -428,6 +469,7 @@ func (m *IamPermissionsModule) getPermissionsFromAttachedPolicy(arn string, atta
|
||||
Type: IAMtype,
|
||||
PolicyType: "Managed",
|
||||
PolicyName: p.Name,
|
||||
PolicyArn: p.Arn,
|
||||
Effect: effect,
|
||||
Action: "[NotAction] " + action,
|
||||
Resource: resource,
|
||||
@@ -473,6 +515,7 @@ func (m *IamPermissionsModule) getPermissionsFromInlinePolicy(arn string, inline
|
||||
Type: IAMtype,
|
||||
PolicyType: "Inline",
|
||||
PolicyName: aws.ToString(inlinePolicy.PolicyName),
|
||||
PolicyArn: aws.ToString(inlinePolicy.PolicyName),
|
||||
Effect: effect,
|
||||
Action: action,
|
||||
Resource: resource,
|
||||
@@ -498,6 +541,7 @@ func (m *IamPermissionsModule) getPermissionsFromInlinePolicy(arn string, inline
|
||||
Type: IAMtype,
|
||||
PolicyType: "Inline",
|
||||
PolicyName: aws.ToString(inlinePolicy.PolicyName),
|
||||
PolicyArn: aws.ToString(inlinePolicy.PolicyName),
|
||||
Effect: effect,
|
||||
Action: "[NotAction] " + action,
|
||||
Resource: resource,
|
||||
|
||||
+44
-13
@@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
@@ -17,12 +18,14 @@ import (
|
||||
|
||||
type PmapperModule struct {
|
||||
// General configuration data
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
|
||||
// Main module data
|
||||
pmapperGraph graph.Graph[string, string]
|
||||
@@ -136,8 +139,8 @@ func (m *PmapperModule) DoesPrincipalHaveAdmin(principal string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *PmapperModule) PrintPmapperData(outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These stuct values are used by the output module
|
||||
func (m *PmapperModule) PrintPmapperData(outputDirectory string, verbosity int) {
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "pmapper"
|
||||
@@ -163,11 +166,39 @@ func (m *PmapperModule) PrintPmapperData(outputFormat string, outputDirectory st
|
||||
}
|
||||
|
||||
m.output.Headers = []string{
|
||||
"Account",
|
||||
"Principal Arn",
|
||||
"IsAdmin?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"Principal Arn",
|
||||
"IsAdmin?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
// Otherwise, use the default columns.
|
||||
} else {
|
||||
tableCols = []string{
|
||||
"Principal Arn",
|
||||
"IsAdmin?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
}
|
||||
|
||||
//Table rows
|
||||
var isAdmin, pathToAdmin string
|
||||
for i := range m.Nodes {
|
||||
@@ -186,6 +217,7 @@ func (m *PmapperModule) PrintPmapperData(outputFormat string, outputDirectory st
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
m.Nodes[i].Arn,
|
||||
isAdmin,
|
||||
pathToAdmin,
|
||||
@@ -196,8 +228,6 @@ func (m *PmapperModule) PrintPmapperData(outputFormat string, outputDirectory st
|
||||
|
||||
if len(m.output.Body) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
|
||||
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -206,9 +236,10 @@ func (m *PmapperModule) PrintPmapperData(outputFormat string, outputDirectory st
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
|
||||
+56
-25
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
@@ -16,12 +17,14 @@ type IamPrincipalsModule struct {
|
||||
// General configuration data
|
||||
IAMClient sdk.AWSIAMClientInterface
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
|
||||
// Main module data
|
||||
Users []User
|
||||
@@ -61,8 +64,8 @@ type Role struct {
|
||||
InlinePolicies []string
|
||||
}
|
||||
|
||||
func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These stuct values are used by the output module
|
||||
func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosity int) {
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "principals"
|
||||
@@ -92,13 +95,44 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDire
|
||||
//fmt.Printf("\nAnalyzed Resources by Region\n\n")
|
||||
|
||||
m.output.Headers = []string{
|
||||
"Service",
|
||||
"Account",
|
||||
"Type",
|
||||
"Name",
|
||||
"Arn",
|
||||
"AttachedPolicies",
|
||||
"InlinePolicies",
|
||||
}
|
||||
|
||||
// "AttachedPolicies",
|
||||
// "InlinePolicies",
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"Type",
|
||||
"Name",
|
||||
"Arn",
|
||||
//"AttachedPolicies",
|
||||
//"InlinePolicies",
|
||||
}
|
||||
|
||||
// Otherwise, use the default columns.
|
||||
} else {
|
||||
tableCols = []string{
|
||||
"Type",
|
||||
"Name",
|
||||
"Arn",
|
||||
// "AttachedPolicies",
|
||||
// "InlinePolicies",
|
||||
}
|
||||
}
|
||||
|
||||
//Table rows
|
||||
@@ -106,13 +140,12 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDire
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
m.Users[i].AWSService,
|
||||
aws.ToString(m.Caller.Account),
|
||||
m.Users[i].Type,
|
||||
m.Users[i].Name,
|
||||
m.Users[i].Arn,
|
||||
|
||||
// m.Users[i].AttachedPolicies,
|
||||
// m.Users[i].InlinePolicies,
|
||||
strings.Join(m.Users[i].AttachedPolicies, " , "),
|
||||
strings.Join(m.Users[i].InlinePolicies, " , "),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -122,22 +155,19 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDire
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
m.Roles[i].AWSService,
|
||||
aws.ToString(m.Caller.Account),
|
||||
m.Roles[i].Type,
|
||||
m.Roles[i].Name,
|
||||
m.Roles[i].Arn,
|
||||
|
||||
// m.Roles[i].AttachedPolicies,
|
||||
// m.Roles[i].InlinePolicies,
|
||||
strings.Join(m.Roles[i].AttachedPolicies, " , "),
|
||||
strings.Join(m.Roles[i].InlinePolicies, " , "),
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
if len(m.output.Body) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//m.output.OutputSelector(outputFormat)
|
||||
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
|
||||
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -146,9 +176,10 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDire
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
|
||||
+50
-16
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
@@ -20,12 +21,14 @@ type RAMModule struct {
|
||||
// General configuration data
|
||||
RAMClient *ram.Client
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
|
||||
// Main module data
|
||||
Resources []Resource
|
||||
@@ -44,8 +47,8 @@ type Resource struct {
|
||||
ShareType string
|
||||
}
|
||||
|
||||
func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These stuct values are used by the output module
|
||||
func (m *RAMModule) PrintRAM(outputDirectory string, verbosity int) {
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "ram"
|
||||
@@ -91,7 +94,7 @@ func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbos
|
||||
|
||||
// add - if struct is not empty do this. otherwise, dont write anything.
|
||||
m.output.Headers = []string{
|
||||
"Service",
|
||||
"Account",
|
||||
"Region",
|
||||
"Share Name",
|
||||
"Type",
|
||||
@@ -99,12 +102,44 @@ func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbos
|
||||
"Share Type",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"Region",
|
||||
"Share Name",
|
||||
"Type",
|
||||
"Owner",
|
||||
"Share Type",
|
||||
}
|
||||
// Otherwise, use the default columns.
|
||||
} else {
|
||||
tableCols = []string{
|
||||
"Service",
|
||||
"Region",
|
||||
"Share Name",
|
||||
"Type",
|
||||
"Owner",
|
||||
"Share Type",
|
||||
}
|
||||
}
|
||||
|
||||
// Table rows
|
||||
for i := range m.Resources {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
m.Resources[i].AWSService,
|
||||
aws.ToString(m.Caller.Account),
|
||||
m.Resources[i].Region,
|
||||
m.Resources[i].Name,
|
||||
m.Resources[i].Type,
|
||||
@@ -116,9 +151,7 @@ func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbos
|
||||
}
|
||||
if len(m.output.Body) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//m.output.OutputSelector(outputFormat)
|
||||
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
|
||||
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -127,9 +160,10 @@ func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbos
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
|
||||
+207
-25
@@ -20,10 +20,14 @@ import (
|
||||
|
||||
type ResourceTrustsModule struct {
|
||||
// General configuration data
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
Goroutines int
|
||||
WrapTable bool
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
Goroutines int
|
||||
WrapTable bool
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
AWSMFAToken string
|
||||
|
||||
AWSProfile string
|
||||
CloudFoxVersion string
|
||||
|
||||
@@ -44,10 +48,14 @@ type Resource2 struct {
|
||||
ResourcePolicySummary string
|
||||
Public string
|
||||
Interesting string
|
||||
TrustedPrincipals string
|
||||
TrustsCrossAccount string
|
||||
TrustsAllAccounts string
|
||||
HasConditions string
|
||||
}
|
||||
|
||||
func (m *ResourceTrustsModule) PrintResources(outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These stuct values are used by the output module
|
||||
func (m *ResourceTrustsModule) PrintResources(outputDirectory string, verbosity int) {
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "resource-trusts"
|
||||
@@ -60,7 +68,7 @@ func (m *ResourceTrustsModule) PrintResources(outputFormat string, outputDirecto
|
||||
}
|
||||
|
||||
fmt.Printf("[%s][%s] Enumerating Resources with resource policies for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s][%s] Supported Services: CodeBuild, ECR, EFS, Lambda, S3, SNS, SQS\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
fmt.Printf("[%s][%s] Supported Services: CodeBuild, ECR, EFS, Glue, Lambda, SecretsManager, S3, SNS, SQS\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
wg := new(sync.WaitGroup)
|
||||
semaphore := make(chan struct{}, m.Goroutines)
|
||||
|
||||
@@ -95,13 +103,42 @@ func (m *ResourceTrustsModule) PrintResources(outputFormat string, outputDirecto
|
||||
|
||||
// add - if struct is not empty do this. otherwise, dont write anything.
|
||||
m.output.Headers = []string{
|
||||
//"Account ID",
|
||||
"Account",
|
||||
"ARN",
|
||||
"Public",
|
||||
"Interesting",
|
||||
"Resource Policy Summary",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"ARN",
|
||||
"Public",
|
||||
"Interesting",
|
||||
"Resource Policy Summary",
|
||||
}
|
||||
} else {
|
||||
tableCols = []string{
|
||||
//"Account ID",
|
||||
"ARN",
|
||||
"Public",
|
||||
"Interesting",
|
||||
"Resource Policy Summary",
|
||||
}
|
||||
}
|
||||
|
||||
// sort the table roles by Interesting
|
||||
sort.Slice(m.Resources2, func(i, j int) bool {
|
||||
return m.Resources2[j].Interesting > m.Resources2[i].Interesting
|
||||
@@ -112,7 +149,7 @@ func (m *ResourceTrustsModule) PrintResources(outputFormat string, outputDirecto
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
//m.Resources2[i].AccountID,
|
||||
aws.ToString(m.Caller.Account),
|
||||
m.Resources2[i].ARN,
|
||||
m.Resources2[i].Public,
|
||||
m.Resources2[i].Interesting,
|
||||
@@ -122,8 +159,6 @@ func (m *ResourceTrustsModule) PrintResources(outputFormat string, outputDirecto
|
||||
|
||||
}
|
||||
if len(m.output.Body) > 0 {
|
||||
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
//m.writeLoot(m.output.FilePath, verbosity, m.AWSProfile)
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -132,9 +167,10 @@ func (m *ResourceTrustsModule) PrintResources(outputFormat string, outputDirecto
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
@@ -212,6 +248,24 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap
|
||||
wg.Add(1)
|
||||
m.getEFSfilesystemPoliciesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
res, err = servicemap.IsServiceInRegion("secretsmanager", r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getSecretsManagerSecretsPoliciesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
res, err = servicemap.IsServiceInRegion("glue", r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getGlueResourcePoliciesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -238,7 +292,7 @@ func (m *ResourceTrustsModule) getSNSTopicsPerRegion(r string, wg *sync.WaitGrou
|
||||
semaphore <- struct{}{}
|
||||
defer func() { <-semaphore }()
|
||||
|
||||
cloudFoxSNSClient := InitCloudFoxSNSClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.WrapTable)
|
||||
cloudFoxSNSClient := InitCloudFoxSNSClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.WrapTable, m.AWSMFAToken)
|
||||
|
||||
ListTopics, err := cloudFoxSNSClient.listTopics(r)
|
||||
if err != nil {
|
||||
@@ -270,7 +324,7 @@ func (m *ResourceTrustsModule) getSNSTopicsPerRegion(r string, wg *sync.WaitGrou
|
||||
topic.IsPublic = "No"
|
||||
}
|
||||
|
||||
// If there is a resouce policy, convert the resource policy into plain english
|
||||
// If there is a resource policy, convert the resource policy into plain english
|
||||
if !topic.Policy.IsEmpty() {
|
||||
|
||||
for i, statement := range topic.Policy.Statement {
|
||||
@@ -314,7 +368,7 @@ func (m *ResourceTrustsModule) getS3Buckets(wg *sync.WaitGroup, semaphore chan s
|
||||
semaphore <- struct{}{}
|
||||
defer func() { <-semaphore }()
|
||||
|
||||
cloudFoxS3Client := initCloudFoxS3Client(m.Caller, m.AWSProfile, m.CloudFoxVersion)
|
||||
cloudFoxS3Client := initCloudFoxS3Client(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.AWSMFAToken)
|
||||
|
||||
ListBuckets, err := sdk.CachedListBuckets(cloudFoxS3Client.S3Client, aws.ToString(m.Caller.Account))
|
||||
if err != nil {
|
||||
@@ -325,7 +379,7 @@ func (m *ResourceTrustsModule) getS3Buckets(wg *sync.WaitGroup, semaphore chan s
|
||||
for _, b := range ListBuckets {
|
||||
var statementSummaryInEnglish string
|
||||
var isInteresting string = "No"
|
||||
bucket := &Bucket{
|
||||
bucket := &BucketRow{
|
||||
Arn: fmt.Sprintf("arn:aws:s3:::%s", aws.ToString(b.Name)),
|
||||
}
|
||||
name := aws.ToString(b.Name)
|
||||
@@ -357,7 +411,7 @@ func (m *ResourceTrustsModule) getS3Buckets(wg *sync.WaitGroup, semaphore chan s
|
||||
bucket.IsPublic = "No"
|
||||
}
|
||||
|
||||
// If there is a resouce policy, convert the resource policy into plain english
|
||||
// If there is a resource policy, convert the resource policy into plain english
|
||||
if !bucket.Policy.IsEmpty() {
|
||||
for i, statement := range bucket.Policy.Statement {
|
||||
var prefix string = ""
|
||||
@@ -397,7 +451,7 @@ func (m *ResourceTrustsModule) getSQSQueuesPerRegion(r string, wg *sync.WaitGrou
|
||||
semaphore <- struct{}{}
|
||||
defer func() { <-semaphore }()
|
||||
|
||||
cloudFoxSQSClient := InitSQSClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
|
||||
cloudFoxSQSClient := InitSQSClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
|
||||
|
||||
ListQueues, err := cloudFoxSQSClient.listQueues(r)
|
||||
if err != nil {
|
||||
@@ -459,9 +513,9 @@ func (m *ResourceTrustsModule) getECRRecordsPerRegion(r string, wg *sync.WaitGro
|
||||
semaphore <- struct{}{}
|
||||
defer func() { <-semaphore }()
|
||||
|
||||
cloudFoxECRClient := InitECRClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
|
||||
cloudFoxECRClient := InitECRClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
|
||||
|
||||
DescribeRepositories, err := cloudFoxECRClient.describeRepositories(r)
|
||||
DescribeRepositories, err := sdk.CachedECRDescribeRepositories(cloudFoxECRClient.ECRClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
return
|
||||
@@ -523,7 +577,7 @@ func (m *ResourceTrustsModule) getCodeBuildResourcePoliciesPerRegion(r string, w
|
||||
semaphore <- struct{}{}
|
||||
defer func() { <-semaphore }()
|
||||
|
||||
cloudFoxCodeBuildClient := InitCodeBuildClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
|
||||
cloudFoxCodeBuildClient := InitCodeBuildClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
|
||||
|
||||
ListProjects, err := sdk.CachedCodeBuildListProjects(cloudFoxCodeBuildClient.CodeBuildClient, aws.ToString(cloudFoxCodeBuildClient.Caller.Account), r)
|
||||
if err != nil {
|
||||
@@ -597,7 +651,7 @@ func (m *ResourceTrustsModule) getLambdaPolicyPerRegion(r string, wg *sync.WaitG
|
||||
semaphore <- struct{}{}
|
||||
defer func() { <-semaphore }()
|
||||
|
||||
cloudFoxLambdaClient := InitLambdaClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
|
||||
cloudFoxLambdaClient := InitLambdaClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
|
||||
|
||||
ListFunctions, err := cloudFoxLambdaClient.listFunctions(r)
|
||||
if err != nil {
|
||||
@@ -662,7 +716,7 @@ func (m *ResourceTrustsModule) getEFSfilesystemPoliciesPerRegion(r string, wg *s
|
||||
semaphore <- struct{}{}
|
||||
defer func() { <-semaphore }()
|
||||
|
||||
cloudFoxEFSClient := InitFileSystemsClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines)
|
||||
cloudFoxEFSClient := InitFileSystemsClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
|
||||
|
||||
ListFileSystems, err := sdk.CachedDescribeFileSystems(cloudFoxEFSClient.EFSClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
@@ -718,6 +772,134 @@ func (m *ResourceTrustsModule) getEFSfilesystemPoliciesPerRegion(r string, wg *s
|
||||
}
|
||||
}
|
||||
|
||||
// getSecretsManagerSecretsPoliciesPerRegion retrieves the resource policies for all Secrets Manager secrets in the specified region.
|
||||
// It sends the resulting Resource2 objects to the dataReceiver channel.
|
||||
// It uses a semaphore to limit the number of concurrent requests and a WaitGroup to wait for all requests to complete.
|
||||
// It takes the region to search in, the WaitGroup to use, the semaphore to use, and the dataReceiver channel to send results to.
|
||||
func (m *ResourceTrustsModule) getSecretsManagerSecretsPoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() { <-semaphore }()
|
||||
|
||||
cloudFoxSecretsManagerClient := InitSecretsManagerClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
|
||||
|
||||
ListSecrets, err := sdk.CachedSecretsManagerListSecrets(cloudFoxSecretsManagerClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for _, s := range ListSecrets {
|
||||
var isPublic string
|
||||
var statementSummaryInEnglish string
|
||||
var isInteresting string = "No"
|
||||
secretPolicy, err := sdk.CachedSecretsManagerGetResourcePolicy(cloudFoxSecretsManagerClient, aws.ToString(s.ARN), r, aws.ToString(m.Caller.Account))
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
continue
|
||||
}
|
||||
|
||||
if secretPolicy.IsPublic() {
|
||||
isPublic = magenta("Yes")
|
||||
isInteresting = magenta("Yes")
|
||||
|
||||
} else {
|
||||
isPublic = "No"
|
||||
}
|
||||
|
||||
if !secretPolicy.IsEmpty() {
|
||||
for i, statement := range secretPolicy.Statement {
|
||||
prefix := ""
|
||||
if len(secretPolicy.Statement) > 1 {
|
||||
prefix = fmt.Sprintf("Statement %d says: ", i)
|
||||
statementSummaryInEnglish = prefix + statement.GetStatementSummaryInEnglish(*m.Caller.Account) + "\n"
|
||||
} else {
|
||||
statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
|
||||
}
|
||||
statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n")
|
||||
if isResourcePolicyInteresting(statementSummaryInEnglish) {
|
||||
//magenta(statementSummaryInEnglish)
|
||||
isInteresting = magenta("Yes")
|
||||
}
|
||||
|
||||
dataReceiver <- Resource2{
|
||||
AccountID: aws.ToString(m.Caller.Account),
|
||||
ARN: aws.ToString(s.ARN),
|
||||
ResourcePolicySummary: statementSummaryInEnglish,
|
||||
Public: isPublic,
|
||||
Name: aws.ToString(s.Name),
|
||||
Region: r,
|
||||
Interesting: isInteresting,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ResourceTrustsModule) getGlueResourcePoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() { <-semaphore }()
|
||||
|
||||
cloudFoxGlueClient := InitGlueClient(m.Caller, m.AWSProfile, m.CloudFoxVersion, m.Goroutines, m.AWSMFAToken)
|
||||
|
||||
ResourcePolicies, err := sdk.CachedGlueGetResourcePolicies(cloudFoxGlueClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for _, resourcePolicy := range ResourcePolicies {
|
||||
var isPublic string
|
||||
var statementSummaryInEnglish string
|
||||
var isInteresting string = "No"
|
||||
|
||||
if resourcePolicy.IsPublic() {
|
||||
isPublic = magenta("Yes")
|
||||
isInteresting = magenta("Yes")
|
||||
|
||||
} else {
|
||||
isPublic = "No"
|
||||
}
|
||||
|
||||
if !resourcePolicy.IsEmpty() {
|
||||
for _, statement := range resourcePolicy.Statement {
|
||||
statementSummaryInEnglish = statement.GetStatementSummaryInEnglish(*m.Caller.Account)
|
||||
resources := statement.Resource
|
||||
for _, resource := range resources {
|
||||
|
||||
statementSummaryInEnglish = strings.TrimSuffix(statementSummaryInEnglish, "\n")
|
||||
if isResourcePolicyInteresting(statementSummaryInEnglish) {
|
||||
//magenta(statementSummaryInEnglish)
|
||||
isInteresting = magenta("Yes")
|
||||
}
|
||||
|
||||
dataReceiver <- Resource2{
|
||||
AccountID: aws.ToString(m.Caller.Account),
|
||||
ARN: resource,
|
||||
ResourcePolicySummary: statementSummaryInEnglish,
|
||||
Public: isPublic,
|
||||
Name: resource,
|
||||
Region: r,
|
||||
Interesting: isInteresting,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isResourcePolicyInteresting(statementSummaryInEnglish string) bool {
|
||||
// check if the statement has any of the following items, but make sure the check is case insensitive
|
||||
// if it does, then return true
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsResourcePolicyInteresting(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "test1",
|
||||
input: "Everyone can sqs:SendMessage & can sqs:ReceiveMessage",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "PrincipalOrgPaths",
|
||||
input: "aws:PrincipalOrgPaths",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Empty",
|
||||
input: "",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "NotInteresting",
|
||||
input: "sns.amazonaws.com can lambda:InvokeFunction",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
actual := isResourcePolicyInteresting(tc.input)
|
||||
if actual != tc.expected {
|
||||
t.Errorf("Expected %v but got %v for input %s", tc.expected, actual, tc.input)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+173
-146
@@ -31,12 +31,16 @@ type RoleTrustsModule struct {
|
||||
CommandCounter internal.CommandCounter
|
||||
SkipAdminCheck bool
|
||||
WrapTable bool
|
||||
pmapperMod PmapperModule
|
||||
pmapperError error
|
||||
iamSimClient IamSimulatorModule
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
pmapperMod PmapperModule
|
||||
pmapperError error
|
||||
iamSimClient IamSimulatorModule
|
||||
|
||||
// Main module data
|
||||
AnalyzedRoles []AnalyzedRole
|
||||
AnalyzedRoles []AnalyzedRole
|
||||
RoleTrustTable []RoleTrustRow
|
||||
|
||||
// Used to store output data for pretty printing
|
||||
output internal.OutputData2
|
||||
@@ -44,6 +48,18 @@ type RoleTrustsModule struct {
|
||||
modLog *logrus.Entry
|
||||
}
|
||||
|
||||
type RoleTrustRow struct {
|
||||
RoleARN string
|
||||
RoleName string
|
||||
TrustedPrincipal string
|
||||
TrustedService string
|
||||
TrustedFederatedProvider string
|
||||
TrustedFederatedSubject string
|
||||
ExternalID string
|
||||
IsAdmin string
|
||||
CanPrivEsc string
|
||||
}
|
||||
|
||||
type AnalyzedRole struct {
|
||||
roleARN *string
|
||||
trustsDoc trustPolicyDocument
|
||||
@@ -86,7 +102,7 @@ type RoleTrustStatementEntry struct {
|
||||
} `json:"Condition"`
|
||||
}
|
||||
|
||||
func (m *RoleTrustsModule) PrintRoleTrusts(outputFormat string, outputDirectory string, verbosity int) {
|
||||
func (m *RoleTrustsModule) PrintRoleTrusts(outputDirectory string, verbosity int) {
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "role-trusts"
|
||||
@@ -129,25 +145,28 @@ func (m *RoleTrustsModule) PrintRoleTrusts(outputFormat string, outputDirectory
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
|
||||
principalsHeader, principalsBody := m.printPrincipalTrusts(outputFormat, outputDirectory)
|
||||
principalsHeader, principalsBody, principalTableCols := m.printPrincipalTrusts(outputDirectory)
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: principalsHeader,
|
||||
Body: principalsBody,
|
||||
Name: "role-trusts-principals",
|
||||
Header: principalsHeader,
|
||||
Body: principalsBody,
|
||||
TableCols: principalTableCols,
|
||||
Name: "role-trusts-principals",
|
||||
})
|
||||
|
||||
servicesHeader, servicesBody := m.printServiceTrusts(outputFormat, outputDirectory)
|
||||
servicesHeader, servicesBody, serviceTableCols := m.printServiceTrusts(outputDirectory)
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: servicesHeader,
|
||||
Body: servicesBody,
|
||||
Name: "role-trusts-services",
|
||||
Header: servicesHeader,
|
||||
Body: servicesBody,
|
||||
TableCols: serviceTableCols,
|
||||
Name: "role-trusts-services",
|
||||
})
|
||||
|
||||
federatedHeader, federatedBody := m.printFederatedTrusts(outputFormat, outputDirectory)
|
||||
federatedHeader, federatedBody, federatedTableCols := m.printFederatedTrusts(outputDirectory)
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: federatedHeader,
|
||||
Body: federatedBody,
|
||||
Name: "role-trusts-federated",
|
||||
Header: federatedHeader,
|
||||
Body: federatedBody,
|
||||
TableCols: federatedTableCols,
|
||||
Name: "role-trusts-federated",
|
||||
})
|
||||
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
@@ -169,196 +188,203 @@ func (m *RoleTrustsModule) PrintRoleTrusts(outputFormat string, outputDirectory
|
||||
|
||||
}
|
||||
|
||||
func (m *RoleTrustsModule) printPrincipalTrusts(outputFormat string, outputDirectory string) ([]string, [][]string) {
|
||||
func (m *RoleTrustsModule) printPrincipalTrusts(outputDirectory string) ([]string, [][]string, []string) {
|
||||
var header []string
|
||||
var body [][]string
|
||||
m.output.FullFilename = ""
|
||||
m.output.Body = nil
|
||||
m.output.CallingModule = "role-trusts"
|
||||
m.output.FullFilename = "role-trusts-principals"
|
||||
var column1 string
|
||||
if m.pmapperError == nil {
|
||||
header = []string{
|
||||
"Role",
|
||||
"Trusted Principal",
|
||||
"ExternalID",
|
||||
"IsAdmin?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
} else {
|
||||
header = []string{
|
||||
"Role",
|
||||
"Trusted Principal",
|
||||
"ExternalID",
|
||||
"IsAdmin?",
|
||||
//"CanPrivEscToAdmin?",
|
||||
}
|
||||
header = []string{
|
||||
"Account",
|
||||
"Role Arn",
|
||||
"Role Name",
|
||||
"Trusted Principal",
|
||||
"ExternalID",
|
||||
"IsAdmin?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{"Account", "Role Arn", "Trusted Principal", "ExternalID", "IsAdmin?", "CanPrivEscToAdmin?"}
|
||||
// Otherwise, use the default columns for this module (brief)
|
||||
} else {
|
||||
tableCols = []string{"Role Name", "Trusted Principal", "ExternalID", "IsAdmin?", "CanPrivEscToAdmin?"}
|
||||
}
|
||||
// Remove the pmapper row if there is no pmapper data
|
||||
if m.pmapperError != nil {
|
||||
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
|
||||
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
|
||||
}
|
||||
|
||||
for _, role := range m.AnalyzedRoles {
|
||||
for _, statement := range role.trustsDoc.Statement {
|
||||
for _, principal := range statement.Principal.AWS {
|
||||
if outputFormat == "wide" {
|
||||
column1 = aws.ToString(role.roleARN)
|
||||
} else {
|
||||
column1 = GetResourceNameFromArn(aws.ToString(role.roleARN))
|
||||
}
|
||||
column2 := principal
|
||||
column3 := statement.Condition.StringEquals.StsExternalID
|
||||
column4 := role.Admin
|
||||
column5 := role.CanPrivEsc
|
||||
if m.pmapperError == nil {
|
||||
body = append(body, []string{column1, column2, column3, column4, column5})
|
||||
} else {
|
||||
body = append(body, []string{column1, column2, column3, column4})
|
||||
}
|
||||
|
||||
RoleTrustRow := RoleTrustRow{
|
||||
RoleARN: aws.ToString(role.roleARN),
|
||||
RoleName: GetResourceNameFromArn(aws.ToString(role.roleARN)),
|
||||
TrustedPrincipal: principal,
|
||||
ExternalID: statement.Condition.StringEquals.StsExternalID,
|
||||
IsAdmin: role.Admin,
|
||||
CanPrivEsc: role.CanPrivEsc,
|
||||
}
|
||||
body = append(body, []string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
RoleTrustRow.RoleARN,
|
||||
RoleTrustRow.RoleName,
|
||||
RoleTrustRow.TrustedPrincipal,
|
||||
RoleTrustRow.ExternalID,
|
||||
RoleTrustRow.IsAdmin,
|
||||
RoleTrustRow.CanPrivEsc})
|
||||
}
|
||||
}
|
||||
}
|
||||
m.sortTrustsTablePerTrustedPrincipal()
|
||||
return header, body
|
||||
// if len(m.output.Body) > 0 {
|
||||
// m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account),m.AWSProfile))
|
||||
// ////m.output.OutputSelector(outputFormat)
|
||||
// //utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
|
||||
// //internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
// fmt.Printf("[%s][%s] %s role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
|
||||
// } else {
|
||||
// fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
// }
|
||||
// fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule)
|
||||
m.sortTrustsTablePerTrustedPrincipal()
|
||||
return header, body, tableCols
|
||||
|
||||
}
|
||||
|
||||
func (m *RoleTrustsModule) printServiceTrusts(outputFormat string, outputDirectory string) ([]string, [][]string) {
|
||||
func (m *RoleTrustsModule) printServiceTrusts(outputDirectory string) ([]string, [][]string, []string) {
|
||||
var header []string
|
||||
var body [][]string
|
||||
m.output.FullFilename = ""
|
||||
m.output.Body = nil
|
||||
m.output.CallingModule = "role-trusts"
|
||||
m.output.FullFilename = "role-trusts-services"
|
||||
var column1 string
|
||||
if m.pmapperError == nil {
|
||||
header = []string{
|
||||
"Role",
|
||||
"Trusted Service",
|
||||
"ExternalID",
|
||||
"IsAdmin?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
header = []string{
|
||||
"Account",
|
||||
"Role Arn",
|
||||
"Role Name",
|
||||
"Trusted Service",
|
||||
"IsAdmin?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{"Role Arn", "Trusted Service", "IsAdmin?", "CanPrivEscToAdmin?"}
|
||||
// Otherwise, use the default columns for this module (brief)
|
||||
} else {
|
||||
header = []string{
|
||||
"Role",
|
||||
"Trusted Service",
|
||||
"ExternalID",
|
||||
"IsAdmin?",
|
||||
//"CanPrivEscToAdmin?",
|
||||
}
|
||||
tableCols = []string{"Role Name", "Trusted Service", "IsAdmin?", "CanPrivEscToAdmin?"}
|
||||
}
|
||||
// Remove the pmapper row if there is no pmapper data
|
||||
if m.pmapperError != nil {
|
||||
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
|
||||
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
|
||||
}
|
||||
|
||||
for _, role := range m.AnalyzedRoles {
|
||||
for _, statement := range role.trustsDoc.Statement {
|
||||
for _, service := range statement.Principal.Service {
|
||||
if outputFormat == "wide" {
|
||||
column1 = aws.ToString(role.roleARN)
|
||||
} else {
|
||||
column1 = GetResourceNameFromArn(aws.ToString(role.roleARN))
|
||||
}
|
||||
column2 := service
|
||||
column3 := statement.Condition.StringEquals.StsExternalID
|
||||
column4 := role.Admin
|
||||
column5 := role.CanPrivEsc
|
||||
if m.pmapperError == nil {
|
||||
body = append(body, []string{column1, column2, column3, column4, column5})
|
||||
} else {
|
||||
body = append(body, []string{column1, column2, column3, column4})
|
||||
RoleTrustRow := RoleTrustRow{
|
||||
RoleARN: aws.ToString(role.roleARN),
|
||||
RoleName: GetResourceNameFromArn(aws.ToString(role.roleARN)),
|
||||
TrustedService: service,
|
||||
IsAdmin: role.Admin,
|
||||
CanPrivEsc: role.CanPrivEsc,
|
||||
}
|
||||
body = append(body, []string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
RoleTrustRow.RoleARN,
|
||||
RoleTrustRow.RoleName,
|
||||
RoleTrustRow.TrustedService,
|
||||
RoleTrustRow.IsAdmin,
|
||||
RoleTrustRow.CanPrivEsc})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sort the rows based on column 2 (service)
|
||||
sort.SliceStable(body, func(i, j int) bool {
|
||||
return body[i][1] < body[j][1]
|
||||
return body[i][3] < body[j][3]
|
||||
})
|
||||
|
||||
//m.sortTrustsTablePerTrustedPrincipal()
|
||||
return header, body
|
||||
return header, body, tableCols
|
||||
|
||||
// if len(m.output.Body) > 0 {
|
||||
// m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account),m.AWSProfile))
|
||||
// //m.output.OutputSelector(outputFormat)
|
||||
// //utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
|
||||
// internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
// fmt.Printf("[%s][%s] %s role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
|
||||
// } else {
|
||||
// fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
// }
|
||||
}
|
||||
|
||||
func (m *RoleTrustsModule) printFederatedTrusts(outputFormat string, outputDirectory string) ([]string, [][]string) {
|
||||
func (m *RoleTrustsModule) printFederatedTrusts(outputDirectory string) ([]string, [][]string, []string) {
|
||||
var header []string
|
||||
var body [][]string
|
||||
m.output.FullFilename = ""
|
||||
m.output.Body = nil
|
||||
m.output.CallingModule = "role-trusts"
|
||||
m.output.FullFilename = "role-trusts-federated"
|
||||
var column1, column3, column2 string
|
||||
if m.pmapperError == nil {
|
||||
|
||||
header = []string{
|
||||
"Role",
|
||||
"Trusted Provider",
|
||||
"Trusted Subject",
|
||||
"IsAdmin?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
} else {
|
||||
header = []string{
|
||||
"Role",
|
||||
"Trusted Provider",
|
||||
"Trusted Subject",
|
||||
"IsAdmin?",
|
||||
//"CanPrivEscToAdmin?",
|
||||
}
|
||||
header = []string{
|
||||
"Account",
|
||||
"Role Arn",
|
||||
"Role Name",
|
||||
"Trusted Provider",
|
||||
"Trusted Subject",
|
||||
"IsAdmin?",
|
||||
"CanPrivEscToAdmin?",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{"Role Arn", "Trusted Provider", "Trusted Subject", "IsAdmin?", "CanPrivEscToAdmin?"}
|
||||
// Otherwise, use the default columns for this module (brief)
|
||||
} else {
|
||||
tableCols = []string{"Role Name", "Trusted Provider", "Trusted Subject", "IsAdmin?", "CanPrivEscToAdmin?"}
|
||||
}
|
||||
// Remove the pmapper row if there is no pmapper data
|
||||
if m.pmapperError != nil {
|
||||
sharedLogger.Errorf("%s - %s - No pmapper data found for this account. Skipping the pmapper column in the output table.", m.output.CallingModule, m.AWSProfile)
|
||||
tableCols = removeStringFromSlice(tableCols, "CanPrivEscToAdmin?")
|
||||
}
|
||||
for _, role := range m.AnalyzedRoles {
|
||||
if outputFormat == "wide" {
|
||||
column1 = aws.ToString(role.roleARN)
|
||||
} else {
|
||||
column1 = GetResourceNameFromArn(aws.ToString(role.roleARN))
|
||||
}
|
||||
for _, statement := range role.trustsDoc.Statement {
|
||||
if len(statement.Principal.Federated) > 0 {
|
||||
column2, column3 = parseFederatedTrustPolicy(statement)
|
||||
column4 := role.Admin
|
||||
column5 := role.CanPrivEsc
|
||||
if m.pmapperError == nil {
|
||||
body = append(body, []string{column1, column2, column3, column4, column5})
|
||||
} else {
|
||||
body = append(body, []string{column1, column2, column3, column4})
|
||||
provider, subject := parseFederatedTrustPolicy(statement)
|
||||
RoleTrustRow := RoleTrustRow{
|
||||
RoleARN: aws.ToString(role.roleARN),
|
||||
RoleName: GetResourceNameFromArn(aws.ToString(role.roleARN)),
|
||||
TrustedFederatedProvider: provider,
|
||||
TrustedFederatedSubject: subject,
|
||||
IsAdmin: role.Admin,
|
||||
CanPrivEsc: role.CanPrivEsc,
|
||||
}
|
||||
body = append(body, []string{
|
||||
aws.ToString(m.Caller.Account),
|
||||
RoleTrustRow.RoleARN,
|
||||
RoleTrustRow.RoleName,
|
||||
RoleTrustRow.TrustedFederatedProvider,
|
||||
RoleTrustRow.TrustedFederatedSubject,
|
||||
RoleTrustRow.IsAdmin,
|
||||
RoleTrustRow.CanPrivEsc})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
m.sortTrustsTablePerTrustedPrincipal()
|
||||
return header, body
|
||||
return header, body, tableCols
|
||||
|
||||
// if len(m.output.Body) > 0 {
|
||||
// m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account),m.AWSProfile))
|
||||
// //m.output.OutputSelector(outputFormat)
|
||||
// //utils.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
|
||||
// internal.OutputSelector(m.output.Verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
// fmt.Printf("[%s][%s] %s role trusts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
|
||||
// } else {
|
||||
// fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
// }
|
||||
}
|
||||
|
||||
func parseFederatedTrustPolicy(statement RoleTrustStatementEntry) (string, string) {
|
||||
@@ -435,6 +461,7 @@ func (m *RoleTrustsModule) getAllRoleTrusts() {
|
||||
Admin: "",
|
||||
CanPrivEsc: "",
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+80
-71
@@ -1,26 +1,28 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/BishopFox/cloudfox/aws/sdk"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/route53"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Route53Module struct {
|
||||
// General configuration data
|
||||
Route53Client *route53.Client
|
||||
Route53Client sdk.AWSRoute53ClientInterface
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSOutputType string
|
||||
AWSTableCols string
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
WrapTable bool
|
||||
@@ -42,9 +44,9 @@ type Record struct {
|
||||
PrivateZone string
|
||||
}
|
||||
|
||||
func (m *Route53Module) PrintRoute53(outputFormat string, outputDirectory string, verbosity int) {
|
||||
func (m *Route53Module) PrintRoute53(outputDirectory string, verbosity int) {
|
||||
|
||||
// These stuct values are used by the output module
|
||||
// These struct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "route53"
|
||||
@@ -60,19 +62,48 @@ func (m *Route53Module) PrintRoute53(outputFormat string, outputDirectory string
|
||||
m.getRoute53Records()
|
||||
|
||||
m.output.Headers = []string{
|
||||
"Service",
|
||||
"Account",
|
||||
"Name",
|
||||
"Type",
|
||||
"Value",
|
||||
"PrivateZone",
|
||||
}
|
||||
|
||||
// If the user specified table columns, use those.
|
||||
// If the user specified -o wide, use the wide default cols for this module.
|
||||
// Otherwise, use the hardcoded default cols for this module.
|
||||
var tableCols []string
|
||||
// If the user specified table columns, use those.
|
||||
if m.AWSTableCols != "" {
|
||||
// If the user specified wide as the output format, use these columns.
|
||||
// remove any spaces between any commas and the first letter after the commas
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
m.AWSTableCols = strings.ReplaceAll(m.AWSTableCols, ", ", ",")
|
||||
tableCols = strings.Split(m.AWSTableCols, ",")
|
||||
} else if m.AWSOutputType == "wide" {
|
||||
tableCols = []string{
|
||||
"Account",
|
||||
"Name",
|
||||
"Type",
|
||||
"Value",
|
||||
"PrivateZone",
|
||||
}
|
||||
|
||||
} else {
|
||||
tableCols = []string{
|
||||
"Name",
|
||||
"Type",
|
||||
"Value",
|
||||
"PrivateZone",
|
||||
}
|
||||
}
|
||||
|
||||
// Table rows
|
||||
for i := range m.Records {
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
m.Records[i].AWSService,
|
||||
aws.ToString(m.Caller.Account),
|
||||
m.Records[i].Name,
|
||||
m.Records[i].Type,
|
||||
m.Records[i].Value,
|
||||
@@ -84,10 +115,7 @@ func (m *Route53Module) PrintRoute53(outputFormat string, outputDirectory string
|
||||
if len(m.output.Body) > 0 {
|
||||
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
//m.output.OutputSelector(outputFormat)
|
||||
//utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
|
||||
//internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile)
|
||||
//m.writeLoot(m.output.FilePath, verbosity)
|
||||
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: m.output.CallingModule,
|
||||
@@ -96,9 +124,10 @@ func (m *Route53Module) PrintRoute53(outputFormat string, outputDirectory string
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
Name: m.output.CallingModule,
|
||||
Header: m.output.Headers,
|
||||
Body: m.output.Body,
|
||||
TableCols: tableCols,
|
||||
Name: m.output.CallingModule,
|
||||
})
|
||||
o.PrefixIdentifier = m.AWSProfile
|
||||
o.Table.DirectoryName = filepath.Join(outputDirectory, "cloudfox-output", "aws", fmt.Sprintf("%s-%s", m.AWSProfile, aws.ToString(m.Caller.Account)))
|
||||
@@ -127,7 +156,7 @@ func (m *Route53Module) writeLoot(outputDirectory string, verbosity int) {
|
||||
var route53APublicRecords string
|
||||
|
||||
for _, record := range m.Records {
|
||||
if record.Type == "A" || record.Type == "AAAA" {
|
||||
if record.Type == "A" || record.Type == "AAAA" || record.Type == "CNAME" {
|
||||
if record.PrivateZone == "True" {
|
||||
route53APrivateRecords = route53APrivateRecords + fmt.Sprintln(record.Name)
|
||||
} else {
|
||||
@@ -176,71 +205,51 @@ func (m *Route53Module) writeLoot(outputDirectory string, verbosity int) {
|
||||
|
||||
func (m *Route53Module) getRoute53Records() {
|
||||
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
|
||||
var PaginationControl *string
|
||||
var recordName string
|
||||
var recordType string
|
||||
|
||||
for {
|
||||
ListHostedZones, err := m.Route53Client.ListHostedZones(
|
||||
context.TODO(),
|
||||
&route53.ListHostedZonesInput{
|
||||
Marker: PaginationControl,
|
||||
},
|
||||
)
|
||||
HostedZones, err := sdk.CachedRoute53ListHostedZones(m.Route53Client, aws.ToString(m.Caller.Account))
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
var privateZone string
|
||||
for _, zone := range HostedZones {
|
||||
if zone.Config.PrivateZone {
|
||||
privateZone = "True"
|
||||
} else {
|
||||
privateZone = "False"
|
||||
}
|
||||
|
||||
Records, err := sdk.CachedRoute53ListResourceRecordSets(m.Route53Client, aws.ToString(m.Caller.Account), aws.ToString(zone.Id))
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
for _, record := range Records {
|
||||
recordName = aws.ToString(record.Name)
|
||||
recordType = string(record.Type)
|
||||
|
||||
for _, resourceRecord := range record.ResourceRecords {
|
||||
recordValue := resourceRecord.Value
|
||||
m.Records = append(
|
||||
m.Records,
|
||||
Record{
|
||||
AWSService: "Route53",
|
||||
Name: recordName,
|
||||
Type: recordType,
|
||||
Value: aws.ToString(recordValue),
|
||||
PrivateZone: privateZone,
|
||||
})
|
||||
|
||||
var privateZone string
|
||||
for _, zone := range ListHostedZones.HostedZones {
|
||||
id := aws.ToString(zone.Id)
|
||||
if zone.Config.PrivateZone {
|
||||
privateZone = "True"
|
||||
} else {
|
||||
privateZone = "False"
|
||||
}
|
||||
|
||||
ListResourceRecordSets, err := m.Route53Client.ListResourceRecordSets(
|
||||
context.TODO(),
|
||||
&route53.ListResourceRecordSetsInput{
|
||||
HostedZoneId: &id,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
for _, record := range ListResourceRecordSets.ResourceRecordSets {
|
||||
recordName = aws.ToString(record.Name)
|
||||
recordType = string(record.Type)
|
||||
|
||||
for _, resourceRecord := range record.ResourceRecords {
|
||||
recordValue := resourceRecord.Value
|
||||
m.Records = append(
|
||||
m.Records,
|
||||
Record{
|
||||
AWSService: "Route53",
|
||||
Name: recordName,
|
||||
Type: recordType,
|
||||
Value: aws.ToString(recordValue),
|
||||
PrivateZone: privateZone,
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// The "NextToken" value is nil when there's no more data to return.
|
||||
if ListHostedZones.NextMarker != nil {
|
||||
PaginationControl = ListHostedZones.NextMarker
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,7 +14,7 @@ type APIGatewayClientInterface interface {
|
||||
GetRestApis(context.Context, *apigateway.GetRestApisInput, ...func(*apigateway.Options)) (*apigateway.GetRestApisOutput, error)
|
||||
}
|
||||
|
||||
func RegisterApiGatewayTypes() {
|
||||
func init() {
|
||||
gob.Register([]apiGatewayTypes.RestApi{})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/apigateway"
|
||||
apiGatewayTypes "github.com/aws/aws-sdk-go-v2/service/apigateway/types"
|
||||
)
|
||||
|
||||
type MockedAWSAPIGatewayClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedAWSAPIGatewayClient) GetRestApis(ctx context.Context, input *apigateway.GetRestApisInput, options ...func(*apigateway.Options)) (*apigateway.GetRestApisOutput, error) {
|
||||
return &apigateway.GetRestApisOutput{
|
||||
Items: []apiGatewayTypes.RestApi{
|
||||
{
|
||||
Id: aws.String("api1"),
|
||||
Name: aws.String("api1"),
|
||||
},
|
||||
{
|
||||
Id: aws.String("api2"),
|
||||
Name: aws.String("api2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -16,7 +16,7 @@ type APIGatewayv2ClientInterface interface {
|
||||
GetApis(context.Context, *apigatewayv2.GetApisInput, ...func(*apigatewayv2.Options)) (*apigatewayv2.GetApisOutput, error)
|
||||
}
|
||||
|
||||
func RegisterApiGatewayV2Types() {
|
||||
func init() {
|
||||
gob.Register([]apiGatwayV2Types.Api{})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/apigatewayv2"
|
||||
apiGatwayV2Types "github.com/aws/aws-sdk-go-v2/service/apigatewayv2/types"
|
||||
)
|
||||
|
||||
type MockedAWSAPIGatewayv2Client struct {
|
||||
}
|
||||
|
||||
func (m *MockedAWSAPIGatewayv2Client) GetApis(ctx context.Context, input *apigatewayv2.GetApisInput, options ...func(*apigatewayv2.Options)) (*apigatewayv2.GetApisOutput, error) {
|
||||
return &apigatewayv2.GetApisOutput{
|
||||
Items: []apiGatwayV2Types.Api{
|
||||
{
|
||||
ApiId: aws.String("api1"),
|
||||
Name: aws.String("api1"),
|
||||
},
|
||||
{
|
||||
ApiId: aws.String("api2"),
|
||||
Name: aws.String("api2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -15,7 +15,7 @@ type AppRunnerClientInterface interface {
|
||||
ListServices(context.Context, *apprunner.ListServicesInput, ...func(*apprunner.Options)) (*apprunner.ListServicesOutput, error)
|
||||
}
|
||||
|
||||
func RegisterApprunnerTypes() {
|
||||
func init() {
|
||||
gob.Register([]apprunnerTypes.Service{})
|
||||
gob.Register([]apprunnerTypes.ServiceSummary{})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/apprunner"
|
||||
apprunnerTypes "github.com/aws/aws-sdk-go-v2/service/apprunner/types"
|
||||
)
|
||||
|
||||
type MockedAppRunnerClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedAppRunnerClient) ListServices(ctx context.Context, input *apprunner.ListServicesInput, options ...func(*apprunner.Options)) (*apprunner.ListServicesOutput, error) {
|
||||
return &apprunner.ListServicesOutput{
|
||||
ServiceSummaryList: []apprunnerTypes.ServiceSummary{
|
||||
{
|
||||
ServiceName: aws.String("service1"),
|
||||
},
|
||||
{
|
||||
ServiceName: aws.String("service2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/service/athena"
|
||||
athenaTypes "github.com/aws/aws-sdk-go-v2/service/athena/types"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
type AWSAthenaClientInterface interface {
|
||||
ListDataCatalogs(context.Context, *athena.ListDataCatalogsInput, ...func(*athena.Options)) (*athena.ListDataCatalogsOutput, error)
|
||||
ListDatabases(context.Context, *athena.ListDatabasesInput, ...func(*athena.Options)) (*athena.ListDatabasesOutput, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register([]athenaTypes.DataCatalogSummary{})
|
||||
}
|
||||
|
||||
func CachedAthenaListDataCatalogs(client AWSAthenaClientInterface, accountID string, region string) ([]athenaTypes.DataCatalogSummary, error) {
|
||||
var PaginationControl *string
|
||||
var dataCatalogs []athenaTypes.DataCatalogSummary
|
||||
cacheKey := fmt.Sprintf("%s-athena-ListDataCatalogs-%s", accountID, region)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]athenaTypes.DataCatalogSummary), nil
|
||||
}
|
||||
for {
|
||||
ListDataCatalogs, err := client.ListDataCatalogs(
|
||||
context.TODO(),
|
||||
&athena.ListDataCatalogsInput{
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *athena.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return dataCatalogs, err
|
||||
}
|
||||
|
||||
dataCatalogs = append(dataCatalogs, ListDataCatalogs.DataCatalogsSummary...)
|
||||
|
||||
//pagination
|
||||
if ListDataCatalogs.NextToken == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = ListDataCatalogs.NextToken
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, dataCatalogs, cache.DefaultExpiration)
|
||||
return dataCatalogs, nil
|
||||
}
|
||||
|
||||
func CachedAthenaListDatabases(client AWSAthenaClientInterface, accountID string, region string, catalogName string) ([]string, error) {
|
||||
var PaginationControl *string
|
||||
var databases []string
|
||||
cacheKey := fmt.Sprintf("%s-athena-ListDatabases-%s-%s", accountID, region, catalogName)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]string), nil
|
||||
}
|
||||
for {
|
||||
ListDatabases, err := client.ListDatabases(
|
||||
context.TODO(),
|
||||
&athena.ListDatabasesInput{
|
||||
CatalogName: &catalogName,
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *athena.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return databases, err
|
||||
}
|
||||
|
||||
for _, database := range ListDatabases.DatabaseList {
|
||||
databases = append(databases, *database.Name)
|
||||
}
|
||||
|
||||
//pagination
|
||||
if ListDatabases.NextToken == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = ListDatabases.NextToken
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, databases, cache.DefaultExpiration)
|
||||
return databases, nil
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/athena"
|
||||
athenaTypes "github.com/aws/aws-sdk-go-v2/service/athena/types"
|
||||
)
|
||||
|
||||
type MockedAWSAthenaClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedAWSAthenaClient) ListDatabases(ctx context.Context, input *athena.ListDatabasesInput, options ...func(*athena.Options)) (*athena.ListDatabasesOutput, error) {
|
||||
return &athena.ListDatabasesOutput{
|
||||
DatabaseList: []athenaTypes.Database{
|
||||
{
|
||||
Name: aws.String("db1"),
|
||||
},
|
||||
{
|
||||
Name: aws.String("db2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedAWSAthenaClient) ListDataCatalogs(ctx context.Context, input *athena.ListDataCatalogsInput, options ...func(*athena.Options)) (*athena.ListDataCatalogsOutput, error) {
|
||||
return &athena.ListDataCatalogsOutput{
|
||||
DataCatalogsSummary: []athenaTypes.DataCatalogSummary{
|
||||
{
|
||||
CatalogName: aws.String("catalog1"),
|
||||
},
|
||||
{
|
||||
CatalogName: aws.String("catalog2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/service/cloud9"
|
||||
cloud9Types "github.com/aws/aws-sdk-go-v2/service/cloud9/types"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
type AWSCloud9ClientInterface interface {
|
||||
ListEnvironments(context.Context, *cloud9.ListEnvironmentsInput, ...func(*cloud9.Options)) (*cloud9.ListEnvironmentsOutput, error)
|
||||
DescribeEnvironments(context.Context, *cloud9.DescribeEnvironmentsInput, ...func(*cloud9.Options)) (*cloud9.DescribeEnvironmentsOutput, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register([]string{})
|
||||
}
|
||||
|
||||
func CachedCloud9ListEnvironments(client AWSCloud9ClientInterface, accountID string, region string) ([]string, error) {
|
||||
var PaginationControl *string
|
||||
var environments []string
|
||||
cacheKey := "cloud9-ListEnvironments-" + accountID + "-" + region
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]string), nil
|
||||
}
|
||||
for {
|
||||
ListEnvironments, err := client.ListEnvironments(
|
||||
context.TODO(),
|
||||
&cloud9.ListEnvironmentsInput{
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *cloud9.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return environments, err
|
||||
}
|
||||
|
||||
environments = append(environments, ListEnvironments.EnvironmentIds...)
|
||||
|
||||
//pagination
|
||||
if ListEnvironments.NextToken == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = ListEnvironments.NextToken
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, environments, cache.DefaultExpiration)
|
||||
return environments, nil
|
||||
}
|
||||
|
||||
func CachedCloud9DescribeEnvironments(client AWSCloud9ClientInterface, accountID string, region string, environmentIDs []string) ([]cloud9Types.Environment, error) {
|
||||
var environments []cloud9Types.Environment
|
||||
cacheKey := "cloud9-DescribeEnvironments-" + accountID + "-" + region
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]cloud9Types.Environment), nil
|
||||
}
|
||||
for _, environmentID := range environmentIDs {
|
||||
DescribeEnvironments, err := client.DescribeEnvironments(
|
||||
context.TODO(),
|
||||
&cloud9.DescribeEnvironmentsInput{
|
||||
EnvironmentIds: []string{environmentID},
|
||||
},
|
||||
func(o *cloud9.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return environments, err
|
||||
}
|
||||
|
||||
environments = append(environments, DescribeEnvironments.Environments...)
|
||||
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, environments, cache.DefaultExpiration)
|
||||
return environments, nil
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/cloud9"
|
||||
"github.com/aws/aws-sdk-go-v2/service/cloud9/types"
|
||||
)
|
||||
|
||||
type MockedAWSCloud9Client struct {
|
||||
}
|
||||
|
||||
func (m *MockedAWSCloud9Client) ListEnvironments(ctx context.Context, input *cloud9.ListEnvironmentsInput, options ...func(*cloud9.Options)) (*cloud9.ListEnvironmentsOutput, error) {
|
||||
return &cloud9.ListEnvironmentsOutput{
|
||||
EnvironmentIds: []string{
|
||||
"env1",
|
||||
"env2",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedAWSCloud9Client) DescribeEnvironments(ctx context.Context, input *cloud9.DescribeEnvironmentsInput, options ...func(*cloud9.Options)) (*cloud9.DescribeEnvironmentsOutput, error) {
|
||||
return &cloud9.DescribeEnvironmentsOutput{
|
||||
Environments: []types.Environment{
|
||||
{
|
||||
Name: aws.String("env1"),
|
||||
Arn: aws.String("arn:aws:cloud9:us-east-1:123456789012:environment/env1"),
|
||||
},
|
||||
{
|
||||
Name: aws.String("env2"),
|
||||
Arn: aws.String("arn:aws:cloud9:us-east-1:123456789012:environment/env2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -18,7 +18,7 @@ type CloudFormationClientInterface interface {
|
||||
ListStacks(context.Context, *cloudformation.ListStacksInput, ...func(*cloudformation.Options)) (*cloudformation.ListStacksOutput, error)
|
||||
}
|
||||
|
||||
func RegisterCloudFormationTypes() {
|
||||
func init() {
|
||||
gob.Register([]cloudFormationTypes.Stack{})
|
||||
gob.Register([]cloudFormationTypes.StackSummary{})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/cloudformation"
|
||||
"github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
|
||||
)
|
||||
|
||||
const DESCRIBE_STACKS_TEST_FILE = "./test-data/cloudformation-describestacks.json"
|
||||
const TEMPLATE_BODY_TEST_FILE = "./test-data/cloudformation-getTemplate.json"
|
||||
|
||||
type Stacks struct {
|
||||
Stacks []struct {
|
||||
StackID string `json:"StackId"`
|
||||
Description string `json:"Description"`
|
||||
Tags []interface{} `json:"Tags"`
|
||||
Outputs []types.Output `json:"Outputs"`
|
||||
Parameters []types.Parameter `json:"Parameters"`
|
||||
StackStatusReason interface{} `json:"StackStatusReason"`
|
||||
CreationTime time.Time `json:"CreationTime"`
|
||||
Capabilities []interface{} `json:"Capabilities"`
|
||||
StackName string `json:"StackName"`
|
||||
RoleArn string `json:"RoleArn"`
|
||||
StackStatus string `json:"StackStatus"`
|
||||
DisableRollback bool `json:"DisableRollback"`
|
||||
} `json:"Stacks"`
|
||||
}
|
||||
|
||||
type TemplateBody struct {
|
||||
TemplateBody string `json:"TemplateBody"`
|
||||
}
|
||||
|
||||
type MockedCloudformationClient struct {
|
||||
describeStacks Stacks
|
||||
getTemplateBody TemplateBody
|
||||
}
|
||||
|
||||
func (m *MockedCloudformationClient) DescribeStacks(ctx context.Context, params *cloudformation.DescribeStacksInput, optFns ...func(*cloudformation.Options)) (*cloudformation.DescribeStacksOutput, error) {
|
||||
|
||||
err := json.Unmarshal(readTestFile(DESCRIBE_STACKS_TEST_FILE), &m.describeStacks)
|
||||
if err != nil {
|
||||
log.Fatalf("can't unmarshall file %s", DESCRIBE_STACKS_TEST_FILE)
|
||||
}
|
||||
var stacks []types.Stack
|
||||
for _, stack := range m.describeStacks.Stacks {
|
||||
stacks = append(stacks, types.Stack{
|
||||
StackName: &stack.StackName,
|
||||
RoleARN: &stack.RoleArn,
|
||||
Outputs: stack.Outputs,
|
||||
Parameters: stack.Parameters,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
return &cloudformation.DescribeStacksOutput{Stacks: stacks}, nil
|
||||
}
|
||||
|
||||
func (m *MockedCloudformationClient) GetTemplate(ctx context.Context, params *cloudformation.GetTemplateInput, optFns ...func(*cloudformation.Options)) (*cloudformation.GetTemplateOutput, error) {
|
||||
err := json.Unmarshal(readTestFile(DESCRIBE_STACKS_TEST_FILE), &m.getTemplateBody)
|
||||
if err != nil {
|
||||
log.Fatalf("can't unmarshall file %s", TEMPLATE_BODY_TEST_FILE)
|
||||
}
|
||||
|
||||
return &cloudformation.GetTemplateOutput{TemplateBody: &m.getTemplateBody.TemplateBody}, nil
|
||||
}
|
||||
|
||||
func (m *MockedCloudformationClient) ListStacks(ctx context.Context, params *cloudformation.ListStacksInput, optFns ...func(*cloudformation.Options)) (*cloudformation.ListStacksOutput, error) {
|
||||
return &cloudformation.ListStacksOutput{}, nil
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/service/cloudfront"
|
||||
cloudfrontTypes "github.com/aws/aws-sdk-go-v2/service/cloudfront/types"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
type AWSCloudFrontClientInterface interface {
|
||||
ListDistributions(ctx context.Context, params *cloudfront.ListDistributionsInput, optFns ...func(*cloudfront.Options)) (*cloudfront.ListDistributionsOutput, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register([]cloudfrontTypes.DistributionSummary{})
|
||||
gob.Register(cloudfrontTypes.DistributionSummary{})
|
||||
}
|
||||
|
||||
func CachedCloudFrontListDistributions(CloudFrontClient AWSCloudFrontClientInterface, accountID string) ([]cloudfrontTypes.DistributionSummary, error) {
|
||||
var PaginationControl *string
|
||||
var distributions []cloudfrontTypes.DistributionSummary
|
||||
cacheKey := "cloudfront-ListDistributions-" + accountID
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
sharedLogger.Debug("Using cached CloudFront distributions data")
|
||||
return cached.([]cloudfrontTypes.DistributionSummary), nil
|
||||
}
|
||||
|
||||
for {
|
||||
ListDistributions, err := CloudFrontClient.ListDistributions(
|
||||
context.TODO(),
|
||||
&cloudfront.ListDistributionsInput{
|
||||
Marker: PaginationControl,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
distributions = append(distributions, ListDistributions.DistributionList.Items...)
|
||||
|
||||
// Pagination control.
|
||||
if ListDistributions.DistributionList.NextMarker != nil {
|
||||
PaginationControl = ListDistributions.DistributionList.NextMarker
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, distributions, cache.DefaultExpiration)
|
||||
return distributions, nil
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/cloudfront"
|
||||
cloudfrontTypes "github.com/aws/aws-sdk-go-v2/service/cloudfront/types"
|
||||
)
|
||||
|
||||
type MockedAWSCloudFrontClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedAWSCloudFrontClient) ListDistributions(ctx context.Context, input *cloudfront.ListDistributionsInput, options ...func(*cloudfront.Options)) (*cloudfront.ListDistributionsOutput, error) {
|
||||
return &cloudfront.ListDistributionsOutput{
|
||||
DistributionList: &cloudfrontTypes.DistributionList{
|
||||
Items: []cloudfrontTypes.DistributionSummary{
|
||||
{
|
||||
Id: aws.String("distribution1"),
|
||||
},
|
||||
{
|
||||
Id: aws.String("distribution2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/service/codeartifact"
|
||||
codeArtifactTypes "github.com/aws/aws-sdk-go-v2/service/codeartifact/types"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
type AWSCodeArtifactClientInterface interface {
|
||||
ListDomains(context.Context, *codeartifact.ListDomainsInput, ...func(*codeartifact.Options)) (*codeartifact.ListDomainsOutput, error)
|
||||
ListRepositories(context.Context, *codeartifact.ListRepositoriesInput, ...func(*codeartifact.Options)) (*codeartifact.ListRepositoriesOutput, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register([]codeArtifactTypes.DomainSummary{})
|
||||
gob.Register([]codeArtifactTypes.RepositorySummary{})
|
||||
}
|
||||
|
||||
func CachedCodeArtifactListDomains(client AWSCodeArtifactClientInterface, accountID string, region string) ([]codeArtifactTypes.DomainSummary, error) {
|
||||
var PaginationControl *string
|
||||
var domains []codeArtifactTypes.DomainSummary
|
||||
cacheKey := fmt.Sprintf("%s-codeartifact-ListDomains-%s", accountID, region)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]codeArtifactTypes.DomainSummary), nil
|
||||
}
|
||||
for {
|
||||
ListDomains, err := client.ListDomains(
|
||||
context.TODO(),
|
||||
&codeartifact.ListDomainsInput{
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *codeartifact.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return domains, err
|
||||
}
|
||||
|
||||
domains = append(domains, ListDomains.Domains...)
|
||||
|
||||
//pagination
|
||||
if ListDomains.NextToken == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = ListDomains.NextToken
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, domains, cache.DefaultExpiration)
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
func CachedCodeArtifactListRepositories(client AWSCodeArtifactClientInterface, accountID string, region string) ([]codeArtifactTypes.RepositorySummary, error) {
|
||||
var PaginationControl *string
|
||||
var repositories []codeArtifactTypes.RepositorySummary
|
||||
cacheKey := fmt.Sprintf("%s-codeartifact-ListRepositories-%s", accountID, region)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]codeArtifactTypes.RepositorySummary), nil
|
||||
}
|
||||
for {
|
||||
ListRepositories, err := client.ListRepositories(
|
||||
context.TODO(),
|
||||
&codeartifact.ListRepositoriesInput{
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *codeartifact.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return repositories, err
|
||||
}
|
||||
|
||||
repositories = append(repositories, ListRepositories.Repositories...)
|
||||
|
||||
//pagination
|
||||
if ListRepositories.NextToken == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = ListRepositories.NextToken
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, repositories, cache.DefaultExpiration)
|
||||
return repositories, nil
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/codeartifact"
|
||||
codeArtifactTypes "github.com/aws/aws-sdk-go-v2/service/codeartifact/types"
|
||||
)
|
||||
|
||||
type MockedAWSCodeArtifactClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedAWSCodeArtifactClient) ListRepositories(ctx context.Context, input *codeartifact.ListRepositoriesInput, options ...func(*codeartifact.Options)) (*codeartifact.ListRepositoriesOutput, error) {
|
||||
return &codeartifact.ListRepositoriesOutput{
|
||||
NextToken: nil,
|
||||
Repositories: []codeArtifactTypes.RepositorySummary{
|
||||
{
|
||||
Name: aws.String("repo1"),
|
||||
Arn: aws.String("arn:aws:codeartifact:us-east-1:123456789012:repository/repo1"),
|
||||
DomainName: aws.String("domain1"),
|
||||
},
|
||||
{
|
||||
Name: aws.String("repo2"),
|
||||
Arn: aws.String("arn:aws:codeartifact:us-east-1:123456789012:repository/repo2"),
|
||||
DomainName: aws.String("domain1"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedAWSCodeArtifactClient) ListDomains(ctx context.Context, input *codeartifact.ListDomainsInput, options ...func(*codeartifact.Options)) (*codeartifact.ListDomainsOutput, error) {
|
||||
return &codeartifact.ListDomainsOutput{
|
||||
NextToken: nil,
|
||||
Domains: []codeArtifactTypes.DomainSummary{
|
||||
{
|
||||
Name: aws.String("domain1"),
|
||||
Arn: aws.String("arn:aws:codeartifact:us-east-1:123456789012:domain/domain1"),
|
||||
},
|
||||
{
|
||||
Name: aws.String("domain2"),
|
||||
Arn: aws.String("arn:aws:codeartifact:us-east-1:123456789012:domain/domain2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -18,7 +18,7 @@ type CodeBuildClientInterface interface {
|
||||
GetResourcePolicy(ctx context.Context, params *codebuild.GetResourcePolicyInput, optFns ...func(*codebuild.Options)) (*codebuild.GetResourcePolicyOutput, error)
|
||||
}
|
||||
|
||||
func RegisterCodeBuildTypes() {
|
||||
func init() {
|
||||
gob.Register(codeBuildTypes.Project{})
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/codebuild"
|
||||
)
|
||||
|
||||
type MockedCodeBuildClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedCodeBuildClient) ListProjects(ctx context.Context, input *codebuild.ListProjectsInput, options ...func(*codebuild.Options)) (*codebuild.ListProjectsOutput, error) {
|
||||
return &codebuild.ListProjectsOutput{
|
||||
Projects: []string{
|
||||
"project1",
|
||||
"project2",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedCodeBuildClient) GetResourcePolicy(ctx context.Context, input *codebuild.GetResourcePolicyInput, options ...func(*codebuild.Options)) (*codebuild.GetResourcePolicyOutput, error) {
|
||||
return &codebuild.GetResourcePolicyOutput{
|
||||
Policy: aws.String(`{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "codebuild:BatchGetProjects",
|
||||
"Resource": "*",
|
||||
"Principal": {
|
||||
"AWS": "arn:aws:iam::123456789012:root"
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
`),
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/service/codecommit"
|
||||
codeCommitTypes "github.com/aws/aws-sdk-go-v2/service/codecommit/types"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
type AWSCodeCommitClientInterface interface {
|
||||
ListRepositories(context.Context, *codecommit.ListRepositoriesInput, ...func(*codecommit.Options)) (*codecommit.ListRepositoriesOutput, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register([]codeCommitTypes.RepositoryNameIdPair{})
|
||||
}
|
||||
|
||||
func CachedCodeCommitListRepositories(client AWSCodeCommitClientInterface, accountID string, region string) ([]codeCommitTypes.RepositoryNameIdPair, error) {
|
||||
var PaginationControl *string
|
||||
var repositories []codeCommitTypes.RepositoryNameIdPair
|
||||
cacheKey := fmt.Sprintf("%s-codecommit-ListRepositories-%s", accountID, region)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]codeCommitTypes.RepositoryNameIdPair), nil
|
||||
}
|
||||
for {
|
||||
ListRepositories, err := client.ListRepositories(
|
||||
context.TODO(),
|
||||
&codecommit.ListRepositoriesInput{
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *codecommit.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return repositories, err
|
||||
}
|
||||
|
||||
repositories = append(repositories, ListRepositories.Repositories...)
|
||||
|
||||
//pagination
|
||||
if ListRepositories.NextToken == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = ListRepositories.NextToken
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, repositories, cache.DefaultExpiration)
|
||||
return repositories, nil
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/codecommit"
|
||||
codeCommitTypes "github.com/aws/aws-sdk-go-v2/service/codecommit/types"
|
||||
)
|
||||
|
||||
// create mocks for codecommit ListRepositories
|
||||
|
||||
type MockedAWSCodeCommitClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedAWSCodeCommitClient) ListRepositories(ctx context.Context, input *codecommit.ListRepositoriesInput, options ...func(*codecommit.Options)) (*codecommit.ListRepositoriesOutput, error) {
|
||||
return &codecommit.ListRepositoriesOutput{
|
||||
NextToken: nil,
|
||||
Repositories: []codeCommitTypes.RepositoryNameIdPair{
|
||||
{
|
||||
RepositoryId: aws.String("repo1"),
|
||||
RepositoryName: aws.String("repo1"),
|
||||
},
|
||||
{
|
||||
RepositoryId: aws.String("repo2"),
|
||||
RepositoryName: aws.String("repo2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/service/codedeploy"
|
||||
codeDeployTypes "github.com/aws/aws-sdk-go-v2/service/codedeploy/types"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
type AWSCodeDeployClientInterface interface {
|
||||
ListApplications(context.Context, *codedeploy.ListApplicationsInput, ...func(*codedeploy.Options)) (*codedeploy.ListApplicationsOutput, error)
|
||||
GetApplication(context.Context, *codedeploy.GetApplicationInput, ...func(*codedeploy.Options)) (*codedeploy.GetApplicationOutput, error)
|
||||
ListDeployments(context.Context, *codedeploy.ListDeploymentsInput, ...func(*codedeploy.Options)) (*codedeploy.ListDeploymentsOutput, error)
|
||||
GetDeployment(context.Context, *codedeploy.GetDeploymentInput, ...func(*codedeploy.Options)) (*codedeploy.GetDeploymentOutput, error)
|
||||
ListDeploymentConfigs(context.Context, *codedeploy.ListDeploymentConfigsInput, ...func(*codedeploy.Options)) (*codedeploy.ListDeploymentConfigsOutput, error)
|
||||
GetDeploymentConfig(context.Context, *codedeploy.GetDeploymentConfigInput, ...func(*codedeploy.Options)) (*codedeploy.GetDeploymentConfigOutput, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register([]string{})
|
||||
gob.Register([]codeDeployTypes.ApplicationInfo{})
|
||||
gob.Register([]codeDeployTypes.DeploymentConfigInfo{})
|
||||
gob.Register([]codeDeployTypes.DeploymentInfo{})
|
||||
|
||||
}
|
||||
|
||||
func CachedCodeDeployListApplications(client AWSCodeDeployClientInterface, accountID string, region string) ([]string, error) {
|
||||
var PaginationControl *string
|
||||
var applications []string
|
||||
cacheKey := fmt.Sprintf("%s-codedeploy-ListApplications-%s", accountID, region)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]string), nil
|
||||
}
|
||||
for {
|
||||
ListApplications, err := client.ListApplications(
|
||||
context.TODO(),
|
||||
&codedeploy.ListApplicationsInput{
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *codedeploy.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return applications, err
|
||||
}
|
||||
|
||||
applications = append(applications, ListApplications.Applications...)
|
||||
|
||||
//pagination
|
||||
if ListApplications.NextToken == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = ListApplications.NextToken
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, applications, cache.DefaultExpiration)
|
||||
return applications, nil
|
||||
}
|
||||
|
||||
func CachedCodeDeployListDeploymentConfigs(client AWSCodeDeployClientInterface, accountID string, region string) ([]string, error) {
|
||||
var PaginationControl *string
|
||||
var deploymentConfigs []string
|
||||
cacheKey := fmt.Sprintf("%s-codedeploy-ListDeploymentConfigs-%s", accountID, region)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]string), nil
|
||||
}
|
||||
for {
|
||||
ListDeploymentConfigs, err := client.ListDeploymentConfigs(
|
||||
context.TODO(),
|
||||
&codedeploy.ListDeploymentConfigsInput{
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *codedeploy.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return deploymentConfigs, err
|
||||
}
|
||||
|
||||
deploymentConfigs = append(deploymentConfigs, ListDeploymentConfigs.DeploymentConfigsList...)
|
||||
|
||||
//pagination
|
||||
if ListDeploymentConfigs.NextToken == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = ListDeploymentConfigs.NextToken
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, deploymentConfigs, cache.DefaultExpiration)
|
||||
return deploymentConfigs, nil
|
||||
}
|
||||
|
||||
func CachedCodeDeployListDeployments(client AWSCodeDeployClientInterface, accountID string, region string) ([]string, error) {
|
||||
var PaginationControl *string
|
||||
var deployments []string
|
||||
cacheKey := fmt.Sprintf("%s-codedeploy-ListDeployments-%s", accountID, region)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]string), nil
|
||||
}
|
||||
for {
|
||||
ListDeployments, err := client.ListDeployments(
|
||||
context.TODO(),
|
||||
&codedeploy.ListDeploymentsInput{
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *codedeploy.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return deployments, err
|
||||
}
|
||||
|
||||
deployments = append(deployments, ListDeployments.Deployments...)
|
||||
|
||||
//pagination
|
||||
if ListDeployments.NextToken == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = ListDeployments.NextToken
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, deployments, cache.DefaultExpiration)
|
||||
return deployments, nil
|
||||
}
|
||||
|
||||
func CachedCodeDeployGetApplication(client AWSCodeDeployClientInterface, accountID string, region string, applicationName string) (codeDeployTypes.ApplicationInfo, error) {
|
||||
cacheKey := fmt.Sprintf("%s-codedeploy-GetApplication-%s-%s", accountID, region, applicationName)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.(codeDeployTypes.ApplicationInfo), nil
|
||||
}
|
||||
GetApplication, err := client.GetApplication(
|
||||
context.TODO(),
|
||||
&codedeploy.GetApplicationInput{
|
||||
ApplicationName: &applicationName,
|
||||
},
|
||||
func(o *codedeploy.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return *GetApplication.Application, err
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, *GetApplication.Application, cache.DefaultExpiration)
|
||||
|
||||
return *GetApplication.Application, nil
|
||||
}
|
||||
|
||||
func CachedCodeDeployGetDeploymentConfig(client AWSCodeDeployClientInterface, accountID string, region string, deploymentConfigName string) (codeDeployTypes.DeploymentConfigInfo, error) {
|
||||
cacheKey := fmt.Sprintf("%s-codedeploy-GetDeploymentConfig-%s-%s", accountID, region, deploymentConfigName)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.(codeDeployTypes.DeploymentConfigInfo), nil
|
||||
}
|
||||
GetDeploymentConfig, err := client.GetDeploymentConfig(
|
||||
context.TODO(),
|
||||
&codedeploy.GetDeploymentConfigInput{
|
||||
DeploymentConfigName: &deploymentConfigName,
|
||||
},
|
||||
func(o *codedeploy.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return *GetDeploymentConfig.DeploymentConfigInfo, err
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, *GetDeploymentConfig.DeploymentConfigInfo, cache.DefaultExpiration)
|
||||
|
||||
return *GetDeploymentConfig.DeploymentConfigInfo, nil
|
||||
}
|
||||
|
||||
func CachedCodeDeployGetDeployment(client AWSCodeDeployClientInterface, accountID string, region string, deploymentID string) (codeDeployTypes.DeploymentInfo, error) {
|
||||
cacheKey := fmt.Sprintf("%s-codedeploy-GetDeployment-%s-%s", accountID, region, deploymentID)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.(codeDeployTypes.DeploymentInfo), nil
|
||||
}
|
||||
GetDeployment, err := client.GetDeployment(
|
||||
context.TODO(),
|
||||
&codedeploy.GetDeploymentInput{
|
||||
DeploymentId: &deploymentID,
|
||||
},
|
||||
func(o *codedeploy.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return *GetDeployment.DeploymentInfo, err
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, *GetDeployment.DeploymentInfo, cache.DefaultExpiration)
|
||||
|
||||
return *GetDeployment.DeploymentInfo, nil
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/codedeploy"
|
||||
codedeployTypes "github.com/aws/aws-sdk-go-v2/service/codedeploy/types"
|
||||
)
|
||||
|
||||
type MockedCodedeployClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedCodedeployClient) ListApplications(ctx context.Context, input *codedeploy.ListApplicationsInput, options ...func(*codedeploy.Options)) (*codedeploy.ListApplicationsOutput, error) {
|
||||
return &codedeploy.ListApplicationsOutput{
|
||||
Applications: []string{
|
||||
"app1",
|
||||
"app2",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedCodedeployClient) ListDeployments(ctx context.Context, input *codedeploy.ListDeploymentsInput, options ...func(*codedeploy.Options)) (*codedeploy.ListDeploymentsOutput, error) {
|
||||
return &codedeploy.ListDeploymentsOutput{
|
||||
Deployments: []string{
|
||||
"deployment1",
|
||||
"deployment2",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedCodedeployClient) ListDeploymentConfigs(ctx context.Context, input *codedeploy.ListDeploymentConfigsInput, options ...func(*codedeploy.Options)) (*codedeploy.ListDeploymentConfigsOutput, error) {
|
||||
return &codedeploy.ListDeploymentConfigsOutput{
|
||||
DeploymentConfigsList: []string{
|
||||
"deploymentConfig1",
|
||||
"deploymentConfig2",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedCodedeployClient) GetApplication(ctx context.Context, input *codedeploy.GetApplicationInput, options ...func(*codedeploy.Options)) (*codedeploy.GetApplicationOutput, error) {
|
||||
return &codedeploy.GetApplicationOutput{
|
||||
Application: &codedeployTypes.ApplicationInfo{
|
||||
ApplicationName: aws.String("application1"),
|
||||
ApplicationId: aws.String("application1"),
|
||||
CreateTime: aws.Time(time.Now()),
|
||||
GitHubAccountName: aws.String("github"),
|
||||
LinkedToGitHub: true,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedCodedeployClient) GetDeployment(ctx context.Context, input *codedeploy.GetDeploymentInput, options ...func(*codedeploy.Options)) (*codedeploy.GetDeploymentOutput, error) {
|
||||
return &codedeploy.GetDeploymentOutput{
|
||||
DeploymentInfo: &codedeployTypes.DeploymentInfo{
|
||||
ApplicationName: aws.String("application1"),
|
||||
CreateTime: aws.Time(time.Now()),
|
||||
DeploymentId: aws.String("deployment1"),
|
||||
DeploymentOverview: &codedeployTypes.DeploymentOverview{
|
||||
Failed: 0,
|
||||
InProgress: 0,
|
||||
Pending: 0,
|
||||
Ready: 0,
|
||||
Skipped: 0,
|
||||
Succeeded: 0,
|
||||
},
|
||||
DeploymentConfigName: aws.String("deploymentConfig1"),
|
||||
Status: codedeployTypes.DeploymentStatusSucceeded,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedCodedeployClient) GetDeploymentConfig(ctx context.Context, input *codedeploy.GetDeploymentConfigInput, options ...func(*codedeploy.Options)) (*codedeploy.GetDeploymentConfigOutput, error) {
|
||||
return &codedeploy.GetDeploymentConfigOutput{
|
||||
DeploymentConfigInfo: &codedeployTypes.DeploymentConfigInfo{
|
||||
DeploymentConfigName: aws.String("deploymentConfig1"),
|
||||
ComputePlatform: codedeployTypes.ComputePlatformServer,
|
||||
MinimumHealthyHosts: &codedeployTypes.MinimumHealthyHosts{
|
||||
Type: codedeployTypes.MinimumHealthyHostsTypeFleetPercent,
|
||||
Value: 100,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/service/datapipeline"
|
||||
dataPipelineTypes "github.com/aws/aws-sdk-go-v2/service/datapipeline/types"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
type AWSDataPipelineClientInterface interface {
|
||||
ListPipelines(ctx context.Context, input *datapipeline.ListPipelinesInput, opts ...func(*datapipeline.Options)) (*datapipeline.ListPipelinesOutput, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register([]dataPipelineTypes.PipelineIdName{})
|
||||
}
|
||||
|
||||
func CachedDataPipelineListPipelines(client AWSDataPipelineClientInterface, accountID string, region string) ([]dataPipelineTypes.PipelineIdName, error) {
|
||||
var PaginationControl *string
|
||||
var pipelines []dataPipelineTypes.PipelineIdName
|
||||
cacheKey := fmt.Sprintf("%s-datapipeline-ListPipelines-%s", accountID, region)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]dataPipelineTypes.PipelineIdName), nil
|
||||
}
|
||||
for {
|
||||
ListPipelines, err := client.ListPipelines(
|
||||
context.TODO(),
|
||||
&datapipeline.ListPipelinesInput{
|
||||
Marker: PaginationControl,
|
||||
},
|
||||
func(o *datapipeline.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return pipelines, err
|
||||
}
|
||||
|
||||
pipelines = append(pipelines, ListPipelines.PipelineIdList...)
|
||||
|
||||
//pagination
|
||||
if ListPipelines.Marker == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = ListPipelines.Marker
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, pipelines, cache.DefaultExpiration)
|
||||
return pipelines, nil
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/datapipeline"
|
||||
dataPipelineTypes "github.com/aws/aws-sdk-go-v2/service/datapipeline/types"
|
||||
)
|
||||
|
||||
type MockedDataPipelineClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedDataPipelineClient) ListPipelines(ctx context.Context, input *datapipeline.ListPipelinesInput, options ...func(*datapipeline.Options)) (*datapipeline.ListPipelinesOutput, error) {
|
||||
return &datapipeline.ListPipelinesOutput{
|
||||
PipelineIdList: []dataPipelineTypes.PipelineIdName{
|
||||
{
|
||||
Id: aws.String("pipeline1"),
|
||||
Name: aws.String("pipeline1"),
|
||||
},
|
||||
{
|
||||
Id: aws.String("pipeline2"),
|
||||
Name: aws.String("pipeline2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
+1
-1
@@ -19,7 +19,7 @@ type DocDBClientInterface interface {
|
||||
DescribeDBInstances(context.Context, *docdb.DescribeDBInstancesInput, ...func(*docdb.Options)) (*docdb.DescribeDBInstancesOutput, error)
|
||||
}
|
||||
|
||||
func RegisterDocDBTypes() {
|
||||
func init() {
|
||||
gob.Register([]docdbTypes.GlobalCluster{})
|
||||
gob.Register([]docdbTypes.DBCluster{})
|
||||
//gob.Register([]docdbTypes.DBInstance{})
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/docdb"
|
||||
docdbTypes "github.com/aws/aws-sdk-go-v2/service/docdb/types"
|
||||
)
|
||||
|
||||
type MockedAWSDocsDBClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedAWSDocsDBClient) DescribeDBClusters(ctx context.Context, input *docdb.DescribeDBClustersInput, options ...func(*docdb.Options)) (*docdb.DescribeDBClustersOutput, error) {
|
||||
return &docdb.DescribeDBClustersOutput{
|
||||
DBClusters: []docdbTypes.DBCluster{
|
||||
{
|
||||
DBClusterIdentifier: aws.String("cluster1"),
|
||||
},
|
||||
{
|
||||
DBClusterIdentifier: aws.String("cluster2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
+1
-1
@@ -16,7 +16,7 @@ type DynamoDBClientInterface interface {
|
||||
DescribeTable(context.Context, *dynamodb.DescribeTableInput, ...func(*dynamodb.Options)) (*dynamodb.DescribeTableOutput, error)
|
||||
}
|
||||
|
||||
func RegisterDynamoDBTypes() {
|
||||
func init() {
|
||||
gob.Register([]string{})
|
||||
gob.Register(dynamoDBTypes.TableDescription{})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
|
||||
dynamodbTypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
||||
)
|
||||
|
||||
type MockedAWSDynamoDBClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedAWSDynamoDBClient) ListTables() ([]string, error) {
|
||||
return []string{"table1", "table2"}, nil
|
||||
}
|
||||
|
||||
func (m *MockedAWSDynamoDBClient) DescribeTable(ctx context.Context, input *dynamodb.DescribeTableInput, options ...func(*dynamodb.Options)) (*dynamodb.DescribeTableOutput, error) {
|
||||
return &dynamodb.DescribeTableOutput{
|
||||
Table: &dynamodbTypes.TableDescription{
|
||||
TableName: input.TableName,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
+173
-3
@@ -6,20 +6,31 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
||||
ec2Types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
type EC2ClientInterface interface {
|
||||
type AWSEC2ClientInterface interface {
|
||||
DescribeInstances(context.Context, *ec2.DescribeInstancesInput, ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error)
|
||||
DescribeNetworkInterfaces(context.Context, *ec2.DescribeNetworkInterfacesInput, ...func(*ec2.Options)) (*ec2.DescribeNetworkInterfacesOutput, error)
|
||||
DescribeSnapshots(context.Context, *ec2.DescribeSnapshotsInput, ...func(*ec2.Options)) (*ec2.DescribeSnapshotsOutput, error)
|
||||
DescribeVolumes(context.Context, *ec2.DescribeVolumesInput, ...func(*ec2.Options)) (*ec2.DescribeVolumesOutput, error)
|
||||
DescribeImages(context.Context, *ec2.DescribeImagesInput, ...func(*ec2.Options)) (*ec2.DescribeImagesOutput, error)
|
||||
DescribeInstanceAttribute(context.Context, *ec2.DescribeInstanceAttributeInput, ...func(*ec2.Options)) (*ec2.DescribeInstanceAttributeOutput, error)
|
||||
}
|
||||
|
||||
func RegisterEC2Types() {
|
||||
func init() {
|
||||
gob.Register([]ec2Types.Instance{})
|
||||
gob.Register([]ec2Types.NetworkInterface{})
|
||||
gob.Register([]ec2Types.Snapshot{})
|
||||
gob.Register([]ec2Types.Volume{})
|
||||
gob.Register([]ec2Types.Image{})
|
||||
|
||||
}
|
||||
|
||||
func CachedEC2DescribeInstances(client EC2ClientInterface, accountID string, region string) ([]ec2Types.Instance, error) {
|
||||
func CachedEC2DescribeInstances(client AWSEC2ClientInterface, accountID string, region string) ([]ec2Types.Instance, error) {
|
||||
var PaginationControl *string
|
||||
var instances []ec2Types.Instance
|
||||
cacheKey := fmt.Sprintf("%s-ec2-DescribeInstances-%s", accountID, region)
|
||||
@@ -56,3 +67,162 @@ func CachedEC2DescribeInstances(client EC2ClientInterface, accountID string, reg
|
||||
internal.Cache.Set(cacheKey, instances, cache.DefaultExpiration)
|
||||
return instances, nil
|
||||
}
|
||||
|
||||
func CachedEC2DescribeInstanceAttributeUserData(client AWSEC2ClientInterface, accountID string, region string, instanceID string) (string, error) {
|
||||
cacheKey := fmt.Sprintf("%s-ec2-DescribeInstanceAttributeUserData-%s-%s", accountID, region, instanceID)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.(string), nil
|
||||
}
|
||||
DescribeInstanceAttribute, err := client.DescribeInstanceAttribute(
|
||||
context.TODO(),
|
||||
&ec2.DescribeInstanceAttributeInput{
|
||||
Attribute: ec2Types.InstanceAttributeNameUserData,
|
||||
InstanceId: &instanceID,
|
||||
},
|
||||
func(o *ec2.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
internal.Cache.Set(cacheKey, aws.ToString(DescribeInstanceAttribute.UserData.Value), cache.DefaultExpiration)
|
||||
|
||||
return aws.ToString(DescribeInstanceAttribute.UserData.Value), nil
|
||||
}
|
||||
|
||||
func CachedEC2DescribeNetworkInterfaces(client AWSEC2ClientInterface, accountID string, region string) ([]ec2Types.NetworkInterface, error) {
|
||||
var PaginationControl *string
|
||||
var NetworkInterfaces []ec2Types.NetworkInterface
|
||||
cacheKey := fmt.Sprintf("%s-ec2-DescribeNetworkInterfaces-%s", accountID, region)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]ec2Types.NetworkInterface), nil
|
||||
}
|
||||
for {
|
||||
DescribeNetworkInterfaces, err := client.DescribeNetworkInterfaces(
|
||||
context.TODO(),
|
||||
&(ec2.DescribeNetworkInterfacesInput{
|
||||
NextToken: PaginationControl,
|
||||
}),
|
||||
func(o *ec2.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return NetworkInterfaces, err
|
||||
}
|
||||
NetworkInterfaces = append(NetworkInterfaces, DescribeNetworkInterfaces.NetworkInterfaces...)
|
||||
|
||||
if DescribeNetworkInterfaces.NextToken == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = DescribeNetworkInterfaces.NextToken
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, NetworkInterfaces, cache.DefaultExpiration)
|
||||
return NetworkInterfaces, nil
|
||||
}
|
||||
|
||||
func CachedEC2DescribeSnapshots(client AWSEC2ClientInterface, accountID string, region string) ([]ec2Types.Snapshot, error) {
|
||||
var PaginationControl *string
|
||||
var Snapshots []ec2Types.Snapshot
|
||||
cacheKey := fmt.Sprintf("%s-ec2-DescribeSnapshots-%s", accountID, region)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]ec2Types.Snapshot), nil
|
||||
}
|
||||
for {
|
||||
DescribeSnapshots, err := client.DescribeSnapshots(
|
||||
context.TODO(),
|
||||
&(ec2.DescribeSnapshotsInput{
|
||||
NextToken: PaginationControl,
|
||||
OwnerIds: []string{accountID},
|
||||
}),
|
||||
func(o *ec2.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return Snapshots, err
|
||||
}
|
||||
Snapshots = append(Snapshots, DescribeSnapshots.Snapshots...)
|
||||
|
||||
if DescribeSnapshots.NextToken == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = DescribeSnapshots.NextToken
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, Snapshots, cache.DefaultExpiration)
|
||||
return Snapshots, nil
|
||||
}
|
||||
|
||||
func CachedEC2DescribeVolumes(client AWSEC2ClientInterface, accountID string, region string) ([]ec2Types.Volume, error) {
|
||||
var PaginationControl *string
|
||||
var Volumes []ec2Types.Volume
|
||||
cacheKey := fmt.Sprintf("%s-ec2-DescribeVolumes-%s", accountID, region)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]ec2Types.Volume), nil
|
||||
}
|
||||
for {
|
||||
DescribeVolumes, err := client.DescribeVolumes(
|
||||
context.TODO(),
|
||||
&(ec2.DescribeVolumesInput{
|
||||
NextToken: PaginationControl,
|
||||
}),
|
||||
func(o *ec2.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return Volumes, err
|
||||
}
|
||||
Volumes = append(Volumes, DescribeVolumes.Volumes...)
|
||||
|
||||
if DescribeVolumes.NextToken == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = DescribeVolumes.NextToken
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, Volumes, cache.DefaultExpiration)
|
||||
return Volumes, nil
|
||||
}
|
||||
|
||||
func CachedEC2DescribeImages(client AWSEC2ClientInterface, accountID string, region string) ([]ec2Types.Image, error) {
|
||||
var PaginationControl *string
|
||||
var Images []ec2Types.Image
|
||||
cacheKey := fmt.Sprintf("%s-ec2-DescribeImages-%s", accountID, region)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]ec2Types.Image), nil
|
||||
}
|
||||
for {
|
||||
DescribeImages, err := client.DescribeImages(
|
||||
context.TODO(),
|
||||
&(ec2.DescribeImagesInput{
|
||||
Owners: []string{accountID},
|
||||
NextToken: PaginationControl,
|
||||
}),
|
||||
func(o *ec2.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return Images, err
|
||||
}
|
||||
Images = append(Images, DescribeImages.Images...)
|
||||
|
||||
if DescribeImages.NextToken == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = DescribeImages.NextToken
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, Images, cache.DefaultExpiration)
|
||||
return Images, nil
|
||||
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
package sdk
|
||||
|
||||
// create test for the Cached EC2 functions. Create a mocked client and mocked functions for each of the methods used in the ec2.go file
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
||||
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
||||
)
|
||||
|
||||
const DESCRIBE_NETWORK_INTEFACES_TEST_FILE = "./test-data/describe-network-interfaces.json"
|
||||
const DESCRIBE_INSTANCES_TEST_FILE = "./test-data/ec2-describeInstances.json"
|
||||
const DESCRIBE_SNAPSHOTS_TEST_FILE = "./test-data/ec2-describeSnapshots.json"
|
||||
const DESCRIBE_VOLUMES_TEST_FILE = "./test-data/ec2-describeVolumes.json"
|
||||
const DESCRIBE_IMAGES_TEST_FILE = "./test-data/ec2-describeImages.json"
|
||||
|
||||
type MockedEC2Client2 struct {
|
||||
describeNetworkInterfaces DescribeNetworkInterfaces
|
||||
describeInstances DescribeInstances
|
||||
describeSnapshots DescribeSnapshots
|
||||
describeVolumes DescribeVolumes
|
||||
describeImages DescribeImages
|
||||
}
|
||||
|
||||
type DescribeNetworkInterfaces struct {
|
||||
NetworkInterfaces []struct {
|
||||
Status string `json:"Status"`
|
||||
MacAddress string `json:"MacAddress"`
|
||||
SourceDestCheck bool `json:"SourceDestCheck"`
|
||||
VpcID string `json:"VpcId"`
|
||||
Description string `json:"Description"`
|
||||
Association struct {
|
||||
PublicIP string `json:"PublicIp"`
|
||||
AssociationID string `json:"AssociationId"`
|
||||
PublicDNSName string `json:"PublicDnsName"`
|
||||
IPOwnerID string `json:"IpOwnerId"`
|
||||
} `json:"Association"`
|
||||
NetworkInterfaceID string `json:"NetworkInterfaceId"`
|
||||
PrivateIPAddresses []struct {
|
||||
PrivateDNSName string `json:"PrivateDnsName"`
|
||||
Association struct {
|
||||
PublicIP string `json:"PublicIp"`
|
||||
AssociationID string `json:"AssociationId"`
|
||||
PublicDNSName string `json:"PublicDnsName"`
|
||||
IPOwnerID string `json:"IpOwnerId"`
|
||||
} `json:"Association"`
|
||||
Primary bool `json:"Primary"`
|
||||
PrivateIPAddress string `json:"PrivateIpAddress"`
|
||||
} `json:"PrivateIpAddresses"`
|
||||
RequesterManaged bool `json:"RequesterManaged"`
|
||||
Ipv6Addresses []interface{} `json:"Ipv6Addresses"`
|
||||
PrivateDNSName string `json:"PrivateDnsName,omitempty"`
|
||||
AvailabilityZone string `json:"AvailabilityZone"`
|
||||
Attachment struct {
|
||||
Status string `json:"Status"`
|
||||
DeviceIndex int `json:"DeviceIndex"`
|
||||
AttachTime time.Time `json:"AttachTime"`
|
||||
InstanceID string `json:"InstanceId"`
|
||||
DeleteOnTermination bool `json:"DeleteOnTermination"`
|
||||
AttachmentID string `json:"AttachmentId"`
|
||||
InstanceOwnerID string `json:"InstanceOwnerId"`
|
||||
} `json:"Attachment"`
|
||||
Groups []struct {
|
||||
GroupName string `json:"GroupName"`
|
||||
GroupID string `json:"GroupId"`
|
||||
} `json:"Groups"`
|
||||
SubnetID string `json:"SubnetId"`
|
||||
OwnerID string `json:"OwnerId"`
|
||||
TagSet []interface{} `json:"TagSet"`
|
||||
PrivateIPAddress string `json:"PrivateIpAddress"`
|
||||
} `json:"NetworkInterfaces"`
|
||||
}
|
||||
|
||||
type DescribeInstances struct {
|
||||
Reservations []struct {
|
||||
Instances []struct {
|
||||
AmiLaunchIndex int `json:"AmiLaunchIndex"`
|
||||
ImageID string `json:"ImageId"`
|
||||
InstanceID string `json:"InstanceId"`
|
||||
InstanceType string `json:"InstanceType"`
|
||||
KernelID string `json:"KernelId"`
|
||||
KeyName string `json:"KeyName"`
|
||||
LaunchTime string `json:"LaunchTime"`
|
||||
Monitoring struct {
|
||||
State string `json:"State"`
|
||||
} `json:"Monitoring"`
|
||||
Placement struct {
|
||||
AvailabilityZone string `json:"AvailabilityZone"`
|
||||
GroupName string `json:"GroupName"`
|
||||
Tenancy string `json:"Tenancy"`
|
||||
} `json:"Placement"`
|
||||
Platform string `json:"Platform"`
|
||||
PrivateDNS string `json:"PrivateDnsName"`
|
||||
PrivateIP string `json:"PrivateIpAddress"`
|
||||
PublicDNS string `json:"PublicDnsName"`
|
||||
PublicIP string `json:"PublicIpAddress"`
|
||||
State string `json:"State"`
|
||||
SubnetID string `json:"SubnetId"`
|
||||
VpcID string `json:"VpcId"`
|
||||
Architecture string `json:"Architecture"`
|
||||
BlockDeviceMappings []struct {
|
||||
DeviceName string `json:"DeviceName"`
|
||||
Ebs struct {
|
||||
AttachTime time.Time `json:"AttachTime"`
|
||||
DeleteOnTermination bool `json:"DeleteOnTermination"`
|
||||
Status string `json:"Status"`
|
||||
VolumeID string `json:"VolumeId"`
|
||||
} `json:"Ebs"`
|
||||
} `json:"BlockDeviceMappings"`
|
||||
ClientToken string `json:"ClientToken"`
|
||||
EbsOptimized bool `json:"EbsOptimized"`
|
||||
Hypervisor string `json:"Hypervisor"`
|
||||
IamInstanceProfile struct {
|
||||
Arn string `json:"Arn"`
|
||||
ID string `json:"Id"`
|
||||
} `json:"IamInstanceProfile"`
|
||||
NetworkInterfaces []struct {
|
||||
Attachment struct {
|
||||
AttachTime time.Time `json:"AttachTime"`
|
||||
AttachmentID string `json:"AttachmentId"`
|
||||
DeleteOnTermination bool `json:"DeleteOnTermination"`
|
||||
DeviceIndex int `json:"DeviceIndex"`
|
||||
Status string `json:"Status"`
|
||||
} `json:"Attachment"`
|
||||
Description string `json:"Description"`
|
||||
Groups []struct {
|
||||
GroupName string `json:"GroupName"`
|
||||
GroupID string `json:"GroupId"`
|
||||
} `json:"Groups"`
|
||||
MacAddress string `json:"MacAddress"`
|
||||
NetworkInterfaceID string `json:"NetworkInterfaceId"`
|
||||
OwnerID string `json:"OwnerId"`
|
||||
PrivateDNSName string `json:"PrivateDnsName"`
|
||||
PrivateIPAddress string `json:"PrivateIpAddress"`
|
||||
PrivateIPAddresses []struct {
|
||||
Association struct {
|
||||
IPOwnerID string `json:"IpOwnerId"`
|
||||
PublicDNSName string `json:"PublicDnsName"`
|
||||
PublicIP string `json:"PublicIp"`
|
||||
} `json:"Association"`
|
||||
Primary bool `json:"Primary"`
|
||||
PrivateDNSName string `json:"PrivateDnsName"`
|
||||
PrivateIPAddress string `json:"PrivateIpAddress"`
|
||||
} `json:"PrivateIpAddresses"`
|
||||
SourceDestCheck bool `json:"SourceDestCheck"`
|
||||
Status string `json:"Status"`
|
||||
SubnetID string `json:"SubnetId"`
|
||||
VpcID string `json:"VpcId"`
|
||||
} `json:"NetworkInterfaces"`
|
||||
RootDeviceName string `json:"RootDeviceName"`
|
||||
RootDeviceType string `json:"RootDeviceType"`
|
||||
SecurityGroups []struct {
|
||||
GroupName string `json:"GroupName"`
|
||||
GroupID string `json:"GroupId"`
|
||||
} `json:"SecurityGroups"`
|
||||
SourceDestCheck bool `json:"SourceDestCheck"`
|
||||
StateReason struct {
|
||||
Code string `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
} `json:"StateReason"`
|
||||
Tags []struct {
|
||||
Key string `json:"Key"`
|
||||
Value string `json:"Value"`
|
||||
} `json:"Tags"`
|
||||
VirtualizationType string `json:"VirtualizationType"`
|
||||
} `json:"Instances"`
|
||||
OwnerID string `json:"OwnerId"`
|
||||
RequesterID string `json:"RequesterId"`
|
||||
ReservationID string `json:"ReservationId"`
|
||||
} `json:"Reservations"`
|
||||
}
|
||||
|
||||
type DescribeImages struct {
|
||||
Images []struct {
|
||||
Architecture string `json:"Architecture"`
|
||||
BlockDeviceMappings []struct {
|
||||
DeviceName string `json:"DeviceName"`
|
||||
Ebs struct {
|
||||
DeleteOnTermination bool `json:"DeleteOnTermination"`
|
||||
SnapshotID string `json:"SnapshotId"`
|
||||
VolumeSize int `json:"VolumeSize"`
|
||||
VolumeType string `json:"VolumeType"`
|
||||
} `json:"Ebs"`
|
||||
} `json:"BlockDeviceMappings"`
|
||||
CreationDate string `json:"CreationDate"`
|
||||
Description string `json:"Description"`
|
||||
EnaSupport bool `json:"EnaSupport"`
|
||||
Hypervisor string `json:"Hypervisor"`
|
||||
ImageID string `json:"ImageId"`
|
||||
ImageLocation string `json:"ImageLocation"`
|
||||
ImageType string `json:"ImageType"`
|
||||
KernelID string `json:"KernelId"`
|
||||
Name string `json:"Name"`
|
||||
OwnerAlias string `json:"OwnerAlias"`
|
||||
OwnerID string `json:"OwnerId"`
|
||||
Platform string `json:"Platform"`
|
||||
ProductCodes []struct {
|
||||
ProductCodeID string `json:"ProductCodeId"`
|
||||
ProductCodeType string `json:"ProductCodeType"`
|
||||
} `json:"ProductCodes"`
|
||||
Public bool `json:"Public"`
|
||||
RamdiskID string `json:"RamdiskId"`
|
||||
RootDeviceName string `json:"RootDeviceName"`
|
||||
RootDeviceType string `json:"RootDeviceType"`
|
||||
SriovNetSupport string `json:"SriovNetSupport"`
|
||||
State string `json:"State"`
|
||||
StateReason struct {
|
||||
Code string `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
} `json:"StateReason"`
|
||||
Tags []struct {
|
||||
Key string `json:"Key"`
|
||||
Value string `json:"Value"`
|
||||
} `json:"Tags"`
|
||||
VirtualizationType string `json:"VirtualizationType"`
|
||||
} `json:"Images"`
|
||||
NextToken string `json:"NextToken"`
|
||||
}
|
||||
|
||||
type DescribeSnapshots struct {
|
||||
Snapshots []struct {
|
||||
DataEncryptionKeyID string `json:"DataEncryptionKeyId"`
|
||||
Description string `json:"Description"`
|
||||
Encrypted bool `json:"Encrypted"`
|
||||
KMSKeyID string `json:"KmsKeyId"`
|
||||
OwnerAlias string `json:"OwnerAlias"`
|
||||
OwnerID string `json:"OwnerId"`
|
||||
Progress string `json:"Progress"`
|
||||
SnapshotID string `json:"SnapshotId"`
|
||||
StartTime time.Time `json:"StartTime"`
|
||||
State string `json:"State"`
|
||||
StateMessage string `json:"StateMessage"`
|
||||
TagSet []struct {
|
||||
Key string `json:"Key"`
|
||||
Value string `json:"Value"`
|
||||
} `json:"Tags"`
|
||||
VolumeID string `json:"VolumeId"`
|
||||
VolumeSize int `json:"VolumeSize"`
|
||||
} `json:"Snapshots"`
|
||||
}
|
||||
|
||||
type DescribeVolumes struct {
|
||||
Volumes []struct {
|
||||
Attachments []struct {
|
||||
AttachTime time.Time `json:"AttachTime"`
|
||||
DeleteOnTermination bool `json:"DeleteOnTermination"`
|
||||
Device string `json:"Device"`
|
||||
InstanceID string `json:"InstanceId"`
|
||||
State string `json:"State"`
|
||||
VolumeID string `json:"VolumeId"`
|
||||
} `json:"Attachments"`
|
||||
AvailabilityZone string `json:"AvailabilityZone"`
|
||||
CreateTime string `json:"CreateTime"`
|
||||
Encrypted bool `json:"Encrypted"`
|
||||
KMSKeyID string `json:"KmsKeyId"`
|
||||
Size int `json:"Size"`
|
||||
SnapshotID string `json:"SnapshotId"`
|
||||
State string `json:"State"`
|
||||
VolumeID string `json:"VolumeId"`
|
||||
} `json:"Volumes"`
|
||||
}
|
||||
|
||||
// DescribeInstanceAttribute is a struct for the DescribeInstanceAttribute function - include userdata
|
||||
type DescribeInstanceAttribute struct {
|
||||
UserData string `json:"UserData"`
|
||||
InstanceID string `json:"InstanceId"`
|
||||
}
|
||||
|
||||
func (c *MockedEC2Client2) DescribeNetworkInterfaces(ctx context.Context, input *ec2.DescribeNetworkInterfacesInput, f ...func(o *ec2.Options)) (*ec2.DescribeNetworkInterfacesOutput, error) {
|
||||
var nics []ec2types.NetworkInterface
|
||||
err := json.Unmarshal(readTestFile(DESCRIBE_NETWORK_INTEFACES_TEST_FILE), &c.describeNetworkInterfaces)
|
||||
if err != nil {
|
||||
log.Fatalf("can't unmarshall file %s", DESCRIBE_NETWORK_INTEFACES_TEST_FILE)
|
||||
}
|
||||
for _, mockednic := range c.describeNetworkInterfaces.NetworkInterfaces {
|
||||
nics = append(nics, ec2types.NetworkInterface{
|
||||
Association: &ec2types.NetworkInterfaceAssociation{
|
||||
PublicIp: aws.String(mockednic.Association.PublicIP),
|
||||
},
|
||||
NetworkInterfaceId: aws.String(mockednic.NetworkInterfaceID),
|
||||
PrivateIpAddress: aws.String(mockednic.PrivateIPAddress),
|
||||
VpcId: aws.String(mockednic.VpcID),
|
||||
Attachment: &ec2types.NetworkInterfaceAttachment{InstanceId: aws.String(mockednic.Attachment.InstanceID)},
|
||||
Description: aws.String(mockednic.Description),
|
||||
})
|
||||
}
|
||||
return &ec2.DescribeNetworkInterfacesOutput{NetworkInterfaces: nics}, nil
|
||||
}
|
||||
|
||||
func (c *MockedEC2Client2) DescribeInstances(ctx context.Context, input *ec2.DescribeInstancesInput, f ...func(o *ec2.Options)) (*ec2.DescribeInstancesOutput, error) {
|
||||
var instances []ec2types.Instance
|
||||
err := json.Unmarshal(readTestFile(DESCRIBE_INSTANCES_TEST_FILE), &c.describeInstances)
|
||||
if err != nil {
|
||||
log.Fatalf("can't unmarshall file %s", DESCRIBE_INSTANCES_TEST_FILE)
|
||||
}
|
||||
for _, mockedReservation := range c.describeInstances.Reservations {
|
||||
for _, mockedInstance := range mockedReservation.Instances {
|
||||
for _, inputInstanceID := range input.InstanceIds {
|
||||
if mockedInstance.InstanceID == inputInstanceID {
|
||||
instances = append(instances, ec2types.Instance{
|
||||
InstanceId: aws.String(mockedInstance.InstanceID),
|
||||
InstanceType: ec2types.InstanceType(mockedInstance.InstanceType),
|
||||
ImageId: aws.String(mockedInstance.ImageID),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return &ec2.DescribeInstancesOutput{Reservations: []ec2types.Reservation{
|
||||
{Instances: instances},
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (c *MockedEC2Client2) DescribeVolumes(ctx context.Context, input *ec2.DescribeVolumesInput, f ...func(o *ec2.Options)) (*ec2.DescribeVolumesOutput, error) {
|
||||
var volumes []ec2types.Volume
|
||||
err := json.Unmarshal(readTestFile(DESCRIBE_VOLUMES_TEST_FILE), &c.describeVolumes)
|
||||
if err != nil {
|
||||
log.Fatalf("can't unmarshall file %s", DESCRIBE_VOLUMES_TEST_FILE)
|
||||
}
|
||||
for _, mockedVolume := range c.describeVolumes.Volumes {
|
||||
for _, inputVolumeID := range input.VolumeIds {
|
||||
if mockedVolume.VolumeID == inputVolumeID {
|
||||
volumes = append(volumes, ec2types.Volume{
|
||||
VolumeId: aws.String(mockedVolume.VolumeID),
|
||||
Size: aws.Int32(int32(mockedVolume.Size)),
|
||||
Attachments: []ec2types.VolumeAttachment{
|
||||
{
|
||||
InstanceId: aws.String(mockedVolume.Attachments[0].InstanceID),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return &ec2.DescribeVolumesOutput{Volumes: volumes}, nil
|
||||
}
|
||||
|
||||
func (c *MockedEC2Client2) DescribeSnapshots(ctx context.Context, input *ec2.DescribeSnapshotsInput, f ...func(o *ec2.Options)) (*ec2.DescribeSnapshotsOutput, error) {
|
||||
var snapshots []ec2types.Snapshot
|
||||
err := json.Unmarshal(readTestFile(DESCRIBE_SNAPSHOTS_TEST_FILE), &c.describeSnapshots)
|
||||
if err != nil {
|
||||
log.Fatalf("can't unmarshall file %s", DESCRIBE_SNAPSHOTS_TEST_FILE)
|
||||
}
|
||||
for _, mockedSnapshot := range c.describeSnapshots.Snapshots {
|
||||
for _, inputSnapshotID := range input.SnapshotIds {
|
||||
if mockedSnapshot.SnapshotID == inputSnapshotID {
|
||||
snapshots = append(snapshots, ec2types.Snapshot{
|
||||
SnapshotId: aws.String(mockedSnapshot.SnapshotID),
|
||||
VolumeId: aws.String(mockedSnapshot.VolumeID),
|
||||
State: ec2types.SnapshotState(mockedSnapshot.State),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return &ec2.DescribeSnapshotsOutput{Snapshots: snapshots}, nil
|
||||
}
|
||||
|
||||
func (c *MockedEC2Client2) DescribeImages(ctx context.Context, input *ec2.DescribeImagesInput, f ...func(o *ec2.Options)) (*ec2.DescribeImagesOutput, error) {
|
||||
var images []ec2types.Image
|
||||
err := json.Unmarshal(readTestFile(DESCRIBE_IMAGES_TEST_FILE), &c.describeImages)
|
||||
if err != nil {
|
||||
log.Fatalf("can't unmarshall file %s", DESCRIBE_IMAGES_TEST_FILE)
|
||||
}
|
||||
for _, mockedImage := range c.describeImages.Images {
|
||||
for _, inputImageID := range input.ImageIds {
|
||||
if mockedImage.ImageID == inputImageID {
|
||||
images = append(images, ec2types.Image{
|
||||
ImageId: aws.String(mockedImage.ImageID),
|
||||
Name: aws.String(mockedImage.Name),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return &ec2.DescribeImagesOutput{Images: images}, nil
|
||||
}
|
||||
|
||||
func (c *MockedEC2Client2) DescribeInstanceAttribute(ctx context.Context, input *ec2.DescribeInstanceAttributeInput, f ...func(o *ec2.Options)) (*ec2.DescribeInstanceAttributeOutput, error) {
|
||||
return &ec2.DescribeInstanceAttributeOutput{
|
||||
UserData: &ec2types.AttributeValue{
|
||||
Value: aws.String("userdata"),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
+3
-2
@@ -19,9 +19,10 @@ type AWSECRClientInterface interface {
|
||||
GetRepositoryPolicy(ctx context.Context, params *ecr.GetRepositoryPolicyInput, optFns ...func(*ecr.Options)) (*ecr.GetRepositoryPolicyOutput, error)
|
||||
}
|
||||
|
||||
func RegisterECRTypes() {
|
||||
func init() {
|
||||
gob.Register([]ecrTypes.Repository{})
|
||||
gob.Register([]ecrTypes.ImageDetail{})
|
||||
gob.Register(ecrTypes.Repository{})
|
||||
|
||||
}
|
||||
|
||||
@@ -69,7 +70,7 @@ func CachedECRDescribeRepositories(ECRClient AWSECRClientInterface, accountID st
|
||||
func CachedECRDescribeImages(ECRClient AWSECRClientInterface, accountID string, region string, repositoryName string) ([]ecrTypes.ImageDetail, error) {
|
||||
var PaginationControl *string
|
||||
var images []ecrTypes.ImageDetail
|
||||
cacheKey := fmt.Sprintf("%s-efs-DescribImages-%s-%s", accountID, region, strings.ReplaceAll(repositoryName, "/", "-"))
|
||||
cacheKey := fmt.Sprintf("%s-ecr-DescribeImages-%s-%s", accountID, region, strings.ReplaceAll(repositoryName, "/", "-"))
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
sharedLogger.Debug("Using cached Images data")
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ecr"
|
||||
ecrTypes "github.com/aws/aws-sdk-go-v2/service/ecr/types"
|
||||
)
|
||||
|
||||
type MockedECRClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedECRClient) DescribeRepositories(ctx context.Context, input *ecr.DescribeRepositoriesInput, options ...func(*ecr.Options)) (*ecr.DescribeRepositoriesOutput, error) {
|
||||
return &ecr.DescribeRepositoriesOutput{
|
||||
Repositories: []ecrTypes.Repository{
|
||||
{
|
||||
RepositoryName: aws.String("repo1"),
|
||||
RepositoryUri: aws.String("11111111111111.dkr.ecr.us-east-1.amazonaws.com/repo1"),
|
||||
},
|
||||
{
|
||||
RepositoryName: aws.String("repo2"),
|
||||
RepositoryUri: aws.String("11111111111111.dkr.ecr.us-east-1.amazonaws.com/repo2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedECRClient) DescribeImages(ctx context.Context, input *ecr.DescribeImagesInput, options ...func(*ecr.Options)) (*ecr.DescribeImagesOutput, error) {
|
||||
if aws.ToString(input.RepositoryName) == "repo1" {
|
||||
return &ecr.DescribeImagesOutput{
|
||||
ImageDetails: []ecrTypes.ImageDetail{
|
||||
{
|
||||
ImageTags: []string{
|
||||
"customtag",
|
||||
"tag2",
|
||||
},
|
||||
ImagePushedAt: aws.Time(time.Date(2022, 10, 25, 15, 14, 0, 0, time.UTC)),
|
||||
ImageSizeInBytes: aws.Int64(123456),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
} else if aws.ToString(input.RepositoryName) == "repo2" {
|
||||
return &ecr.DescribeImagesOutput{
|
||||
ImageDetails: []ecrTypes.ImageDetail{
|
||||
{
|
||||
ImageTags: []string{
|
||||
"latest",
|
||||
},
|
||||
ImagePushedAt: aws.Time(time.Date(2021, 10, 15, 11, 14, 0, 0, time.UTC)),
|
||||
ImageSizeInBytes: aws.Int64(2222222),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
} else {
|
||||
return &ecr.DescribeImagesOutput{
|
||||
ImageDetails: []ecrTypes.ImageDetail{
|
||||
{
|
||||
ImageTags: []string{
|
||||
"customtag",
|
||||
"tag2",
|
||||
},
|
||||
ImagePushedAt: aws.Time(time.Date(2022, 10, 25, 15, 14, 0, 0, time.UTC)),
|
||||
ImageSizeInBytes: aws.Int64(111),
|
||||
},
|
||||
{
|
||||
ImageTags: []string{
|
||||
"latest",
|
||||
},
|
||||
ImagePushedAt: aws.Time(time.Date(2021, 10, 15, 11, 14, 0, 0, time.UTC)),
|
||||
ImageSizeInBytes: aws.Int64(333),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *MockedECRClient) GetRepositoryPolicy(ctx context.Context, input *ecr.GetRepositoryPolicyInput, options ...func(*ecr.Options)) (*ecr.GetRepositoryPolicyOutput, error) {
|
||||
return &ecr.GetRepositoryPolicyOutput{
|
||||
PolicyText: aws.String(`{
|
||||
"Version": "2008-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "AllowPushPull",
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"AWS": [
|
||||
"arn:aws:iam::123456789012:root",
|
||||
"arn:aws:iam::123456789012:user/MyUser"
|
||||
]
|
||||
},
|
||||
"Action": [
|
||||
|
||||
"ecr:GetDownloadUrlForLayer",
|
||||
"ecr:BatchGetImage",
|
||||
"ecr:BatchCheckLayerAvailability",
|
||||
"ecr:PutImage",
|
||||
"ecr:InitiateLayerUpload",
|
||||
"ecr:UploadLayerPart",
|
||||
"ecr:CompleteLayerUpload"
|
||||
]
|
||||
|
||||
}
|
||||
]
|
||||
}`),
|
||||
}, nil
|
||||
}
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ecs"
|
||||
ecsTypes "github.com/aws/aws-sdk-go-v2/service/ecs/types"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
type AWSECSClientInterface interface {
|
||||
ListClusters(ctx context.Context, params *ecs.ListClustersInput, optFns ...func(*ecs.Options)) (*ecs.ListClustersOutput, error)
|
||||
ListTasks(ctx context.Context, params *ecs.ListTasksInput, optFns ...func(*ecs.Options)) (*ecs.ListTasksOutput, error)
|
||||
ListServices(ctx context.Context, params *ecs.ListServicesInput, optFns ...func(*ecs.Options)) (*ecs.ListServicesOutput, error)
|
||||
DescribeTasks(ctx context.Context, params *ecs.DescribeTasksInput, optFns ...func(*ecs.Options)) (*ecs.DescribeTasksOutput, error)
|
||||
DescribeTaskDefinition(ctx context.Context, params *ecs.DescribeTaskDefinitionInput, optFns ...func(*ecs.Options)) (*ecs.DescribeTaskDefinitionOutput, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register([]string{})
|
||||
gob.Register(ecsTypes.Task{})
|
||||
gob.Register([]ecsTypes.Task{})
|
||||
gob.Register(ecsTypes.TaskDefinition{})
|
||||
|
||||
}
|
||||
|
||||
func CachedECSListClusters(ECSClient AWSECSClientInterface, accountID string, region string) ([]string, error) {
|
||||
var PaginationControl *string
|
||||
var clusters []string
|
||||
cacheKey := fmt.Sprintf("%s-ecs-ListClusters-%s", accountID, region)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
sharedLogger.Debug("Using cached ECS clusters data")
|
||||
return cached.([]string), nil
|
||||
}
|
||||
|
||||
for {
|
||||
ListClusters, err := ECSClient.ListClusters(
|
||||
context.TODO(),
|
||||
&ecs.ListClustersInput{
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *ecs.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
clusters = append(clusters, ListClusters.ClusterArns...)
|
||||
|
||||
// Pagination control.
|
||||
if ListClusters.NextToken != nil {
|
||||
PaginationControl = ListClusters.NextToken
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, clusters, cache.DefaultExpiration)
|
||||
return clusters, nil
|
||||
}
|
||||
|
||||
func CachedECSListTasks(ECSClient AWSECSClientInterface, accountID string, region string, cluster string) ([]string, error) {
|
||||
var PaginationControl *string
|
||||
var tasks []string
|
||||
//grab cluster name from AWS ARN
|
||||
clusterName := cluster[strings.LastIndex(cluster, "/")+1:]
|
||||
|
||||
cacheKey := fmt.Sprintf("%s-ecs-ListTasks-%s-%s", accountID, region, clusterName)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
sharedLogger.Debug("Using cached ECS tasks data")
|
||||
return cached.([]string), nil
|
||||
}
|
||||
|
||||
for {
|
||||
ListTasks, err := ECSClient.ListTasks(
|
||||
context.TODO(),
|
||||
&ecs.ListTasksInput{
|
||||
Cluster: &cluster,
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *ecs.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
tasks = append(tasks, ListTasks.TaskArns...)
|
||||
|
||||
// Pagination control.
|
||||
if ListTasks.NextToken != nil {
|
||||
PaginationControl = ListTasks.NextToken
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, tasks, cache.DefaultExpiration)
|
||||
return tasks, nil
|
||||
}
|
||||
|
||||
func CachedECSDescribeTasks(ECSClient AWSECSClientInterface, accountID string, region string, cluster string, tasks []string) ([]ecsTypes.Task, error) {
|
||||
var taskDetails []ecsTypes.Task
|
||||
//replace semi-colons with underscores in task definition name
|
||||
clusterFileSystemSafe := strings.ReplaceAll(cluster, ":", "_")
|
||||
clusterFileSystemSafe = strings.ReplaceAll(clusterFileSystemSafe, "/", "_")
|
||||
cacheKey := fmt.Sprintf("%s-ecs-DescribeTasks-%s-%s", accountID, region, clusterFileSystemSafe)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
sharedLogger.Debug("Using cached ECS task details data")
|
||||
return cached.([]ecsTypes.Task), nil
|
||||
}
|
||||
|
||||
DescribeTasks, err := ECSClient.DescribeTasks(
|
||||
context.TODO(),
|
||||
&ecs.DescribeTasksInput{
|
||||
Cluster: &cluster,
|
||||
Tasks: tasks,
|
||||
},
|
||||
func(o *ecs.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
return []ecsTypes.Task{}, err
|
||||
}
|
||||
|
||||
taskDetails = append(taskDetails, DescribeTasks.Tasks...)
|
||||
|
||||
internal.Cache.Set(cacheKey, taskDetails, cache.DefaultExpiration)
|
||||
return taskDetails, nil
|
||||
}
|
||||
|
||||
func CachedECSDescribeTaskDefinition(ECSClient AWSECSClientInterface, accountID string, region string, taskDefinition string) (ecsTypes.TaskDefinition, error) {
|
||||
var taskDefinitionDetails ecsTypes.TaskDefinition
|
||||
//replace semi-colons with underscores in task definition name
|
||||
taskDefinitionFileSystemSafe := strings.ReplaceAll(taskDefinition, ":", "_")
|
||||
taskDefinitionFileSystemSafe = strings.ReplaceAll(taskDefinitionFileSystemSafe, "/", "_")
|
||||
cacheKey := fmt.Sprintf("%s-ecs-DescribeTaskDefinition-%s-%s", accountID, region, taskDefinitionFileSystemSafe)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
sharedLogger.Debug("Using cached ECS task definition details data")
|
||||
return cached.(ecsTypes.TaskDefinition), nil
|
||||
}
|
||||
|
||||
DescribeTaskDefinition, err := ECSClient.DescribeTaskDefinition(
|
||||
context.TODO(),
|
||||
&ecs.DescribeTaskDefinitionInput{
|
||||
TaskDefinition: &taskDefinition,
|
||||
},
|
||||
func(o *ecs.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
return ecsTypes.TaskDefinition{}, err
|
||||
}
|
||||
|
||||
taskDefinitionDetails = *DescribeTaskDefinition.TaskDefinition
|
||||
|
||||
internal.Cache.Set(cacheKey, taskDefinitionDetails, cache.DefaultExpiration)
|
||||
return taskDefinitionDetails, nil
|
||||
}
|
||||
|
||||
func CachedECSListServices(ECSClient AWSECSClientInterface, accountID string, region string, cluster string) ([]string, error) {
|
||||
var PaginationControl *string
|
||||
var services []string
|
||||
//grab cluster name from AWS ARN
|
||||
clusterName := cluster[strings.LastIndex(cluster, "/")+1:]
|
||||
|
||||
cacheKey := fmt.Sprintf("%s-ecs-ListServices-%s-%s", accountID, region, clusterName)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
sharedLogger.Debug("Using cached ECS services data")
|
||||
return cached.([]string), nil
|
||||
}
|
||||
|
||||
for {
|
||||
ListServices, err := ECSClient.ListServices(
|
||||
context.TODO(),
|
||||
&ecs.ListServicesInput{
|
||||
Cluster: &cluster,
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *ecs.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
services = append(services, ListServices.ServiceArns...)
|
||||
|
||||
// Pagination control.
|
||||
if ListServices.NextToken != nil {
|
||||
PaginationControl = ListServices.NextToken
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, services, cache.DefaultExpiration)
|
||||
return services, nil
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ecs"
|
||||
ecsTypes "github.com/aws/aws-sdk-go-v2/service/ecs/types"
|
||||
)
|
||||
|
||||
const DESCRIBE_TASKS_TEST_FILE = "./test-data/describe-tasks.json"
|
||||
|
||||
type MockedECSClient struct {
|
||||
describeTasks DescribeTasks
|
||||
}
|
||||
|
||||
type ListTasks struct {
|
||||
TaskArns []string `json:"taskArns"`
|
||||
}
|
||||
|
||||
type DescribeTasks struct {
|
||||
Tasks []struct {
|
||||
Attachments []struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Details []struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
} `json:"details"`
|
||||
} `json:"attachments"`
|
||||
Attributes []struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
} `json:"attributes"`
|
||||
AvailabilityZone string `json:"availabilityZone"`
|
||||
ClusterArn string `json:"clusterArn"`
|
||||
Connectivity string `json:"connectivity"`
|
||||
ConnectivityAt string `json:"connectivityAt"`
|
||||
Containers []struct {
|
||||
ContainerArn string `json:"containerArn"`
|
||||
TaskArn string `json:"taskArn"`
|
||||
Name string `json:"name"`
|
||||
Image string `json:"image"`
|
||||
RuntimeID string `json:"runtimeId"`
|
||||
LastStatus string `json:"lastStatus"`
|
||||
NetworkBindings []interface{} `json:"networkBindings"`
|
||||
NetworkInterfaces []struct {
|
||||
AttachmentID string `json:"attachmentId"`
|
||||
PrivateIpv4Address string `json:"privateIpv4Address"`
|
||||
} `json:"networkInterfaces"`
|
||||
HealthStatus string `json:"healthStatus"`
|
||||
CPU string `json:"cpu"`
|
||||
Memory string `json:"memory"`
|
||||
} `json:"containers"`
|
||||
CPU string `json:"cpu"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
DesiredStatus string `json:"desiredStatus"`
|
||||
EnableExecuteCommand bool `json:"enableExecuteCommand"`
|
||||
Group string `json:"group"`
|
||||
HealthStatus string `json:"healthStatus"`
|
||||
LastStatus string `json:"lastStatus"`
|
||||
LaunchType string `json:"launchType"`
|
||||
Memory string `json:"memory"`
|
||||
Overrides struct {
|
||||
ContainerOverrides []struct {
|
||||
Name string `json:"name"`
|
||||
} `json:"containerOverrides"`
|
||||
InferenceAcceleratorOverrides []interface{} `json:"inferenceAcceleratorOverrides"`
|
||||
} `json:"overrides"`
|
||||
PlatformVersion string `json:"platformVersion"`
|
||||
PlatformFamily string `json:"platformFamily"`
|
||||
PullStartedAt string `json:"pullStartedAt"`
|
||||
PullStoppedAt string `json:"pullStoppedAt"`
|
||||
StartedAt string `json:"startedAt"`
|
||||
StartedBy string `json:"startedBy"`
|
||||
Tags []interface{} `json:"tags"`
|
||||
TaskArn string `json:"taskArn"`
|
||||
TaskDefinitionArn string `json:"taskDefinitionArn"`
|
||||
Version int `json:"version"`
|
||||
EphemeralStorage struct {
|
||||
SizeInGiB int `json:"sizeInGiB"`
|
||||
} `json:"ephemeralStorage"`
|
||||
} `json:"tasks"`
|
||||
Failures []interface{} `json:"failures"`
|
||||
}
|
||||
|
||||
func (c *MockedECSClient) ListClusters(context.Context, *ecs.ListClustersInput, ...func(*ecs.Options)) (*ecs.ListClustersOutput, error) {
|
||||
return &ecs.ListClustersOutput{ClusterArns: []string{
|
||||
"arn:aws:ecs:us-east-1:123456789012:cluster/MyCluster",
|
||||
"arn:aws:ecs:us-east-1:123456789012:cluster/MyCluster2",
|
||||
"arn:aws:ecs:us-east-1:123456789012:cluster/MyCluster3",
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (c *MockedECSClient) ListTasks(ctx context.Context, input *ecs.ListTasksInput, f ...func(*ecs.Options)) (*ecs.ListTasksOutput, error) {
|
||||
return &ecs.ListTasksOutput{TaskArns: []string{
|
||||
"arn:aws:ecs:us-east-1:123456789012:task/MyCluster/74de0355a10a4f979ac495c14EXAMPLE",
|
||||
"arn:aws:ecs:us-east-1:123456789012:task/MyCluster/d789e94343414c25b9f6bd59eEXAMPLE",
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (c *MockedECSClient) ListServices(ctx context.Context, input *ecs.ListServicesInput, f ...func(*ecs.Options)) (*ecs.ListServicesOutput, error) {
|
||||
return &ecs.ListServicesOutput{ServiceArns: []string{
|
||||
"arn:aws:ecs:us-east-1:123456789012:service/MyService",
|
||||
"arn:aws:ecs:us-east-1:123456789012:service/MyService2",
|
||||
"arn:aws:ecs:us-east-1:123456789012:service/MyService3",
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (c *MockedECSClient) DescribeTasks(ctx context.Context, input *ecs.DescribeTasksInput, f ...func(*ecs.Options)) (*ecs.DescribeTasksOutput, error) {
|
||||
err := json.Unmarshal(readTestFile(DESCRIBE_TASKS_TEST_FILE), &c.describeTasks)
|
||||
if err != nil {
|
||||
log.Fatalf("can't unmarshall file %s", DESCRIBE_TASKS_TEST_FILE)
|
||||
}
|
||||
var tasks []ecsTypes.Task
|
||||
for _, mockedTask := range c.describeTasks.Tasks {
|
||||
if mockedTask.ClusterArn == aws.ToString(input.Cluster) {
|
||||
for _, inputTask := range input.Tasks {
|
||||
if mockedTask.TaskArn == inputTask {
|
||||
var attachments []ecsTypes.Attachment
|
||||
for _, a := range mockedTask.Attachments {
|
||||
var deets []ecsTypes.KeyValuePair
|
||||
for _, detail := range a.Details {
|
||||
deets = append(deets, ecsTypes.KeyValuePair{
|
||||
Name: aws.String(detail.Name),
|
||||
Value: aws.String(detail.Value)})
|
||||
}
|
||||
attachments = append(attachments, ecsTypes.Attachment{
|
||||
Type: aws.String(a.Type),
|
||||
Details: deets,
|
||||
Id: aws.String(a.ID),
|
||||
Status: aws.String(a.Status),
|
||||
})
|
||||
}
|
||||
tasks = append(tasks, ecsTypes.Task{
|
||||
ClusterArn: aws.String(mockedTask.ClusterArn),
|
||||
TaskDefinitionArn: aws.String(mockedTask.TaskDefinitionArn),
|
||||
LaunchType: ecsTypes.LaunchType(*aws.String(mockedTask.LaunchType)),
|
||||
TaskArn: aws.String(mockedTask.TaskArn),
|
||||
Attachments: attachments,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return &ecs.DescribeTasksOutput{Tasks: tasks}, nil
|
||||
}
|
||||
|
||||
func (c *MockedECSClient) DescribeTaskDefinition(ctx context.Context, input *ecs.DescribeTaskDefinitionInput, f ...func(o *ecs.Options)) (*ecs.DescribeTaskDefinitionOutput, error) {
|
||||
testTaskDefinition := ecsTypes.TaskDefinition{}
|
||||
testTaskDefinition.TaskRoleArn = aws.String("test123")
|
||||
return &ecs.DescribeTaskDefinitionOutput{TaskDefinition: &testTaskDefinition}, nil
|
||||
}
|
||||
+1
-1
@@ -20,7 +20,7 @@ type AWSEFSClientInterface interface {
|
||||
DescribeFileSystemPolicy(ctx context.Context, params *efs.DescribeFileSystemPolicyInput, optFns ...func(*efs.Options)) (*efs.DescribeFileSystemPolicyOutput, error)
|
||||
}
|
||||
|
||||
func RegisterEFSTypes() {
|
||||
func init() {
|
||||
gob.Register([]efsTypes.FileSystemDescription{})
|
||||
gob.Register([]efsTypes.MountTargetDescription{})
|
||||
gob.Register([]efsTypes.AccessPointDescription{})
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/efs"
|
||||
efsTypes "github.com/aws/aws-sdk-go-v2/service/efs/types"
|
||||
)
|
||||
|
||||
type MockedEfsClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedEfsClient) DescribeFileSystems(ctx context.Context, input *efs.DescribeFileSystemsInput, options ...func(*efs.Options)) (*efs.DescribeFileSystemsOutput, error) {
|
||||
return &efs.DescribeFileSystemsOutput{
|
||||
FileSystems: []efsTypes.FileSystemDescription{
|
||||
{
|
||||
FileSystemId: aws.String("fs-12345678"),
|
||||
},
|
||||
{
|
||||
FileSystemId: aws.String("fs-87654321"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedEfsClient) DescribeMountTargets(ctx context.Context, input *efs.DescribeMountTargetsInput, options ...func(*efs.Options)) (*efs.DescribeMountTargetsOutput, error) {
|
||||
return &efs.DescribeMountTargetsOutput{
|
||||
MountTargets: []efsTypes.MountTargetDescription{
|
||||
{
|
||||
MountTargetId: aws.String("fsmt-12345678"),
|
||||
FileSystemId: aws.String("fs-12345678"),
|
||||
IpAddress: aws.String("10.1.1.1"),
|
||||
},
|
||||
{
|
||||
MountTargetId: aws.String("fsmt-87654321"),
|
||||
FileSystemId: aws.String("fs-87654321"),
|
||||
IpAddress: aws.String("10.2.2.2.2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedEfsClient) DescribeAccessPoints(ctx context.Context, input *efs.DescribeAccessPointsInput, options ...func(*efs.Options)) (*efs.DescribeAccessPointsOutput, error) {
|
||||
return &efs.DescribeAccessPointsOutput{
|
||||
AccessPoints: []efsTypes.AccessPointDescription{
|
||||
{
|
||||
AccessPointId: aws.String("fsap-12345678"),
|
||||
FileSystemId: aws.String("fs-12345678"),
|
||||
Name: aws.String("fsap-12345678"),
|
||||
PosixUser: &efsTypes.PosixUser{
|
||||
Gid: aws.Int64(1000),
|
||||
Uid: aws.Int64(1000),
|
||||
},
|
||||
},
|
||||
{
|
||||
AccessPointId: aws.String("fsap-87654321"),
|
||||
FileSystemId: aws.String("fs-87654321"),
|
||||
Name: aws.String("fsap-12345679"),
|
||||
PosixUser: &efsTypes.PosixUser{
|
||||
Gid: aws.Int64(1000),
|
||||
Uid: aws.Int64(1000),
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
+2
-2
@@ -18,7 +18,7 @@ type EKSClientInterface interface {
|
||||
ListNodegroups(context.Context, *eks.ListNodegroupsInput, ...func(*eks.Options)) (*eks.ListNodegroupsOutput, error)
|
||||
}
|
||||
|
||||
func RegisterEKSTypes() {
|
||||
func init() {
|
||||
gob.Register([]string{})
|
||||
gob.Register(eksTypes.Cluster{})
|
||||
gob.Register(eksTypes.Nodegroup{})
|
||||
@@ -126,7 +126,7 @@ func CachedEKSListNodeGroups(client EKSClientInterface, accountID string, region
|
||||
|
||||
func CachedEKSDescribeNodeGroup(client EKSClientInterface, accountID string, region string, clusterName string, nodeGroupName string) (eksTypes.Nodegroup, error) {
|
||||
var nodeGroup eksTypes.Nodegroup
|
||||
cacheKey := fmt.Sprintf("%s-eks-DescribeNodeGroup-%s-%s-%s", accountID, region, nodeGroupName)
|
||||
cacheKey := fmt.Sprintf("%s-eks-DescribeNodeGroup-%s-%s-%s", accountID, region, clusterName, nodeGroupName)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.(eksTypes.Nodegroup), nil
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/eks"
|
||||
eksTypes "github.com/aws/aws-sdk-go-v2/service/eks/types"
|
||||
)
|
||||
|
||||
type MockedAWSEksClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedAWSEksClient) ListClusters(ctx context.Context, input *eks.ListClustersInput, options ...func(*eks.Options)) (*eks.ListClustersOutput, error) {
|
||||
return &eks.ListClustersOutput{
|
||||
Clusters: []string{
|
||||
"cluster1",
|
||||
"cluster2",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedAWSEksClient) DescribeCluster(ctx context.Context, input *eks.DescribeClusterInput, options ...func(*eks.Options)) (*eks.DescribeClusterOutput, error) {
|
||||
return &eks.DescribeClusterOutput{
|
||||
Cluster: &eksTypes.Cluster{
|
||||
Arn: aws.String("arn:aws:eks:us-east-1:123456789012:cluster/cluster1"),
|
||||
Endpoint: aws.String("https://cluster1.us-east-1.eks.amazonaws.com"),
|
||||
Name: aws.String("cluster1"),
|
||||
PlatformVersion: aws.String("eks.1"),
|
||||
Version: aws.String("1.18"),
|
||||
RoleArn: aws.String("arn:aws:iam::123456789012:role/eks-role"),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedAWSEksClient) ListNodegroups(ctx context.Context, input *eks.ListNodegroupsInput, options ...func(*eks.Options)) (*eks.ListNodegroupsOutput, error) {
|
||||
return &eks.ListNodegroupsOutput{
|
||||
Nodegroups: []string{
|
||||
"nodegroup1",
|
||||
"nodegroup2",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedAWSEksClient) DescribeNodegroup(ctx context.Context, input *eks.DescribeNodegroupInput, options ...func(*eks.Options)) (*eks.DescribeNodegroupOutput, error) {
|
||||
return &eks.DescribeNodegroupOutput{
|
||||
Nodegroup: &eksTypes.Nodegroup{
|
||||
ClusterName: aws.String("cluster1"),
|
||||
NodeRole: aws.String("arn:aws:iam::123456789012:role/eks-role"),
|
||||
NodegroupName: aws.String("nodegroup1"),
|
||||
NodegroupArn: aws.String("arn:aws:eks:us-east-1:123456789012:nodegroup/cluster1/nodegroup1"),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/service/elasticache"
|
||||
elasticacheTypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
type AWSElastiCacheClientInterface interface {
|
||||
DescribeCacheClusters(context.Context, *elasticache.DescribeCacheClustersInput, ...func(*elasticache.Options)) (*elasticache.DescribeCacheClustersOutput, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register([]elasticacheTypes.CacheCluster{})
|
||||
|
||||
}
|
||||
|
||||
func CachedElastiCacheDescribeCacheClusters(client AWSElastiCacheClientInterface, accountID string, region string) ([]elasticacheTypes.CacheCluster, error) {
|
||||
var PaginationControl *string
|
||||
var clusters []elasticacheTypes.CacheCluster
|
||||
cacheKey := fmt.Sprintf("%s-elasticache-DescribeCacheClusters-%s", accountID, region)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]elasticacheTypes.CacheCluster), nil
|
||||
}
|
||||
for {
|
||||
DescribeCacheClusters, err := client.DescribeCacheClusters(
|
||||
context.TODO(),
|
||||
&elasticache.DescribeCacheClustersInput{
|
||||
Marker: PaginationControl,
|
||||
},
|
||||
func(o *elasticache.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return clusters, err
|
||||
}
|
||||
|
||||
clusters = append(clusters, DescribeCacheClusters.CacheClusters...)
|
||||
|
||||
//pagination
|
||||
if DescribeCacheClusters.Marker == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = DescribeCacheClusters.Marker
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, clusters, cache.DefaultExpiration)
|
||||
return clusters, nil
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/elasticache"
|
||||
elasticacheTypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types"
|
||||
)
|
||||
|
||||
type MockedElasticacheClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedElasticacheClient) DescribeCacheClusters(ctx context.Context, input *elasticache.DescribeCacheClustersInput, options ...func(*elasticache.Options)) (*elasticache.DescribeCacheClustersOutput, error) {
|
||||
return &elasticache.DescribeCacheClustersOutput{
|
||||
CacheClusters: []elasticacheTypes.CacheCluster{
|
||||
{
|
||||
CacheClusterId: aws.String("test"),
|
||||
ARN: aws.String("arn:aws:elasticache:us-east-1:123456789012:cluster:myCluster"),
|
||||
Engine: aws.String("redis"),
|
||||
EngineVersion: aws.String("6.x"),
|
||||
CacheNodeType: aws.String("cache.t3.micro"),
|
||||
NumCacheNodes: aws.Int32(1),
|
||||
CacheClusterStatus: aws.String("available"),
|
||||
PreferredAvailabilityZone: aws.String("us-east-1a"),
|
||||
CacheSubnetGroupName: aws.String("default"),
|
||||
ReplicationGroupId: aws.String("test"),
|
||||
SecurityGroups: []elasticacheTypes.SecurityGroupMembership{
|
||||
{
|
||||
SecurityGroupId: aws.String("test"),
|
||||
Status: aws.String("active"),
|
||||
},
|
||||
},
|
||||
|
||||
AutoMinorVersionUpgrade: aws.Bool(true),
|
||||
PreferredMaintenanceWindow: aws.String("sun:05:00-sun:06:00"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk"
|
||||
elasticbeanstalkTypes "github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk/types"
|
||||
)
|
||||
|
||||
type AWSElasticBeanstalkClientInterface interface {
|
||||
DescribeApplications(context.Context, *elasticbeanstalk.DescribeApplicationsInput, ...func(*elasticbeanstalk.Options)) (*elasticbeanstalk.DescribeApplicationsOutput, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register([]elasticbeanstalkTypes.ApplicationDescription{})
|
||||
}
|
||||
|
||||
func CachedElasticBeanstalkDescribeApplications(client AWSElasticBeanstalkClientInterface, accountID string, region string) ([]elasticbeanstalkTypes.ApplicationDescription, error) {
|
||||
var applications []elasticbeanstalkTypes.ApplicationDescription
|
||||
DescribeApplications, err := client.DescribeApplications(
|
||||
context.TODO(),
|
||||
&elasticbeanstalk.DescribeApplicationsInput{},
|
||||
func(o *elasticbeanstalk.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return applications, err
|
||||
}
|
||||
|
||||
applications = append(applications, DescribeApplications.Applications...)
|
||||
return applications, nil
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk"
|
||||
elasticbeanstalkTypes "github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk/types"
|
||||
)
|
||||
|
||||
type MockedElasticBeanstalkClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedElasticBeanstalkClient) DescribeApplications(ctx context.Context, input *elasticbeanstalk.DescribeApplicationsInput, options ...func(*elasticbeanstalk.Options)) (*elasticbeanstalk.DescribeApplicationsOutput, error) {
|
||||
return &elasticbeanstalk.DescribeApplicationsOutput{
|
||||
Applications: []elasticbeanstalkTypes.ApplicationDescription{
|
||||
{
|
||||
ApplicationName: aws.String("app1"),
|
||||
ApplicationArn: aws.String("arn:aws:elasticbeanstalk:us-east-1:123456789012:application/app1"),
|
||||
ConfigurationTemplates: []string{
|
||||
"template1",
|
||||
},
|
||||
},
|
||||
{
|
||||
ApplicationName: aws.String("app2"),
|
||||
ApplicationArn: aws.String("arn:aws:elasticbeanstalk:us-east-1:123456789012:application/app2"),
|
||||
ConfigurationTemplates: []string{
|
||||
"template2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@ type ELBClientInterface interface {
|
||||
DescribeLoadBalancers(context.Context, *elasticloadbalancing.DescribeLoadBalancersInput, ...func(*elasticloadbalancing.Options)) (*elasticloadbalancing.DescribeLoadBalancersOutput, error)
|
||||
}
|
||||
|
||||
func RegisterELBTypes() {
|
||||
func init() {
|
||||
gob.Register([]elbTypes.LoadBalancerDescription{})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing"
|
||||
elbTypes "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing/types"
|
||||
)
|
||||
|
||||
type MockedElbClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedElbClient) DescribeLoadBalancers(ctx context.Context, input *elasticloadbalancing.DescribeLoadBalancersInput, options ...func(*elasticloadbalancing.Options)) (*elasticloadbalancing.DescribeLoadBalancersOutput, error) {
|
||||
return &elasticloadbalancing.DescribeLoadBalancersOutput{
|
||||
LoadBalancerDescriptions: []elbTypes.LoadBalancerDescription{
|
||||
{
|
||||
LoadBalancerName: aws.String("elb1"),
|
||||
DNSName: aws.String("elb1"),
|
||||
Instances: []elbTypes.Instance{
|
||||
{
|
||||
InstanceId: aws.String("i-1234567890abcdef0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
LoadBalancerName: aws.String("elb2"),
|
||||
DNSName: aws.String("elb2"),
|
||||
Instances: []elbTypes.Instance{
|
||||
{
|
||||
InstanceId: aws.String("i-1234567890abcdef1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@ type ELBv2ClientInterface interface {
|
||||
DescribeLoadBalancers(context.Context, *elasticloadbalancingv2.DescribeLoadBalancersInput, ...func(*elasticloadbalancingv2.Options)) (*elasticloadbalancingv2.DescribeLoadBalancersOutput, error)
|
||||
}
|
||||
|
||||
func RegisterELBv2Types() {
|
||||
func init() {
|
||||
gob.Register([]elbV2Types.LoadBalancer{})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2"
|
||||
elbv2Types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types"
|
||||
)
|
||||
|
||||
type MockedElbv2Client struct {
|
||||
}
|
||||
|
||||
func (m *MockedElbv2Client) DescribeLoadBalancers(ctx context.Context, input *elasticloadbalancingv2.DescribeLoadBalancersInput, options ...func(*elasticloadbalancingv2.Options)) (*elasticloadbalancingv2.DescribeLoadBalancersOutput, error) {
|
||||
return &elasticloadbalancingv2.DescribeLoadBalancersOutput{
|
||||
LoadBalancers: []elbv2Types.LoadBalancer{
|
||||
{
|
||||
LoadBalancerArn: aws.String("arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188"),
|
||||
DNSName: aws.String("my-load-balancer-424835706.us-east-1.elb.amazonaws.com"),
|
||||
CanonicalHostedZoneId: aws.String("Z2P70J7EXAMPLE"),
|
||||
CreatedTime: aws.Time(time.Now()),
|
||||
LoadBalancerName: aws.String("my-load-balancer"),
|
||||
Scheme: elbv2Types.LoadBalancerSchemeEnumInternetFacing,
|
||||
VpcId: aws.String("vpc-3ac0fb5f"),
|
||||
State: &elbv2Types.LoadBalancerState{
|
||||
Code: elbv2Types.LoadBalancerStateEnumActive,
|
||||
Reason: aws.String(""),
|
||||
},
|
||||
Type: elbv2Types.LoadBalancerTypeEnumApplication,
|
||||
AvailabilityZones: []elbv2Types.AvailabilityZone{
|
||||
{
|
||||
ZoneName: aws.String("us-east-1a"),
|
||||
SubnetId: aws.String("subnet-8360a9e7"),
|
||||
LoadBalancerAddresses: []elbv2Types.LoadBalancerAddress{
|
||||
{
|
||||
IpAddress: aws.String("1.2.3.4"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ZoneName: aws.String("us-east-1b"),
|
||||
SubnetId: aws.String("subnet-b7d581c0"),
|
||||
LoadBalancerAddresses: []elbv2Types.LoadBalancerAddress{
|
||||
{
|
||||
IpAddress: aws.String("2.3.4.5"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
SecurityGroups: []string{
|
||||
"sg-5943793c",
|
||||
},
|
||||
IpAddressType: elbv2Types.IpAddressTypeDualstack,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/service/emr"
|
||||
emrTypes "github.com/aws/aws-sdk-go-v2/service/emr/types"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
type AWSEMRClientInterface interface {
|
||||
ListClusters(context.Context, *emr.ListClustersInput, ...func(*emr.Options)) (*emr.ListClustersOutput, error)
|
||||
ListInstances(context.Context, *emr.ListInstancesInput, ...func(*emr.Options)) (*emr.ListInstancesOutput, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register([]emrTypes.ClusterSummary{})
|
||||
|
||||
//need to do this to avoid conflicts with the Instance type in the ec2 package
|
||||
type EMRInstance emrTypes.Instance
|
||||
gob.Register([]EMRInstance{})
|
||||
}
|
||||
|
||||
func CachedEMRListClusters(client AWSEMRClientInterface, accountID string, region string) ([]emrTypes.ClusterSummary, error) {
|
||||
var PaginationControl *string
|
||||
var clusters []emrTypes.ClusterSummary
|
||||
cacheKey := fmt.Sprintf("%s-emr-ListClusters-%s", accountID, region)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]emrTypes.ClusterSummary), nil
|
||||
}
|
||||
for {
|
||||
ListClusters, err := client.ListClusters(
|
||||
context.TODO(),
|
||||
&emr.ListClustersInput{
|
||||
Marker: PaginationControl,
|
||||
},
|
||||
func(o *emr.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return clusters, err
|
||||
}
|
||||
|
||||
clusters = append(clusters, ListClusters.Clusters...)
|
||||
|
||||
//pagination
|
||||
if ListClusters.Marker == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = ListClusters.Marker
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, clusters, cache.DefaultExpiration)
|
||||
return clusters, nil
|
||||
}
|
||||
|
||||
func CachedEMRListInstances(client AWSEMRClientInterface, accountID string, region string, clusterID string) ([]emrTypes.Instance, error) {
|
||||
var PaginationControl *string
|
||||
var instances []emrTypes.Instance
|
||||
cacheKey := fmt.Sprintf("%s-emr-ListInstances-%s-%s", accountID, region, clusterID)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]emrTypes.Instance), nil
|
||||
}
|
||||
for {
|
||||
ListInstances, err := client.ListInstances(
|
||||
context.TODO(),
|
||||
&emr.ListInstancesInput{
|
||||
ClusterId: &clusterID,
|
||||
Marker: PaginationControl,
|
||||
},
|
||||
func(o *emr.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return instances, err
|
||||
}
|
||||
|
||||
instances = append(instances, ListInstances.Instances...)
|
||||
|
||||
//pagination
|
||||
if ListInstances.Marker == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = ListInstances.Marker
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, instances, cache.DefaultExpiration)
|
||||
return instances, nil
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/emr"
|
||||
emrTypes "github.com/aws/aws-sdk-go-v2/service/emr/types"
|
||||
)
|
||||
|
||||
type MockedEMRClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedEMRClient) ListClusters(ctx context.Context, input *emr.ListClustersInput, options ...func(*emr.Options)) (*emr.ListClustersOutput, error) {
|
||||
return &emr.ListClustersOutput{
|
||||
Clusters: []emrTypes.ClusterSummary{
|
||||
{
|
||||
Id: aws.String("cluster1"),
|
||||
},
|
||||
{
|
||||
Id: aws.String("cluster2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedEMRClient) ListInstances(ctx context.Context, input *emr.ListInstancesInput, options ...func(*emr.Options)) (*emr.ListInstancesOutput, error) {
|
||||
return &emr.ListInstancesOutput{
|
||||
Instances: []emrTypes.Instance{
|
||||
{
|
||||
Id: aws.String("instance1"),
|
||||
InstanceType: aws.String("m5.xlarge"),
|
||||
Ec2InstanceId: aws.String("i-1234567890"),
|
||||
PrivateDnsName: aws.String("ip-10-0-0-1.ec2.internal"),
|
||||
PublicDnsName: aws.String("ec2-1-2-3-4.compute-1.amazonaws.com"),
|
||||
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||
PublicIpAddress: aws.String("1.2.3.4"),
|
||||
},
|
||||
{
|
||||
Id: aws.String("instance2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/BishopFox/cloudfox/internal/aws/policy"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/glue"
|
||||
glueTypes "github.com/aws/aws-sdk-go-v2/service/glue/types"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
type AWSGlueClientInterface interface {
|
||||
ListDevEndpoints(ctx context.Context, params *glue.ListDevEndpointsInput, optFns ...func(*glue.Options)) (*glue.ListDevEndpointsOutput, error)
|
||||
ListJobs(ctx context.Context, params *glue.ListJobsInput, optFns ...func(*glue.Options)) (*glue.ListJobsOutput, error)
|
||||
GetTables(ctx context.Context, params *glue.GetTablesInput, optFns ...func(*glue.Options)) (*glue.GetTablesOutput, error)
|
||||
GetDatabases(ctx context.Context, params *glue.GetDatabasesInput, optFns ...func(*glue.Options)) (*glue.GetDatabasesOutput, error)
|
||||
GetResourcePolicies(ctx context.Context, params *glue.GetResourcePoliciesInput, optFns ...func(*glue.Options)) (*glue.GetResourcePoliciesOutput, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register([]string{})
|
||||
gob.Register(glueTypes.DevEndpoint{})
|
||||
gob.Register(glueTypes.Job{})
|
||||
gob.Register([]glueTypes.Table{})
|
||||
gob.Register([]glueTypes.Database{})
|
||||
gob.Register([]policy.Policy{})
|
||||
}
|
||||
|
||||
func CachedGlueListDevEndpoints(GlueClient AWSGlueClientInterface, accountID string, region string) ([]string, error) {
|
||||
var PaginationControl *string
|
||||
var devEndpoints []string
|
||||
cacheKey := "glue-ListDevEndpoints-" + accountID + "-" + region
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
sharedLogger.Debug("Using cached Glue dev endpoints data")
|
||||
return cached.([]string), nil
|
||||
}
|
||||
|
||||
for {
|
||||
ListDevEndpoints, err := GlueClient.ListDevEndpoints(
|
||||
context.TODO(),
|
||||
&glue.ListDevEndpointsInput{
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *glue.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
devEndpoints = append(devEndpoints, ListDevEndpoints.DevEndpointNames...)
|
||||
|
||||
// Pagination control.
|
||||
if aws.ToString(ListDevEndpoints.NextToken) != "" {
|
||||
PaginationControl = ListDevEndpoints.NextToken
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, devEndpoints, cache.DefaultExpiration)
|
||||
|
||||
return devEndpoints, nil
|
||||
}
|
||||
|
||||
func CachedGlueListJobs(GlueClient AWSGlueClientInterface, accountID string, region string) ([]string, error) {
|
||||
var PaginationControl *string
|
||||
var jobs []string
|
||||
cacheKey := "glue-ListJobs-" + accountID + "-" + region
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
sharedLogger.Debug("Using cached Glue jobs data")
|
||||
return cached.([]string), nil
|
||||
}
|
||||
|
||||
for {
|
||||
ListJobs, err := GlueClient.ListJobs(
|
||||
context.TODO(),
|
||||
&glue.ListJobsInput{
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *glue.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
jobs = append(jobs, ListJobs.JobNames...)
|
||||
|
||||
// Pagination control.
|
||||
if ListJobs.NextToken != nil {
|
||||
PaginationControl = ListJobs.NextToken
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, jobs, cache.DefaultExpiration)
|
||||
|
||||
return jobs, nil
|
||||
}
|
||||
|
||||
func CachedGlueGetTables(GlueClient AWSGlueClientInterface, accountID string, region string, dbName string) ([]glueTypes.Table, error) {
|
||||
var PaginationControl *string
|
||||
var tables []glueTypes.Table
|
||||
cacheKey := "glue-GetTables-" + accountID + "-" + region
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
sharedLogger.Debug("Using cached Glue tables data")
|
||||
return cached.([]glueTypes.Table), nil
|
||||
}
|
||||
|
||||
for {
|
||||
GetTables, err := GlueClient.GetTables(
|
||||
context.TODO(),
|
||||
&glue.GetTablesInput{
|
||||
DatabaseName: &dbName,
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *glue.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
tables = append(tables, GetTables.TableList...)
|
||||
|
||||
// Pagination control.
|
||||
if GetTables.NextToken != nil {
|
||||
PaginationControl = GetTables.NextToken
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, tables, cache.DefaultExpiration)
|
||||
|
||||
return tables, nil
|
||||
}
|
||||
|
||||
func CachedGlueGetDatabases(GlueClient AWSGlueClientInterface, accountID string, region string) ([]glueTypes.Database, error) {
|
||||
var PaginationControl *string
|
||||
var databases []glueTypes.Database
|
||||
cacheKey := "glue-GetDatabases-" + accountID + "-" + region
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
sharedLogger.Debug("Using cached Glue databases data")
|
||||
return cached.([]glueTypes.Database), nil
|
||||
}
|
||||
|
||||
for {
|
||||
GetDatabases, err := GlueClient.GetDatabases(
|
||||
context.TODO(),
|
||||
&glue.GetDatabasesInput{
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *glue.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
databases = append(databases, GetDatabases.DatabaseList...)
|
||||
|
||||
// Pagination control.
|
||||
if GetDatabases.NextToken != nil {
|
||||
PaginationControl = GetDatabases.NextToken
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, databases, cache.DefaultExpiration)
|
||||
|
||||
return databases, nil
|
||||
}
|
||||
|
||||
func CachedGlueGetResourcePolicies(GlueClient AWSGlueClientInterface, accountID string, region string) ([]policy.Policy, error) {
|
||||
var PaginationControl *string
|
||||
var GluePolicy policy.Policy
|
||||
var policies []policy.Policy
|
||||
var policyJSON string
|
||||
cacheKey := "glue-GetResourcePolicies-" + accountID + "-" + region
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
sharedLogger.Debug("Using cached Glue resource policies data")
|
||||
return cached.([]policy.Policy), nil
|
||||
}
|
||||
|
||||
for {
|
||||
GetResourcePolicies, err := GlueClient.GetResourcePolicies(
|
||||
context.TODO(),
|
||||
&glue.GetResourcePoliciesInput{
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *glue.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
for _, policyPointer := range GetResourcePolicies.GetResourcePoliciesResponseList {
|
||||
policyJSON = aws.ToString(policyPointer.PolicyInJson)
|
||||
GluePolicy, err = policy.ParseJSONPolicy([]byte(policyJSON))
|
||||
if err != nil {
|
||||
return policies, errors.New("error parsing Glue policy")
|
||||
}
|
||||
policies = append(policies, GluePolicy)
|
||||
}
|
||||
|
||||
// Pagination control.
|
||||
if GetResourcePolicies.NextToken != nil {
|
||||
PaginationControl = GetResourcePolicies.NextToken
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, policies, cache.DefaultExpiration)
|
||||
|
||||
return policies, nil
|
||||
}
|
||||
|
||||
// in the resource trust command, need to parse the actual resource policies to determine the resources for the cloudfox table. crazy pants
|
||||
@@ -0,0 +1,80 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/glue"
|
||||
glueTypes "github.com/aws/aws-sdk-go-v2/service/glue/types"
|
||||
)
|
||||
|
||||
type MockedGlueClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedGlueClient) ListDevEndpoints(ctx context.Context, input *glue.ListDevEndpointsInput, options ...func(*glue.Options)) (*glue.ListDevEndpointsOutput, error) {
|
||||
return &glue.ListDevEndpointsOutput{
|
||||
DevEndpointNames: []string{
|
||||
"devendpoint1",
|
||||
"devendpoint2",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedGlueClient) ListJobs(ctx context.Context, input *glue.ListJobsInput, options ...func(*glue.Options)) (*glue.ListJobsOutput, error) {
|
||||
return &glue.ListJobsOutput{
|
||||
JobNames: []string{
|
||||
"job1",
|
||||
"job2",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedGlueClient) GetTables(ctx context.Context, input *glue.GetTablesInput, options ...func(*glue.Options)) (*glue.GetTablesOutput, error) {
|
||||
return &glue.GetTablesOutput{
|
||||
TableList: []glueTypes.Table{
|
||||
{
|
||||
Name: aws.String("table1"),
|
||||
DatabaseName: aws.String("database1"),
|
||||
Description: aws.String("description1"),
|
||||
Parameters: map[string]string{
|
||||
"param1": "value1",
|
||||
"param2": "value2",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: aws.String("table2"),
|
||||
DatabaseName: aws.String("database2"),
|
||||
Description: aws.String("description2"),
|
||||
Parameters: map[string]string{
|
||||
"param1": "value1",
|
||||
"param2": "value2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedGlueClient) GetDatabases(ctx context.Context, input *glue.GetDatabasesInput, options ...func(*glue.Options)) (*glue.GetDatabasesOutput, error) {
|
||||
return &glue.GetDatabasesOutput{
|
||||
DatabaseList: []glueTypes.Database{
|
||||
{
|
||||
Name: aws.String("database1"),
|
||||
Description: aws.String("description1"),
|
||||
LocationUri: aws.String("s3://bucket1"),
|
||||
Parameters: map[string]string{
|
||||
"param1": "value1",
|
||||
"param2": "value2",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: aws.String("database2"),
|
||||
Description: aws.String("description2"),
|
||||
LocationUri: aws.String("s3://bucket2"),
|
||||
Parameters: map[string]string{
|
||||
"param1": "value1",
|
||||
"param2": "value2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@ type GrafanaClientInterface interface {
|
||||
ListWorkspaces(context.Context, *grafana.ListWorkspacesInput, ...func(*grafana.Options)) (*grafana.ListWorkspacesOutput, error)
|
||||
}
|
||||
|
||||
func RegisterGrafanaTypes() {
|
||||
func init() {
|
||||
gob.Register([]grafanaTypes.WorkspaceSummary{})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/grafana"
|
||||
grafanaTypes "github.com/aws/aws-sdk-go-v2/service/grafana/types"
|
||||
)
|
||||
|
||||
type MockedGrafanaClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedGrafanaClient) ListWorkspaces(ctx context.Context, input *grafana.ListWorkspacesInput, options ...func(*grafana.Options)) (*grafana.ListWorkspacesOutput, error) {
|
||||
return &grafana.ListWorkspacesOutput{
|
||||
Workspaces: []grafanaTypes.WorkspaceSummary{
|
||||
{
|
||||
Authentication: &grafanaTypes.AuthenticationSummary{
|
||||
Providers: []grafanaTypes.AuthenticationProviderTypes{
|
||||
grafanaTypes.AuthenticationProviderTypesAwsSso,
|
||||
},
|
||||
},
|
||||
Created: aws.Time(time.Now()),
|
||||
Id: aws.String("workspace1"),
|
||||
Name: aws.String("workspace1"),
|
||||
Status: grafanaTypes.WorkspaceStatusActive,
|
||||
},
|
||||
{
|
||||
Authentication: &grafanaTypes.AuthenticationSummary{
|
||||
Providers: []grafanaTypes.AuthenticationProviderTypes{
|
||||
grafanaTypes.AuthenticationProviderTypesAwsSso,
|
||||
},
|
||||
},
|
||||
Created: aws.Time(time.Now()),
|
||||
Id: aws.String("workspace2"),
|
||||
Name: aws.String("workspace2"),
|
||||
Status: grafanaTypes.WorkspaceStatusActive,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
+40
-2
@@ -22,12 +22,14 @@ type AWSIAMClientInterface interface {
|
||||
GetAccountAuthorizationDetails(ctx context.Context, params *iam.GetAccountAuthorizationDetailsInput, optFns ...func(*iam.Options)) (*iam.GetAccountAuthorizationDetailsOutput, error)
|
||||
SimulatePrincipalPolicy(ctx context.Context, params *iam.SimulatePrincipalPolicyInput, optFns ...func(*iam.Options)) (*iam.SimulatePrincipalPolicyOutput, error)
|
||||
ListInstanceProfiles(ctx context.Context, params *iam.ListInstanceProfilesInput, optFns ...func(*iam.Options)) (*iam.ListInstanceProfilesOutput, error)
|
||||
ListGroups(ctx context.Context, params *iam.ListGroupsInput, optFns ...func(*iam.Options)) (*iam.ListGroupsOutput, error)
|
||||
}
|
||||
|
||||
func RegisterIamTypes() {
|
||||
func init() {
|
||||
gob.Register([]iamTypes.User{})
|
||||
gob.Register([]iamTypes.AccessKeyMetadata{})
|
||||
gob.Register([]iamTypes.Role{})
|
||||
gob.Register([]iamTypes.Group{})
|
||||
gob.Register([]iamTypes.PolicyDetail{})
|
||||
gob.Register([]iamTypes.InstanceProfile{})
|
||||
gob.Register([]iamTypes.EvaluationResult{})
|
||||
@@ -205,7 +207,7 @@ func CachedIamSimulatePrincipalPolicy(IAMClient AWSIAMClientInterface, accountID
|
||||
var EvaluationResults []iamTypes.EvaluationResult
|
||||
md5hashedActionNames := md5.Sum([]byte(strings.Join(actionNames, "")))
|
||||
md5hashedResourceArns := md5.Sum([]byte(strings.Join(resourceArns, "")))
|
||||
// proccess arn and get the name of the resource
|
||||
// process arn and get the name of the resource
|
||||
arn, err := arn.Parse(*principal)
|
||||
if err != nil {
|
||||
return EvaluationResults, err
|
||||
@@ -250,3 +252,39 @@ func CachedIamSimulatePrincipalPolicy(IAMClient AWSIAMClientInterface, accountID
|
||||
internal.Cache.Set(cacheKey, EvaluationResults, cache.DefaultExpiration)
|
||||
return EvaluationResults, nil
|
||||
}
|
||||
|
||||
func CachedIamListGroups(IAMClient AWSIAMClientInterface, accountID string) ([]iamTypes.Group, error) {
|
||||
var PaginationControl *string
|
||||
var Groups []iamTypes.Group
|
||||
cacheKey := fmt.Sprintf("%s-iam-ListGroups", accountID)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]iamTypes.Group), nil
|
||||
}
|
||||
|
||||
for {
|
||||
ListGroups, err := IAMClient.ListGroups(
|
||||
context.TODO(),
|
||||
&iam.ListGroupsInput{
|
||||
Marker: PaginationControl,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return Groups, err
|
||||
}
|
||||
|
||||
Groups = append(Groups, ListGroups.Groups...)
|
||||
|
||||
// Pagination control.
|
||||
if ListGroups.Marker != nil {
|
||||
PaginationControl = ListGroups.Marker
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, Groups, cache.DefaultExpiration)
|
||||
return Groups, nil
|
||||
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/iam"
|
||||
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
|
||||
)
|
||||
|
||||
type MockedIAMClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedIAMClient) ListUsers(ctx context.Context, input *iam.ListUsersInput, options ...func(*iam.Options)) (*iam.ListUsersOutput, error) {
|
||||
return &iam.ListUsersOutput{
|
||||
Users: []iamTypes.User{
|
||||
{
|
||||
Arn: aws.String("arn:aws:iam::123456789012:user/user1"),
|
||||
CreateDate: aws.Time(time.Now()),
|
||||
Path: aws.String("/"),
|
||||
UserId: aws.String("123456789012"),
|
||||
UserName: aws.String("user1"),
|
||||
},
|
||||
{
|
||||
Arn: aws.String("arn:aws:iam::123456789012:user/user2"),
|
||||
CreateDate: aws.Time(time.Now()),
|
||||
Path: aws.String("/"),
|
||||
UserId: aws.String("123456789012"),
|
||||
UserName: aws.String("user2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedIAMClient) ListAccessKeys(ctx context.Context, input *iam.ListAccessKeysInput, options ...func(*iam.Options)) (*iam.ListAccessKeysOutput, error) {
|
||||
return &iam.ListAccessKeysOutput{
|
||||
AccessKeyMetadata: []iamTypes.AccessKeyMetadata{
|
||||
{
|
||||
AccessKeyId: aws.String("accesskey1"),
|
||||
CreateDate: aws.Time(time.Now()),
|
||||
Status: iamTypes.StatusTypeActive,
|
||||
UserName: aws.String("user1"),
|
||||
},
|
||||
{
|
||||
AccessKeyId: aws.String("accesskey2"),
|
||||
CreateDate: aws.Time(time.Now()),
|
||||
Status: iamTypes.StatusTypeActive,
|
||||
UserName: aws.String("user2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (m *MockedIAMClient) ListGroups(ctx context.Context, input *iam.ListGroupsInput, options ...func(*iam.Options)) (*iam.ListGroupsOutput, error) {
|
||||
return &iam.ListGroupsOutput{
|
||||
Groups: []iamTypes.Group{
|
||||
{
|
||||
Arn: aws.String("arn:aws:iam::123456789012:group/group1"),
|
||||
CreateDate: aws.Time(time.Now()),
|
||||
GroupId: aws.String("123456789012"),
|
||||
GroupName: aws.String("group1"),
|
||||
Path: aws.String("/"),
|
||||
},
|
||||
{
|
||||
Arn: aws.String("arn:aws:iam::123456789012:group/group2"),
|
||||
CreateDate: aws.Time(time.Now()),
|
||||
GroupId: aws.String("123456789012"),
|
||||
GroupName: aws.String("group2"),
|
||||
Path: aws.String("/"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (m *MockedIAMClient) ListRoles(ctx context.Context, input *iam.ListRolesInput, options ...func(*iam.Options)) (*iam.ListRolesOutput, error) {
|
||||
return &iam.ListRolesOutput{
|
||||
Roles: []iamTypes.Role{
|
||||
{
|
||||
Arn: aws.String("arn:aws:iam::123456789012:role/role1"),
|
||||
AssumeRolePolicyDocument: aws.String("{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"),
|
||||
CreateDate: aws.Time(time.Now()),
|
||||
RoleId: aws.String("123456789012"),
|
||||
RoleName: aws.String("role1"),
|
||||
Path: aws.String("/"),
|
||||
},
|
||||
{
|
||||
Arn: aws.String("arn:aws:iam::123456789012:role/role2"),
|
||||
AssumeRolePolicyDocument: aws.String("{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"),
|
||||
CreateDate: aws.Time(time.Now()),
|
||||
RoleId: aws.String("123456789012"),
|
||||
RoleName: aws.String("role2"),
|
||||
Path: aws.String("/"),
|
||||
},
|
||||
{
|
||||
Arn: aws.String("arn:aws:iam::123456789012:role/role3"),
|
||||
AssumeRolePolicyDocument: aws.String("{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"},\"Action\":\"sts:AssumeRole\"}]}"),
|
||||
CreateDate: aws.Time(time.Now()),
|
||||
RoleId: aws.String("123456789012"),
|
||||
RoleName: aws.String("role3"),
|
||||
Path: aws.String("/"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (m *MockedIAMClient) GetAccountAuthorizationDetails(ctx context.Context, params *iam.GetAccountAuthorizationDetailsInput, optFns ...func(*iam.Options)) (*iam.GetAccountAuthorizationDetailsOutput, error) {
|
||||
return &iam.GetAccountAuthorizationDetailsOutput{
|
||||
GroupDetailList: []iamTypes.GroupDetail{
|
||||
{
|
||||
Arn: aws.String("arn:aws:iam::123456789012:group/group1"),
|
||||
CreateDate: aws.Time(time.Now()),
|
||||
GroupId: aws.String("123456789012"),
|
||||
GroupName: aws.String("group1"),
|
||||
Path: aws.String("/"),
|
||||
},
|
||||
{
|
||||
Arn: aws.String("arn:aws:iam::123456789012:group/group2"),
|
||||
CreateDate: aws.Time(time.Now()),
|
||||
GroupId: aws.String("123456789012"),
|
||||
GroupName: aws.String("group2"),
|
||||
Path: aws.String("/"),
|
||||
},
|
||||
},
|
||||
RoleDetailList: []iamTypes.RoleDetail{
|
||||
{
|
||||
Arn: aws.String("arn:aws:iam::123456789012:role/role1"),
|
||||
AssumeRolePolicyDocument: aws.String("{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"),
|
||||
CreateDate: aws.Time(time.Now()),
|
||||
RoleId: aws.String("123456789012"),
|
||||
RoleName: aws.String("role1"),
|
||||
Path: aws.String("/"),
|
||||
},
|
||||
{
|
||||
Arn: aws.String("arn:aws:iam::123456789012:role/role2"),
|
||||
AssumeRolePolicyDocument: aws.String("{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"),
|
||||
CreateDate: aws.Time(time.Now()),
|
||||
RoleId: aws.String("123456789012"),
|
||||
RoleName: aws.String("role2"),
|
||||
Path: aws.String("/"),
|
||||
},
|
||||
{
|
||||
Arn: aws.String("arn:aws:iam::123456789012:role/role3"),
|
||||
AssumeRolePolicyDocument: aws.String("{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"},\"Action\":\"sts:AssumeRole\"}]}"),
|
||||
CreateDate: aws.Time(time.Now()),
|
||||
RoleId: aws.String("123456789012"),
|
||||
|
||||
RoleName: aws.String("role3"),
|
||||
Path: aws.String("/"),
|
||||
},
|
||||
},
|
||||
UserDetailList: []iamTypes.UserDetail{
|
||||
{
|
||||
Arn: aws.String("arn:aws:iam::123456789012:user/user1"),
|
||||
CreateDate: aws.Time(time.Now()),
|
||||
|
||||
UserId: aws.String("123456789012"),
|
||||
UserName: aws.String("user1"),
|
||||
Path: aws.String("/"),
|
||||
},
|
||||
{
|
||||
Arn: aws.String("arn:aws:iam::123456789012:user/user2"),
|
||||
CreateDate: aws.Time(time.Now()),
|
||||
|
||||
UserId: aws.String("123456789012"),
|
||||
UserName: aws.String("user2"),
|
||||
Path: aws.String("/"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedIAMClient) SimulatePrincipalPolicy(ctx context.Context, params *iam.SimulatePrincipalPolicyInput, optFns ...func(*iam.Options)) (*iam.SimulatePrincipalPolicyOutput, error) {
|
||||
return &iam.SimulatePrincipalPolicyOutput{
|
||||
EvaluationResults: []iamTypes.EvaluationResult{
|
||||
{
|
||||
EvalActionName: aws.String("sts:AssumeRole"),
|
||||
EvalDecision: iamTypes.PolicyEvaluationDecisionTypeAllowed,
|
||||
EvalResourceName: aws.String("arn:aws:iam::123456789012:role/role1"),
|
||||
MatchedStatements: []iamTypes.Statement{
|
||||
{
|
||||
SourcePolicyId: aws.String("PolicyForRole1"),
|
||||
SourcePolicyType: iamTypes.PolicySourceTypeUser,
|
||||
StartPosition: &iamTypes.Position{
|
||||
Column: 0,
|
||||
Line: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (m *MockedIAMClient) ListInstanceProfiles(ctx context.Context, params *iam.ListInstanceProfilesInput, optFns ...func(*iam.Options)) (*iam.ListInstanceProfilesOutput, error) {
|
||||
return &iam.ListInstanceProfilesOutput{
|
||||
InstanceProfiles: []iamTypes.InstanceProfile{
|
||||
{
|
||||
Arn: aws.String("arn:aws:iam::123456789012:instance-profile/instance-profile1"),
|
||||
CreateDate: aws.Time(time.Now()),
|
||||
InstanceProfileId: aws.String("123456789012"),
|
||||
InstanceProfileName: aws.String("instance-profile1"),
|
||||
Path: aws.String("/"),
|
||||
Roles: []iamTypes.Role{
|
||||
{
|
||||
Arn: aws.String("arn:aws:iam::123456789012:role/role1"),
|
||||
AssumeRolePolicyDocument: aws.String("{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"),
|
||||
CreateDate: aws.Time(time.Now()),
|
||||
RoleId: aws.String("123456789012"),
|
||||
RoleName: aws.String("role1"),
|
||||
Path: aws.String("/"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/aws-sdk-go-v2/service/kinesis"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
type AWSKinesisClientInterface interface {
|
||||
ListStreams(context.Context, *kinesis.ListStreamsInput, ...func(*kinesis.Options)) (*kinesis.ListStreamsOutput, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register([]string{})
|
||||
}
|
||||
|
||||
func CachedKinesisListStreams(client AWSKinesisClientInterface, accountID string, region string) ([]string, error) {
|
||||
var PaginationControl *string
|
||||
var streams []string
|
||||
cacheKey := fmt.Sprintf("%s-kinesis-ListStreams-%s", accountID, region)
|
||||
cached, found := internal.Cache.Get(cacheKey)
|
||||
if found {
|
||||
return cached.([]string), nil
|
||||
}
|
||||
for {
|
||||
ListStreams, err := client.ListStreams(
|
||||
context.TODO(),
|
||||
&kinesis.ListStreamsInput{
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *kinesis.Options) {
|
||||
o.Region = region
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return streams, err
|
||||
}
|
||||
|
||||
streams = append(streams, ListStreams.StreamNames...)
|
||||
|
||||
//pagination
|
||||
if ListStreams.NextToken == nil {
|
||||
break
|
||||
}
|
||||
PaginationControl = ListStreams.NextToken
|
||||
}
|
||||
|
||||
internal.Cache.Set(cacheKey, streams, cache.DefaultExpiration)
|
||||
return streams, nil
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/kinesis"
|
||||
)
|
||||
|
||||
type MockedKinesisClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedKinesisClient) ListStreams(ctx context.Context, input *kinesis.ListStreamsInput, options ...func(*kinesis.Options)) (*kinesis.ListStreamsOutput, error) {
|
||||
return &kinesis.ListStreamsOutput{
|
||||
HasMoreStreams: aws.Bool(false),
|
||||
StreamNames: []string{
|
||||
"stream1",
|
||||
"stream2",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
+1
-1
@@ -17,7 +17,7 @@ type LambdaClientInterface interface {
|
||||
GetFunctionUrlConfig(context.Context, *lambda.GetFunctionUrlConfigInput, ...func(*lambda.Options)) (*lambda.GetFunctionUrlConfigOutput, error)
|
||||
}
|
||||
|
||||
func RegisterLambdaTypes() {
|
||||
func init() {
|
||||
gob.Register([]lambdaTypes.FunctionConfiguration{})
|
||||
gob.Register(customGetFuntionURLOutput{})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/lambda"
|
||||
lambdaTypes "github.com/aws/aws-sdk-go-v2/service/lambda/types"
|
||||
)
|
||||
|
||||
type MockedLambdaClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedLambdaClient) ListFunctions(ctx context.Context, input *lambda.ListFunctionsInput, options ...func(*lambda.Options)) (*lambda.ListFunctionsOutput, error) {
|
||||
return &lambda.ListFunctionsOutput{
|
||||
Functions: []lambdaTypes.FunctionConfiguration{
|
||||
{
|
||||
FunctionArn: aws.String("arn:aws:lambda:us-east-1:123456789012:function:my-function"),
|
||||
FunctionName: aws.String("my-function"),
|
||||
Handler: aws.String("index.handler"),
|
||||
Runtime: lambdaTypes.RuntimeNodejs18x,
|
||||
Environment: &lambdaTypes.EnvironmentResponse{
|
||||
Variables: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
FunctionArn: aws.String("arn:aws:lambda:us-east-1:123456789012:function:my-function2"),
|
||||
FunctionName: aws.String("my-function2"),
|
||||
Handler: aws.String("index.handler"),
|
||||
Runtime: lambdaTypes.RuntimeNodejs18x,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedLambdaClient) GetFunction(ctx context.Context, input *lambda.GetFunctionInput, options ...func(*lambda.Options)) (*lambda.GetFunctionOutput, error) {
|
||||
return &lambda.GetFunctionOutput{
|
||||
Configuration: &lambdaTypes.FunctionConfiguration{
|
||||
FunctionArn: aws.String("arn:aws:lambda:us-east-1:123456789012:function:my-function"),
|
||||
FunctionName: aws.String("my-function"),
|
||||
Handler: aws.String("index.handler"),
|
||||
Runtime: lambdaTypes.RuntimeNodejs18x,
|
||||
Environment: &lambdaTypes.EnvironmentResponse{
|
||||
Variables: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *MockedLambdaClient) GetFunctionUrlConfig(ctx context.Context, input *lambda.GetFunctionInput, options ...func(*lambda.Options)) (*lambda.GetFunctionUrlConfigOutput, error) {
|
||||
return &lambda.GetFunctionUrlConfigOutput{
|
||||
FunctionUrl: aws.String("https://my-function.us-east-1.amazonaws.com/Prod/"),
|
||||
FunctionArn: aws.String("arn:aws:lambda:us-east-1:123456789012:function:my-function"),
|
||||
AuthType: lambdaTypes.FunctionUrlAuthTypeNone,
|
||||
}, nil
|
||||
}
|
||||
@@ -16,8 +16,12 @@ type lightsailClientInterface interface {
|
||||
GetContainerServices(context.Context, *lightsail.GetContainerServicesInput, ...func(*lightsail.Options)) (*lightsail.GetContainerServicesOutput, error)
|
||||
}
|
||||
|
||||
func RegisterLightsailTypes() {
|
||||
//gob.Register([]lightsailTypes.Instance{})
|
||||
func init() {
|
||||
|
||||
//need to do this to avoid conflicts with the Instance type in the ec2 package
|
||||
type lightsailInstance lightsailTypes.Instance
|
||||
gob.Register([]lightsailInstance{})
|
||||
|
||||
gob.Register([]lightsailTypes.ContainerService{})
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/lightsail"
|
||||
lightsailTypes "github.com/aws/aws-sdk-go-v2/service/lightsail/types"
|
||||
)
|
||||
|
||||
type MockedLightsailClient struct {
|
||||
}
|
||||
|
||||
func (m *MockedLightsailClient) GetInstances(ctx context.Context, input *lightsail.GetInstancesInput, options ...func(*lightsail.Options)) (*lightsail.GetInstancesOutput, error) {
|
||||
return &lightsail.GetInstancesOutput{
|
||||
Instances: []lightsailTypes.Instance{
|
||||
{
|
||||
BlueprintId: aws.String("blueprint1"),
|
||||
BundleId: aws.String("bundle1"),
|
||||
CreatedAt: aws.Time(time.Now()),
|
||||
Location: &lightsailTypes.ResourceLocation{
|
||||
AvailabilityZone: aws.String("us-east-1a"),
|
||||
RegionName: lightsailTypes.RegionNameUsEast1,
|
||||
},
|
||||
Name: aws.String("instance1"),
|
||||
Networking: &lightsailTypes.InstanceNetworking{
|
||||
MonthlyTransfer: &lightsailTypes.MonthlyTransfer{
|
||||
GbPerMonthAllocated: aws.Int32(1),
|
||||
},
|
||||
},
|
||||
PrivateIpAddress: aws.String("10.1.1.1"),
|
||||
PublicIpAddress: aws.String("1.2.3.4"),
|
||||
},
|
||||
{
|
||||
BlueprintId: aws.String("blueprint2"),
|
||||
BundleId: aws.String("bundle2"),
|
||||
CreatedAt: aws.Time(time.Now()),
|
||||
Location: &lightsailTypes.ResourceLocation{
|
||||
AvailabilityZone: aws.String("us-east-1b"),
|
||||
RegionName: lightsailTypes.RegionNameUsEast1,
|
||||
},
|
||||
Name: aws.String("instance2"),
|
||||
Networking: &lightsailTypes.InstanceNetworking{
|
||||
MonthlyTransfer: &lightsailTypes.MonthlyTransfer{
|
||||
GbPerMonthAllocated: aws.Int32(2),
|
||||
},
|
||||
},
|
||||
PrivateIpAddress: aws.String("10.2.2.2"),
|
||||
PublicIpAddress: aws.String("2.3.4.4"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (m *MockedLightsailClient) GetContainerServices(ctx context.Context, input *lightsail.GetContainerServicesInput, options ...func(*lightsail.Options)) (*lightsail.GetContainerServicesOutput, error) {
|
||||
return &lightsail.GetContainerServicesOutput{
|
||||
ContainerServices: []lightsailTypes.ContainerService{
|
||||
{
|
||||
Arn: aws.String("arn1"),
|
||||
Location: &lightsailTypes.ResourceLocation{
|
||||
AvailabilityZone: aws.String("us-east-1a"),
|
||||
RegionName: lightsailTypes.RegionNameUsEast1,
|
||||
},
|
||||
Url: aws.String("https://container1"),
|
||||
PrivateDomainName: aws.String("container1"),
|
||||
},
|
||||
{
|
||||
Arn: aws.String("arn2"),
|
||||
Location: &lightsailTypes.ResourceLocation{
|
||||
AvailabilityZone: aws.String("us-east-1a"),
|
||||
RegionName: lightsailTypes.RegionNameUsEast1,
|
||||
},
|
||||
Url: aws.String("https://container2"),
|
||||
PrivateDomainName: aws.String("container2"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
Loaded 100 of 171 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user