mirror of
https://github.com/BishopFox/cloudfox.git
synced 2026-11-07 09:56:01 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
530dcbdb67 | ||
|
|
79eb720e72 | ||
|
|
c3dac71f1d | ||
|
|
ecdd33c522 | ||
|
|
1c243c73dc | ||
|
|
ca7df5a039 | ||
|
|
8850384fc0 | ||
|
|
775c233858 | ||
|
|
9fc38f5e64 | ||
|
|
d20b2b1099 | ||
|
|
eb95e887de | ||
|
|
064f285652 | ||
|
|
3e0718636f | ||
|
|
f672c34806 | ||
|
|
4d4992da33 | ||
|
|
a5d4124bb1 | ||
|
|
6123172784 | ||
|
|
6ace00021b | ||
|
|
5183b32d75 | ||
|
|
ffa8e09fbe | ||
|
|
26562574c3 | ||
|
|
2e82cc895d | ||
|
|
dadef384c0 | ||
|
|
40d7a3c2d0 | ||
|
|
8ce9a00697 | ||
|
|
7d8ebc27c7 | ||
|
|
45e548cf0b | ||
|
|
9498d2db13 | ||
|
|
71e85c29d2 | ||
|
|
5525266855 | ||
|
|
a28d4e8556 | ||
|
|
ee7c3c0387 | ||
|
|
467f6b459b | ||
|
|
cc38c1f147 | ||
|
|
bb879c34c2 | ||
|
|
00317a5336 | ||
|
|
8bfee76c28 | ||
|
|
712ec9c382 | ||
|
|
7c8afb6fef | ||
|
|
1a5f64e8c5 | ||
|
|
7c323f4f75 | ||
|
|
3db423b740 | ||
|
|
abe6f3da61 | ||
|
|
16fcbc2fdd | ||
|
|
7cafe1f066 | ||
|
|
59b082a40b | ||
|
|
b33653822c | ||
|
|
e15c71734e | ||
|
|
065e04affe | ||
|
|
26dbb56d60 | ||
|
|
58b9c7306e | ||
|
|
18f6cbe35f | ||
|
|
8cdb854740 | ||
|
|
dfe2fc1037 | ||
|
|
e35aa612ba | ||
|
|
89c6c0ddb3 | ||
|
|
6aee4780b3 | ||
|
|
351a9cc724 | ||
|
|
b3531faead | ||
|
|
7ad15cc599 | ||
|
|
95d1fbc621 | ||
|
|
91ad816b75 | ||
|
|
cd3a4be1ff | ||
|
|
b05254d434 | ||
|
|
79dbe13468 | ||
|
|
a63c62d5b1 | ||
|
|
a342c4da04 | ||
|
|
9a7ecc5a0f | ||
|
|
8dd7fba172 | ||
|
|
bc72d939f3 | ||
|
|
fe96654ae7 | ||
|
|
a8d086bf2a |
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 434 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 273 KiB |
@@ -4,21 +4,32 @@ CloudFox helps you gain situational awareness in unfamiliar cloud environments.
|
||||
#### CloudFox helps you answer the following common questions (and many more):
|
||||
|
||||
* What regions is this AWS account using and roughly how many resources are in the account?
|
||||
* What secrets are lurking in EC2 userdata or service specific environment variables?
|
||||
* What secrets are lurking in EC2 userdata or service specific environment variables?
|
||||
* What workloads have administrative permissions attached?
|
||||
* What actions/permissions does this [principal] have?
|
||||
* What roles trusts are overly permissive or allow cross-account assumption?
|
||||
* What role trusts are overly permissive or allow cross-account assumption?
|
||||
* What endpoints/hostnames/IPs can I attack from an external starting point (public internet)?
|
||||
* What endpoints/hostnames/IPs can I attack from an internal starting point (assumed breach within the VPC)?
|
||||
* What filesystems can I potentially mount from a compromised resource inside the VPC?
|
||||
|
||||
## Demos, Examples, Walkthroughs
|
||||
* [Blog - Introducing: CloudFox](https://bishopfox.com/blog/introducing-cloudfox)
|
||||
* [Video - CloudFox Intro Demo's](https://www.youtube.com/watch?v=ReWoUgpUuiQ)
|
||||
* [Video - Tool Talk: CloudFox AWS sub-command walkthroughs](https://youtu.be/KKsYfL5uVU4?t=360)
|
||||
|
||||
## 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:
|
||||
|
||||
`cloudfox aws --profile [profile-name] all-checks`
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
CloudFox is designed to be executed by a principal with limited read-only permissions, but it's purpose is to help you find attack paths that can be exploited in simulated compromise scenarios (aka, objective based penetration testing).
|
||||
### White Box Enumeration
|
||||
CloudFox was designed to be executed by a principal with limited read-only permissions, but it's purpose is to help you find attack paths that can be exploited in simulated compromise scenarios (aka, objective based penetration testing).
|
||||
|
||||
### Black Box Enumeration
|
||||
CloudFox can be with "found" credentials, similar to how you would use [weirdAAL](https://github.com/carnal0wnage/weirdAAL) or [enumerate-iam](https://github.com/andresriancho/enumerate-iam). Checks that fail, do so silently, so any data returned means your "found" creds have the access needed to retrieve it.
|
||||
|
||||
For the full documentation please refer to our [wiki](https://github.com/BishopFox/CloudFox/wiki).
|
||||
|
||||
@@ -27,7 +38,7 @@ For the full documentation please refer to our [wiki](https://github.com/BishopF
|
||||
|
||||
| Provider| CloudFox Commands |
|
||||
| - | - |
|
||||
| AWS | 15 |
|
||||
| AWS | 19 |
|
||||
| Azure | 2 (alpha) |
|
||||
| GCP | Support Planned |
|
||||
| Kubernetes | Support Planned |
|
||||
@@ -52,6 +63,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.
|
||||
* A principal with one recommended policies attached (described below)
|
||||
* Recommended attached policies: **`SecurityAudit` + [CloudFox custom policy](./misc/aws/cloudfox-policy.json)**
|
||||
|
||||
@@ -74,6 +86,7 @@ Additional policy notes (as of 09/2022):
|
||||
| AWS | [all-checks](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#all-checks) | Run all of the other commands using reasonable defaults. You'll still want to check out the non-default options of each command, but this is a great place to start. |
|
||||
| AWS | [access-keys](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#access-keys) | Lists active access keys for all users. Useful for cross referencing a key you found with which in-scope account it belongs to. |
|
||||
| AWS | [buckets](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#filesystems) | Lists the buckets in the account and gives you handy commands for inspecting them further. |
|
||||
| AWS | [cloudformation](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#cloudformation) | Lists the cloudformation stacks in the account. Generates loot file with stack details, stack parameters, and stack output - look for secrets. |
|
||||
| AWS | [ecr](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#ecr) | List the most recently pushed image URI from all repositories. Use the loot file to pull selected images down with docker/nerdctl for inspection. |
|
||||
| AWS | [endpoints](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#endpoints) | Enumerates endpoints from various services. Scan these endpoints from both an internal and external position to look for things that don't require authentication, are misconfigured, etc. |
|
||||
| AWS | [env-vars](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#env-vars) | Grabs the environment variables from services that have them (App Runner, ECS, Lambda, Lightsail containers, Sagemaker are supported. If you find a sensitive secret, use `cloudfox iam-simulator` AND `pmapper` to see who has access to them. |
|
||||
@@ -81,12 +94,15 @@ Additional policy notes (as of 09/2022):
|
||||
| AWS | [iam-simulator](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#iam-simulator) | Like pmapper, but uses the IAM policy simulator. It uses AWS's evaluation logic, but notably, it doesn't consider transitive access via privesc, which is why you should also always also use pmapper. |
|
||||
| AWS | [instances](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#instances) | Enumerates useful information for EC2 Instances in all regions like name, public/private IPs, and instance profiles. Generates loot files you can feed to nmap and other tools for service enumeration. |
|
||||
| AWS | [inventory](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#inventory) | Gain a rough understanding of size of the account and preferred regions. |
|
||||
| AWS | [lambda](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#lambda) | Lists the lambda functions in the account, including which one's have admin roles attached. Also gives you handy commands for downloading each function. |
|
||||
| AWS | [outbound-assumed-roles](#outbound-assumed-roles) | List the roles that have been assumed by principals in this account. This is an excellent way to find outbound attack paths that lead into other accounts. |
|
||||
| AWS | [permissions](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#permissions) | Enumerates IAM permissions associated with all users and roles. Grep this output to figure out what permissions a particular principal has rather than logging into the AWS console and painstakingly expanding each policy attached to the principal you are investigating. |
|
||||
| AWS | [principals](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#principals) | Enumerates IAM users and Roles so you have the data at your fingertips. |
|
||||
| AWS | [ram](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#ram) | List all resources in this account that are shared with other accounts, or resources from other accounts that are shared with this account. Useful for cross-account attack paths. |
|
||||
| AWS | [role-trusts](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#role-trusts) | Enumerates IAM role trust policies so you can look for overly permissive role trusts or find roles that trust a specific service. |
|
||||
| AWS | [route53](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#route53) | Enumerate all records from all route53 managed zones. Use this for application and service enumeration. |
|
||||
| AWS | [secrets](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#secrets) | List secrets from SecretsManager and SSM. Look for interesting secrets in the list and then see who has access to them using use `cloudfox iam-simulator` and/or `pmapper`. |
|
||||
| AWS | [tags](https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#tags) | List all resources with tags, and all of the tags. This can be used similar to inventory as another method to identify what types of resources exist in an account. |
|
||||
| Azure | [instances-map](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#instances-map) | Enumerates useful information for Compute instances in all available resource groups and subscriptions |
|
||||
| Azure | [rbac-map](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#rbac-map) | Enumerates Role Assignments for all tenants |
|
||||
|
||||
|
||||
+12
-10
@@ -21,6 +21,7 @@ type AccessKeysModule struct {
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSProfile string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
CommandCounter console.CommandCounter
|
||||
|
||||
// Main module data
|
||||
@@ -41,15 +42,16 @@ func (m *AccessKeysModule) PrintAccessKeys(filter string, outputFormat string, o
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "access-keys"
|
||||
m.modLog = utils.TxtLogger.WithFields(logrus.Fields{
|
||||
m.modLog = utils.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
},
|
||||
)
|
||||
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), aws.ToString(m.Caller.UserId))
|
||||
m.AWSProfile = utils.BuildAWSPath(m.Caller)
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] Mapping user access keys for account: %s.\n", cyan(m.output.CallingModule), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s][%s] Mapping user access keys for account: %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
m.getAccessKeysForAllUsers()
|
||||
|
||||
// Variables used to draw table output
|
||||
@@ -74,23 +76,23 @@ func (m *AccessKeysModule) PrintAccessKeys(filter string, outputFormat string, o
|
||||
if len(m.output.Body) > 0 {
|
||||
|
||||
// Pretty prints output
|
||||
fmt.Printf("[%s] Only active access keys are shown.\n", cyan(m.output.CallingModule))
|
||||
//fmt.Printf("[%s] Preparing output.\n\n")
|
||||
fmt.Printf("[%s][%s] Only active access keys are shown.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
//fmt.Printf("[%s][%s] Preparing output.\n\n")
|
||||
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", 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)
|
||||
|
||||
m.writeLoot(outputDirectory, verbosity)
|
||||
fmt.Printf("[%s] %s access keys found.\n", cyan(m.output.CallingModule), strconv.Itoa(len(m.output.Body)))
|
||||
m.writeLoot(m.output.FilePath, verbosity)
|
||||
fmt.Printf("[%s][%s] %s access keys found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
} else {
|
||||
fmt.Printf("[%s] No access keys found, skipping the creation of an output file.\n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] No access keys found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (m *AccessKeysModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
path := filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile, "loot")
|
||||
path := filepath.Join(outputDirectory, "loot")
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
@@ -107,7 +109,7 @@ func (m *AccessKeysModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
}
|
||||
fmt.Printf("[%s] Loot written to [%s]\n", cyan(m.output.CallingModule), f)
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), f)
|
||||
|
||||
}
|
||||
|
||||
|
||||
+29
-16
@@ -25,6 +25,7 @@ type BucketsModule struct {
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
|
||||
// Main module data
|
||||
@@ -46,16 +47,18 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "buckets"
|
||||
m.modLog = utils.TxtLogger.WithFields(logrus.Fields{
|
||||
m.modLog = utils.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), aws.ToString(m.Caller.UserId))
|
||||
m.AWSProfile = utils.BuildAWSPath(m.Caller)
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] Enumerating buckets for account %s.\n", cyan(m.output.CallingModule), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s][%s] Enumerating buckets for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
semaphore := make(chan struct{}, m.Goroutines)
|
||||
|
||||
// Create a channel to signal the spinner aka task status goroutine to finish
|
||||
spinnerDone := make(chan bool)
|
||||
@@ -71,7 +74,7 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
|
||||
|
||||
wg.Add(1)
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(wg, dataReceiver)
|
||||
go m.executeChecks(wg, semaphore, dataReceiver)
|
||||
|
||||
wg.Wait()
|
||||
// Send a message to the spinner goroutine to close the channel and stop
|
||||
@@ -100,11 +103,11 @@ func (m *BucketsModule) PrintBuckets(outputFormat string, outputDirectory string
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", 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)
|
||||
m.writeLoot(outputDirectory, verbosity, m.AWSProfile)
|
||||
fmt.Printf("[%s] %s buckets found.\n", cyan(m.output.CallingModule), strconv.Itoa(len(m.output.Body)))
|
||||
m.writeLoot(m.output.FilePath, verbosity, m.AWSProfile)
|
||||
fmt.Printf("[%s][%s] %s buckets found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
|
||||
} else {
|
||||
fmt.Printf("[%s] No buckets found, skipping the creation of an output file.\n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] No buckets found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -122,18 +125,18 @@ func (m *BucketsModule) Receiver(receiver chan Bucket, receiverDone chan bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *BucketsModule) executeChecks(wg *sync.WaitGroup, dataReceiver chan Bucket) {
|
||||
func (m *BucketsModule) executeChecks(wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Bucket) {
|
||||
defer wg.Done()
|
||||
|
||||
m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
m.getBuckets(m.output.Verbosity, dataReceiver)
|
||||
wg.Add(1)
|
||||
m.getBuckets(m.output.Verbosity, wg, semaphore, dataReceiver)
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}
|
||||
|
||||
func (m *BucketsModule) writeLoot(outputDirectory string, verbosity int, profile string) {
|
||||
path := filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile, "loot")
|
||||
path := filepath.Join(outputDirectory, "loot")
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
@@ -167,16 +170,26 @@ func (m *BucketsModule) writeLoot(outputDirectory string, verbosity int, profile
|
||||
|
||||
if verbosity > 2 {
|
||||
fmt.Println()
|
||||
fmt.Printf("[%s] %s \n", cyan(m.output.CallingModule), green("Use the commands below to manually inspect certain buckets of interest."))
|
||||
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("Use the commands below to manually inspect certain buckets of interest."))
|
||||
fmt.Print(out)
|
||||
fmt.Printf("[%s] %s \n", cyan(m.output.CallingModule), green("End of loot file."))
|
||||
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("End of loot file."))
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] Loot written to [%s]\n", cyan(m.output.CallingModule), pullFile)
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), pullFile)
|
||||
|
||||
}
|
||||
|
||||
func (m *BucketsModule) getBuckets(verbosity int, dataReceiver chan Bucket) {
|
||||
func (m *BucketsModule) getBuckets(verbosity int, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Bucket) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
|
||||
var r string = "Global"
|
||||
var name string
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/BishopFox/cloudfox/console"
|
||||
"github.com/BishopFox/cloudfox/utils"
|
||||
"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"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type CloudformationModule struct {
|
||||
// General configuration data
|
||||
CloudFormationClient *cloudformation.Client
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
|
||||
// Main module data
|
||||
CFStacks []CFStack
|
||||
CommandCounter console.CommandCounter
|
||||
// Used to store output data for pretty printing
|
||||
output utils.OutputData2
|
||||
modLog *logrus.Entry
|
||||
}
|
||||
|
||||
type CFStack struct {
|
||||
AWSService string
|
||||
Region string
|
||||
Name string
|
||||
Role string
|
||||
Outputs []types.Output
|
||||
Parameters []types.Parameter
|
||||
Template string
|
||||
}
|
||||
|
||||
func (m *CloudformationModule) PrintCloudformationStacks(outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These stuct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "cloudformation"
|
||||
m.modLog = utils.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = utils.BuildAWSPath(m.Caller)
|
||||
}
|
||||
|
||||
fmt.Printf("[%s][%s] Enumerating cloudformation stacks for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
semaphore := make(chan struct{}, m.Goroutines)
|
||||
|
||||
// Create a channel to signal the spinner aka task status goroutine to finish
|
||||
spinnerDone := make(chan bool)
|
||||
//fire up the the task status spinner/updated
|
||||
go console.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "regions")
|
||||
|
||||
//create a channel to receive the objects
|
||||
dataReceiver := make(chan CFStack)
|
||||
|
||||
// Create a channel to signal to stop
|
||||
receiverDone := make(chan bool)
|
||||
go m.Receiver(dataReceiver, receiverDone)
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
// Send a message to the spinner goroutine to close the channel and stop
|
||||
spinnerDone <- true
|
||||
<-spinnerDone
|
||||
// Send a message to the data receiver goroutine to close the channel and stop
|
||||
receiverDone <- true
|
||||
<-receiverDone
|
||||
|
||||
// add - if struct is not empty do this. otherwise, dont write anything.
|
||||
m.output.Headers = []string{
|
||||
"Service",
|
||||
"Region",
|
||||
"Name",
|
||||
"Role",
|
||||
// "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"
|
||||
// }
|
||||
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
m.CFStacks[i].AWSService,
|
||||
m.CFStacks[i].Region,
|
||||
m.CFStacks[i].Name,
|
||||
m.CFStacks[i].Role,
|
||||
// isParameters,
|
||||
// isOutputs,
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
if len(m.output.Body) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", 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)
|
||||
m.writeLoot(m.output.FilePath, verbosity)
|
||||
fmt.Printf("[%s][%s] %s cloudformation stacks found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
} else {
|
||||
fmt.Printf("[%s][%s] No cloudformation stacks found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *CloudformationModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan CFStack) {
|
||||
defer wg.Done()
|
||||
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getCFStacksPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
|
||||
func (m *CloudformationModule) Receiver(receiver chan CFStack, receiverDone chan bool) {
|
||||
defer close(receiverDone)
|
||||
for {
|
||||
select {
|
||||
case data := <-receiver:
|
||||
m.CFStacks = append(m.CFStacks, data)
|
||||
case <-receiverDone:
|
||||
receiverDone <- true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *CloudformationModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
path := filepath.Join(outputDirectory, "loot")
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
pullFile := filepath.Join(path, "cloudformation-data.txt")
|
||||
|
||||
var out string
|
||||
out = out + fmt.Sprintln("#############################################")
|
||||
out = out + fmt.Sprintln("# Look for secrets. Use something like trufflehog")
|
||||
out = out + fmt.Sprintln("#############################################")
|
||||
out = out + fmt.Sprintln("")
|
||||
|
||||
for _, stack := range m.CFStacks {
|
||||
out = out + fmt.Sprintf("=============================================\n")
|
||||
out = out + fmt.Sprintf("Stack Name: %s\n\n", stack.Name)
|
||||
out = out + fmt.Sprintf("Stack Outputs:\n\n")
|
||||
for _, output := range stack.Outputs {
|
||||
outputDescription := aws.ToString(output.Description)
|
||||
outputExport := aws.ToString(output.ExportName)
|
||||
outputKey := aws.ToString(output.OutputKey)
|
||||
outputValue := aws.ToString(output.OutputValue)
|
||||
out = out + fmt.Sprintf("Stack Output Description: %s\n", outputDescription)
|
||||
out = out + fmt.Sprintf("Stack Output Name: %s\n", outputExport)
|
||||
out = out + fmt.Sprintf("Stack Output Key: %s\n", outputKey)
|
||||
out = out + fmt.Sprintf("Stack Output Value: %s\n\n", outputValue)
|
||||
}
|
||||
out = out + fmt.Sprintf("Stack Parameters:\n\n")
|
||||
for _, param := range stack.Parameters {
|
||||
paramKey := aws.ToString(param.ParameterKey)
|
||||
paramValue := aws.ToString(param.ParameterValue)
|
||||
out = out + fmt.Sprintf("Stack Parameter Key: %s\n", paramKey)
|
||||
out = out + fmt.Sprintf("Stack Parameter Value: %s\n\n", paramValue)
|
||||
}
|
||||
//out = out + fmt.Sprintf("Stack Parameters:\n %s\n", stack.Parameters)
|
||||
out = out + fmt.Sprintf("Stack Template:\n %s\n", stack.Template)
|
||||
out = out + fmt.Sprintf("=============================================\n")
|
||||
|
||||
}
|
||||
err = os.WriteFile(pullFile, []byte(out), 0644)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
if verbosity > 2 {
|
||||
fmt.Println()
|
||||
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("Look for secrets. Use something like trufflehog"))
|
||||
fmt.Print(out)
|
||||
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("Look for secrets. Use something like trufflehog"))
|
||||
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("End of loot file."))
|
||||
}
|
||||
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), pullFile)
|
||||
|
||||
}
|
||||
|
||||
func (m *CloudformationModule) getCFStacksPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan CFStack) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
|
||||
var PaginationControl *string
|
||||
|
||||
for {
|
||||
DescribeStacks, err := m.CloudFormationClient.DescribeStacks(
|
||||
context.TODO(),
|
||||
&cloudformation.DescribeStacksInput{
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *cloudformation.Options) {
|
||||
o.Region = r
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
//var stackOutputs []types.Output
|
||||
for _, stack := range DescribeStacks.Stacks {
|
||||
stackName := aws.ToString(stack.StackName)
|
||||
stackRole := aws.ToString(stack.RoleARN)
|
||||
stackOutputs := stack.Outputs
|
||||
stackParameters := stack.Parameters
|
||||
|
||||
for {
|
||||
GetTemplate, err := m.CloudFormationClient.GetTemplate(
|
||||
context.TODO(),
|
||||
&cloudformation.GetTemplateInput{
|
||||
StackName: &stackName,
|
||||
},
|
||||
func(o *cloudformation.Options) {
|
||||
o.Region = r
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
stackTemplateBody := aws.ToString(GetTemplate.TemplateBody)
|
||||
|
||||
dataReceiver <- CFStack{
|
||||
AWSService: "cloudformation",
|
||||
Name: stackName,
|
||||
Region: r,
|
||||
Role: stackRole,
|
||||
Outputs: stackOutputs,
|
||||
Parameters: stackParameters,
|
||||
Template: stackTemplateBody,
|
||||
}
|
||||
|
||||
// }
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// The "NextToken" value is nil when there's no more data to return.
|
||||
if DescribeStacks.NextToken != nil {
|
||||
PaginationControl = DescribeStacks.NextToken
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
-19
@@ -26,6 +26,7 @@ type ECRModule struct {
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
|
||||
// Main module data
|
||||
@@ -51,16 +52,17 @@ func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbos
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "ecr"
|
||||
m.modLog = utils.TxtLogger.WithFields(logrus.Fields{
|
||||
m.modLog = utils.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), aws.ToString(m.Caller.UserId))
|
||||
m.AWSProfile = utils.BuildAWSPath(m.Caller)
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] Enumerating container repositories for account %s.\n", cyan(m.output.CallingModule), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s][%s] Enumerating container repositories for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
semaphore := make(chan struct{}, m.Goroutines)
|
||||
|
||||
// Create a channel to signal the spinner aka task status goroutine to finish
|
||||
spinnerDone := make(chan bool)
|
||||
@@ -77,7 +79,7 @@ func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbos
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, dataReceiver)
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
|
||||
}
|
||||
|
||||
@@ -120,22 +122,20 @@ func (m *ECRModule) PrintECR(outputFormat string, outputDirectory string, verbos
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", 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)
|
||||
m.writeLoot(outputDirectory, verbosity)
|
||||
fmt.Printf("[%s] %s repositories found.\n", cyan(m.output.CallingModule), strconv.Itoa(len(m.output.Body)))
|
||||
m.writeLoot(m.output.FilePath, verbosity)
|
||||
fmt.Printf("[%s][%s] %s repositories found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
} else {
|
||||
fmt.Printf("[%s] No repositories found, skipping the creation of an output file.\n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] No repositories found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *ECRModule) executeChecks(r string, wg *sync.WaitGroup, dataReceiver chan Repository) {
|
||||
func (m *ECRModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Repository) {
|
||||
defer wg.Done()
|
||||
|
||||
m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
m.getECRRecordsPerRegion(r, dataReceiver)
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Add(1)
|
||||
m.getECRRecordsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
|
||||
func (m *ECRModule) Receiver(receiver chan Repository, receiverDone chan bool) {
|
||||
@@ -152,7 +152,7 @@ func (m *ECRModule) Receiver(receiver chan Repository, receiverDone chan bool) {
|
||||
}
|
||||
|
||||
func (m *ECRModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
path := filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile, "loot")
|
||||
path := filepath.Join(outputDirectory, "loot")
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
@@ -186,18 +186,28 @@ func (m *ECRModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
|
||||
if verbosity > 2 {
|
||||
fmt.Println()
|
||||
fmt.Printf("[%s] %s \n", cyan(m.output.CallingModule), green("Use the commands below to authenticate to ECR and download the images that look interesting"))
|
||||
fmt.Printf("[%s] %s \n\n", cyan(m.output.CallingModule), green("You will need the ecr:GetAuthorizationToken on the registry to authenticate and this is not part of the SecurityAudit permissions policy"))
|
||||
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("Use the commands below to authenticate to ECR and download the images that look interesting"))
|
||||
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("You will need the ecr:GetAuthorizationToken on the registry to authenticate and this is not part of the SecurityAudit permissions policy"))
|
||||
|
||||
fmt.Print(out)
|
||||
fmt.Printf("[%s] %s \n\n", cyan(m.output.CallingModule), green("End of loot file."))
|
||||
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("End of loot file."))
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] Loot written to [%s]\n", cyan(m.output.CallingModule), pullFile)
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), pullFile)
|
||||
|
||||
}
|
||||
|
||||
func (m *ECRModule) getECRRecordsPerRegion(r string, dataReceiver chan Repository) {
|
||||
func (m *ECRModule) getECRRecordsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Repository) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
|
||||
var PaginationControl *string
|
||||
var PaginationControl2 *string
|
||||
|
||||
+15
-14
@@ -55,6 +55,7 @@ type EndpointsModule struct {
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
|
||||
// Main module data
|
||||
@@ -83,19 +84,19 @@ func (m *EndpointsModule) PrintEndpoints(outputFormat string, outputDirectory st
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "endpoints"
|
||||
m.modLog = utils.TxtLogger.WithFields(logrus.Fields{
|
||||
m.modLog = utils.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), aws.ToString(m.Caller.UserId))
|
||||
m.AWSProfile = utils.BuildAWSPath(m.Caller)
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] Enumerating endpoints for account %s.\n", cyan(m.output.CallingModule), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s] Supported Services: App Runner, APIGateway, ApiGatewayV2, Cloudfront, EKS, ELB, ELBv2, Grafana, \n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s] \t\t\tLambda, MQ, OpenSearch, Redshift, RDS\n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] Enumerating endpoints for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s][%s] Supported Services: App Runner, APIGateway, ApiGatewayV2, Cloudfront, EKS, ELB, ELBv2, Grafana, \n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
fmt.Printf("[%s][%s] \t\t\tLambda, MQ, OpenSearch, Redshift, RDS\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
semaphore := make(chan struct{}, 50)
|
||||
semaphore := make(chan struct{}, m.Goroutines)
|
||||
// Create a channel to signal the spinner aka task status goroutine to finish
|
||||
spinnerDone := make(chan bool)
|
||||
//fire up the the task status spinner/updated
|
||||
@@ -168,10 +169,10 @@ func (m *EndpointsModule) PrintEndpoints(outputFormat string, outputDirectory st
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", 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)
|
||||
m.writeLoot(outputDirectory, verbosity)
|
||||
fmt.Printf("[%s] %s endpoints enumerated.\n", cyan(m.output.CallingModule), strconv.Itoa(len(m.output.Body)))
|
||||
m.writeLoot(m.output.FilePath, verbosity)
|
||||
fmt.Printf("[%s][%s] %s endpoints found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
} else {
|
||||
fmt.Printf("[%s] No endpoints found, skipping the creation of an output file.\n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] No endpoints found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
|
||||
// This works great to print errors out after the module but i'm not really sure i want that.
|
||||
@@ -179,7 +180,7 @@ func (m *EndpointsModule) PrintEndpoints(outputFormat string, outputDirectory st
|
||||
// return m.Errors[i] < m.Errors[j]
|
||||
// })
|
||||
// for _, e := range m.Errors {
|
||||
// fmt.Printf("[%s] %s\n", cyan(m.output.CallingModule), e)
|
||||
// fmt.Printf("[%s][%s] %s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), e)
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -259,7 +260,7 @@ func (m *EndpointsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore
|
||||
}
|
||||
|
||||
func (m *EndpointsModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
path := filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile, "loot")
|
||||
path := filepath.Join(outputDirectory, "loot")
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
@@ -283,12 +284,12 @@ func (m *EndpointsModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
|
||||
if verbosity > 2 {
|
||||
fmt.Println()
|
||||
fmt.Printf("[%s] %s \n", cyan(m.output.CallingModule), green("Feed this endpoints into nmap and something like gowitness/aquatone for screenshots."))
|
||||
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("Feed this endpoints into nmap and something like gowitness/aquatone for screenshots."))
|
||||
fmt.Print(out)
|
||||
fmt.Printf("[%s] %s \n\n", cyan(m.output.CallingModule), green("End of loot file."))
|
||||
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("End of loot file."))
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] Loot written to [%s]\n", cyan(m.output.CallingModule), f)
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), f)
|
||||
|
||||
}
|
||||
|
||||
|
||||
+8
-7
@@ -37,6 +37,7 @@ type EnvsModule struct {
|
||||
AWSRegions []string
|
||||
AWSProfile string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
|
||||
// Service Clients
|
||||
ECSClient *ecs.Client
|
||||
@@ -67,18 +68,18 @@ func (m *EnvsModule) PrintEnvs(outputFormat string, outputDirectory string, verb
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "env-vars"
|
||||
m.modLog = utils.TxtLogger.WithFields(logrus.Fields{
|
||||
m.modLog = utils.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), aws.ToString(m.Caller.UserId))
|
||||
m.AWSProfile = utils.BuildAWSPath(m.Caller)
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] Enumerating environment variables in all regions for account %s.\n", cyan(m.output.CallingModule), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s] Supported Services: App Runner, Elastic Container Service, Lambda, Lightsail Containers, Sagemaker \n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] Enumerating environment variables in all regions for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s][%s] Supported Services: App Runner, Elastic Container Service, Lambda, Lightsail Containers, Sagemaker \n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
semaphore := make(chan struct{}, 50)
|
||||
semaphore := make(chan struct{}, m.Goroutines)
|
||||
|
||||
// Create a channel to signal the spinner aka task status goroutine to finish
|
||||
spinnerDone := make(chan bool)
|
||||
@@ -137,10 +138,10 @@ func (m *EnvsModule) PrintEnvs(outputFormat string, outputDirectory string, verb
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", 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)
|
||||
fmt.Printf("[%s] %s environment variables found.\n", cyan(m.output.CallingModule), strconv.Itoa(len(m.output.Body)))
|
||||
fmt.Printf("[%s][%s] %s environment variables found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
|
||||
} else {
|
||||
fmt.Printf("[%s] No environment variables found, skipping the creation of an output file.\n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] No environment variables found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-17
@@ -31,6 +31,7 @@ type FilesystemsModule struct {
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
|
||||
// Main module data
|
||||
@@ -58,21 +59,22 @@ func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirector
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "filesystems"
|
||||
m.modLog = utils.TxtLogger.WithFields(logrus.Fields{
|
||||
m.modLog = utils.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), aws.ToString(m.Caller.UserId))
|
||||
m.AWSProfile = utils.BuildAWSPath(m.Caller)
|
||||
}
|
||||
//populate region in the filesystems module struct
|
||||
for i, region := range m.AWSRegions {
|
||||
m.Regions[i].Region = region
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] Enumerating filesystems for account %s.\n", cyan(m.output.CallingModule), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s] Supported Services: EFS, FSx \n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] Enumerating filesystems for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s][%s] Supported Services: EFS, FSx \n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
semaphore := make(chan struct{}, m.Goroutines)
|
||||
|
||||
// Create a channel to signal the spinner aka task status goroutine to finish
|
||||
spinnerDone := make(chan bool)
|
||||
@@ -89,7 +91,7 @@ func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirector
|
||||
//execute regional checks
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.executeChecks(region, wg, dataReceiver)
|
||||
m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
@@ -135,11 +137,11 @@ func (m *FilesystemsModule) PrintFilesystems(outputFormat string, outputDirector
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", 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)
|
||||
m.writeLoot(outputDirectory, verbosity)
|
||||
fmt.Printf("[%s] %s filesystems found.\n", cyan(m.output.CallingModule), strconv.Itoa(len(m.output.Body)))
|
||||
m.writeLoot(m.output.FilePath, verbosity)
|
||||
fmt.Printf("[%s][%s] %s filesystems found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
|
||||
} else {
|
||||
fmt.Printf("[%s] No filesystems found, skipping the creation of an output file.\n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] No filesystems found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -157,16 +159,16 @@ func (m *FilesystemsModule) Receiver(receiver chan FilesystemObject, receiverDon
|
||||
}
|
||||
}
|
||||
|
||||
func (m *FilesystemsModule) executeChecks(r string, wg *sync.WaitGroup, dataReceiver chan FilesystemObject) {
|
||||
func (m *FilesystemsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan FilesystemObject) {
|
||||
defer wg.Done()
|
||||
wg.Add(1)
|
||||
go m.getEFSSharesPerRegion(r, wg, dataReceiver)
|
||||
go m.getEFSSharesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
wg.Add(1)
|
||||
go m.getFSxSharesPerRegion(r, wg, dataReceiver)
|
||||
go m.getFSxSharesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
|
||||
func (m *FilesystemsModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
path := filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile, "loot")
|
||||
path := filepath.Join(outputDirectory, "loot")
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
@@ -209,17 +211,17 @@ func (m *FilesystemsModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
fmt.Printf("[%s] Loot written to [%s]\n", cyan(m.output.CallingModule), f)
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), f)
|
||||
if verbosity > 2 {
|
||||
fmt.Println()
|
||||
fmt.Printf("[%s] %s \n", cyan(m.output.CallingModule), green("Use the commands below to try and mount the identified filesystems."))
|
||||
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("Use the commands below to try and mount the identified filesystems."))
|
||||
fmt.Print(out)
|
||||
fmt.Printf("[%s] %s \n\n", cyan(m.output.CallingModule), green("End of loot file."))
|
||||
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("End of loot file."))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *FilesystemsModule) getEFSSharesPerRegion(r string, wg *sync.WaitGroup, dataReceiver chan FilesystemObject) {
|
||||
func (m *FilesystemsModule) getEFSSharesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan FilesystemObject) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
@@ -322,7 +324,7 @@ func (m *FilesystemsModule) getEFSSharesPerRegion(r string, wg *sync.WaitGroup,
|
||||
|
||||
}
|
||||
|
||||
func (m *FilesystemsModule) getFSxSharesPerRegion(r string, wg *sync.WaitGroup, dataReceiver chan FilesystemObject) {
|
||||
func (m *FilesystemsModule) getFSxSharesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan FilesystemObject) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
|
||||
+56
-48
@@ -25,6 +25,7 @@ type IamSimulatorModule struct {
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
|
||||
// Main module data
|
||||
@@ -60,22 +61,16 @@ var (
|
||||
"apprunner:DescribeService",
|
||||
"ec2:DescribeInstanceAttributeInput",
|
||||
}
|
||||
adminActionNames = []string{
|
||||
"iam:PutUserPolicy",
|
||||
"iam:AttachUserPolicy",
|
||||
"iam:PutRolePolicy",
|
||||
"iam:AttachRolePolicy",
|
||||
"secretsmanager:GetSecretValue",
|
||||
"ssm:GetDocument",
|
||||
}
|
||||
TxtLogger = utils.TxtLogger()
|
||||
)
|
||||
|
||||
func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string, resource string, outputFormat string, outputDirectory string, verbosity int) {
|
||||
|
||||
// These stuct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "iam-simulator"
|
||||
m.modLog = utils.TxtLogger.WithFields(logrus.Fields{
|
||||
m.modLog = utils.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
|
||||
@@ -84,9 +79,8 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
|
||||
var pmapperOutFileName string
|
||||
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), aws.ToString(m.Caller.UserId))
|
||||
m.AWSProfile = utils.BuildAWSPath(m.Caller)
|
||||
}
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
// Create a channel to signal the spinner aka task status goroutine to finish
|
||||
spinnerDone := make(chan bool)
|
||||
@@ -104,36 +98,36 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
|
||||
if principal != "" {
|
||||
if action != "" {
|
||||
// The user specified a specific --principal and a specific --action
|
||||
fmt.Printf("[%s] Checking to see if %s can do %s.\n", cyan(m.output.CallingModule), principal, action)
|
||||
fmt.Printf("[%s][%s] Checking to see if %s can do %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), principal, action)
|
||||
m.output.FullFilename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
|
||||
actionList = append(actionList, action)
|
||||
m.getPolicySimulatorResult((&principal), actionList, resource, dataReceiver)
|
||||
|
||||
} else {
|
||||
// The user specified a specific --principal, but --action was empty
|
||||
fmt.Printf("[%s] Checking to see if %s can do any actions of interest.\n", cyan(m.output.CallingModule), principal)
|
||||
fmt.Printf("[%s][%s] Checking to see if %s can do any actions of interest.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), principal)
|
||||
m.output.FullFilename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
|
||||
m.getPolicySimulatorResult((&principal), defaultActionNames, resource, dataReceiver)
|
||||
}
|
||||
} else {
|
||||
if action != "" {
|
||||
// The did not specify a specific --principal, but they did specify an --action
|
||||
fmt.Printf("[%s] Checking to see if any principal can do %s.\n", cyan(m.output.CallingModule), action)
|
||||
fmt.Printf("[%s][%s] Checking to see if any principal can do %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), action)
|
||||
m.output.FullFilename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
|
||||
actionList = append(actionList, action)
|
||||
wg.Add(1)
|
||||
m.getIAMUsers(wg, actionList, resource, dataReceiver)
|
||||
wg.Add(1)
|
||||
m.getIAMRoles(wg, actionList, resource, dataReceiver)
|
||||
pmapperOutFileName = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile, "loot", fmt.Sprintf("pmapper-output-%s.txt", action))
|
||||
pmapperOutFileName = filepath.Join(m.output.FullFilename, "loot", fmt.Sprintf("pmapper-output-%s.txt", action))
|
||||
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper --profile %s query \"who can do %s with %s\" | tee %s\n", m.AWSProfile, action, resource, pmapperOutFileName))
|
||||
} else {
|
||||
// Both --principal and --action are empty. Run in default mode!
|
||||
fmt.Printf("[%s] Running multiple iam-simulator queries for account %s. (This command can be pretty slow, FYI)\n", cyan(m.output.CallingModule), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s][%s] Running multiple iam-simulator queries for account %s. (This command can be pretty slow, FYI)\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
m.output.FullFilename = m.output.CallingModule
|
||||
m.executeChecks(wg, resource, dataReceiver)
|
||||
for _, action := range defaultActionNames {
|
||||
pmapperOutFileName = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile, "loot", fmt.Sprintf("pmapper-output-%s.txt", action))
|
||||
pmapperOutFileName = filepath.Join(m.output.FullFilename, "loot", fmt.Sprintf("pmapper-output-%s.txt", action))
|
||||
pmapperCommands = append(pmapperCommands, fmt.Sprintf("pmapper --profile %s query \"who can do %s with %s\" | tee %s\n", m.AWSProfile, action, resource, pmapperOutFileName))
|
||||
}
|
||||
|
||||
@@ -175,23 +169,23 @@ func (m *IamSimulatorModule) PrintIamSimulator(principal string, action string,
|
||||
|
||||
}
|
||||
if len(m.output.Body) > 0 {
|
||||
//m.output.OutputSelector(outputFormat)
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
|
||||
utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
|
||||
fmt.Printf("[%s] We suggest running the pmapper commands in the loot file to get the same information but taking privesc paths into account.\n", cyan(m.output.CallingModule))
|
||||
// fmt.Printf("[%s]\t\tpmapper --profile %s graph create\n", cyan(m.output.CallingModule), m.AWSProfile)
|
||||
fmt.Printf("[%s][%s] We suggest running the pmapper commands in the loot file to get the same information but taking privesc paths into account.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
// fmt.Printf("[%s]\t\tpmapper --profile %s graph create\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.AWSProfile)
|
||||
// for _, line := range pmapperCommands {
|
||||
// fmt.Printf("[%s]\t\t%s", cyan(m.output.CallingModule), line)
|
||||
// fmt.Printf("[%s]\t\t%s", cyan(m.output.CallingModule), cyan(m.AWSProfile), line)
|
||||
// }
|
||||
m.writeLoot(outputDirectory, verbosity, pmapperCommands)
|
||||
m.writeLoot(m.output.FilePath, verbosity, pmapperCommands)
|
||||
|
||||
} else if principal != "" || action != "" {
|
||||
fmt.Printf("[%s] No allowed permissions identified, skipping the creation of an output file.\n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] No allowed permissions identified, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *IamSimulatorModule) writeLoot(outputDirectory string, verbosity int, pmapperCommands []string) {
|
||||
path := filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile, "loot")
|
||||
path := filepath.Join(outputDirectory, "loot")
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
@@ -212,12 +206,12 @@ func (m *IamSimulatorModule) writeLoot(outputDirectory string, verbosity int, pm
|
||||
|
||||
if verbosity > 2 {
|
||||
fmt.Println()
|
||||
fmt.Printf("[%s] %s \n", cyan(m.output.CallingModule), green("We suggest running these pmapper commands in the loot file to get the same information but taking privesc paths into account."))
|
||||
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("We suggest running these pmapper commands in the loot file to get the same information but taking privesc paths into account."))
|
||||
fmt.Print(out)
|
||||
fmt.Printf("[%s] %s \n\n", cyan(m.output.CallingModule), green("End of loot file."))
|
||||
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("End of loot file."))
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] Loot written to [%s]\n", cyan(m.output.CallingModule), outFile)
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), outFile)
|
||||
|
||||
}
|
||||
|
||||
@@ -252,7 +246,6 @@ func (m *IamSimulatorModule) getIAMUsers(wg *sync.WaitGroup, actions []string, r
|
||||
m.CommandCounter.Executing++
|
||||
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
|
||||
var PaginationControl *string
|
||||
var adminCheckResult bool
|
||||
|
||||
for {
|
||||
ListUsers, err := m.IAMClient.ListUsers(
|
||||
@@ -270,8 +263,16 @@ func (m *IamSimulatorModule) getIAMUsers(wg *sync.WaitGroup, actions []string, r
|
||||
for _, user := range ListUsers.Users {
|
||||
//name := user.UserName
|
||||
principal := user.Arn
|
||||
adminCheckResult = m.policySimulatorAdminCheck(principal, adminActionNames, resource, dataReceiver)
|
||||
if !adminCheckResult {
|
||||
adminCheckResult := m.isPrincipalAnAdmin(principal)
|
||||
if adminCheckResult {
|
||||
query := fmt.Sprintf("Appears to be an administrator")
|
||||
dataReceiver <- SimulatorResult{
|
||||
AWSService: "IAM",
|
||||
Principal: aws.ToString(principal),
|
||||
Query: query,
|
||||
Decision: "",
|
||||
}
|
||||
} else {
|
||||
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
|
||||
}
|
||||
|
||||
@@ -299,7 +300,6 @@ func (m *IamSimulatorModule) getIAMRoles(wg *sync.WaitGroup, actions []string, r
|
||||
m.CommandCounter.Executing++
|
||||
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
|
||||
var PaginationControl *string
|
||||
var adminCheckResult bool
|
||||
|
||||
for {
|
||||
ListRoles, err := m.IAMClient.ListRoles(
|
||||
@@ -317,8 +317,16 @@ func (m *IamSimulatorModule) getIAMRoles(wg *sync.WaitGroup, actions []string, r
|
||||
for _, role := range ListRoles.Roles {
|
||||
//name := user.UserName
|
||||
principal := role.Arn
|
||||
adminCheckResult = m.policySimulatorAdminCheck(principal, adminActionNames, resource, dataReceiver)
|
||||
if !adminCheckResult {
|
||||
adminCheckResult := m.isPrincipalAnAdmin(principal)
|
||||
if adminCheckResult {
|
||||
query := fmt.Sprintf("Appears to be an administrator")
|
||||
dataReceiver <- SimulatorResult{
|
||||
AWSService: "IAM",
|
||||
Principal: aws.ToString(principal),
|
||||
Query: query,
|
||||
Decision: "",
|
||||
}
|
||||
} else {
|
||||
m.getPolicySimulatorResult(principal, actions, resource, dataReceiver)
|
||||
}
|
||||
|
||||
@@ -386,25 +394,34 @@ func (m *IamSimulatorModule) getPolicySimulatorResult(principal *string, actionN
|
||||
}
|
||||
}
|
||||
|
||||
func (m *IamSimulatorModule) policySimulatorAdminCheck(principal *string, actionNames []string, resource string, dataReceiver chan SimulatorResult) bool {
|
||||
func (m *IamSimulatorModule) isPrincipalAnAdmin(principal *string) bool {
|
||||
var PaginationControl2 *string
|
||||
var resourceArns []string
|
||||
resourceArns = append(resourceArns, resource)
|
||||
|
||||
resourceArns = append(resourceArns, "*")
|
||||
var adminActionNames = []string{
|
||||
"iam:PutUserPolicy",
|
||||
"iam:AttachUserPolicy",
|
||||
"iam:PutRolePolicy",
|
||||
"iam:AttachRolePolicy",
|
||||
"secretsmanager:GetSecretValue",
|
||||
"ssm:GetDocument",
|
||||
}
|
||||
for {
|
||||
SimulatePrincipalPolicy, err := m.IAMClient.SimulatePrincipalPolicy(
|
||||
context.TODO(),
|
||||
&iam.SimulatePrincipalPolicyInput{
|
||||
Marker: PaginationControl2,
|
||||
ActionNames: actionNames,
|
||||
ActionNames: adminActionNames,
|
||||
PolicySourceArn: principal,
|
||||
ResourceArns: resourceArns,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
//m.modLog.Error(err.Error())
|
||||
TxtLogger.Println(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
m.modLog.Error(fmt.Sprintf("Failed admin check on %s\n\n", aws.ToString(principal)))
|
||||
//m.modLog.Error(fmt.Sprintf("Failed admin check on %s\n\n", aws.ToString(principal)))
|
||||
TxtLogger.Printf("Failed admin check on %s\n\n", aws.ToString(principal))
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -428,14 +445,5 @@ func (m *IamSimulatorModule) policySimulatorAdminCheck(principal *string, action
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("Appears to be an administrator")
|
||||
dataReceiver <- SimulatorResult{
|
||||
AWSService: "IAM",
|
||||
Principal: aws.ToString(principal),
|
||||
Query: query,
|
||||
Decision: "",
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
+136
-43
@@ -15,18 +15,23 @@ import (
|
||||
"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"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type InstancesModule struct {
|
||||
// General configuration data
|
||||
EC2Client *ec2.Client
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
UserDataAttributesOnly bool
|
||||
AWSProfile string
|
||||
EC2Client *ec2.Client
|
||||
IAMClient *iam.Client
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
UserDataAttributesOnly bool
|
||||
AWSProfile string
|
||||
InstanceProfileToRolesMap map[string][]iamTypes.Role
|
||||
|
||||
// Module's Results
|
||||
MappedInstances []MappedInstance
|
||||
@@ -46,7 +51,9 @@ type MappedInstance struct {
|
||||
ExternalIP string
|
||||
PrivateIP string
|
||||
Profile string
|
||||
Role string
|
||||
Region string
|
||||
Admin string
|
||||
}
|
||||
|
||||
func (m *InstancesModule) Instances(filter string, outputFormat string, outputDirectory string, verbosity int) {
|
||||
@@ -54,23 +61,15 @@ func (m *InstancesModule) Instances(filter string, outputFormat string, outputDi
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "instances"
|
||||
m.modLog = utils.TxtLogger.WithFields(logrus.Fields{
|
||||
m.modLog = utils.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), aws.ToString(m.Caller.UserId))
|
||||
m.AWSProfile = utils.BuildAWSPath(m.Caller)
|
||||
}
|
||||
|
||||
// regions, errtemp := m.EC2Client.DescribeRegions(
|
||||
// context.TODO(),
|
||||
// &ec2.DescribeRegionsInput{})
|
||||
|
||||
// if errtemp != nil {
|
||||
// m.modLog.Error(errtemp.Error())
|
||||
// }
|
||||
// for _, reg := range regions.Regions {
|
||||
// fmt.Println(*reg.RegionName)
|
||||
// }
|
||||
// Populate the instance profile to roles map. You can't use getInstanceProfile by name, so the only way to do this is to
|
||||
// list all of the profiles, save the data, and then do the lookup yourself.
|
||||
|
||||
// Parses the type of filter being used (file with instances or single instance id)
|
||||
var instancesToSearch []string
|
||||
@@ -81,7 +80,7 @@ func (m *InstancesModule) Instances(filter string, outputFormat string, outputDi
|
||||
}
|
||||
|
||||
//Connects to EC2 service and maps instances
|
||||
fmt.Printf("[%s] Enumerating EC2 instances in all regions for account %s\n", cyan(m.output.CallingModule), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s][%s] Enumerating EC2 instances in all regions for account %s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
|
||||
@@ -96,7 +95,7 @@ func (m *InstancesModule) Instances(filter string, outputFormat string, outputDi
|
||||
// Create a channel to signal to stop
|
||||
receiverDone := make(chan bool)
|
||||
go m.Receiver(dataReceiver, receiverDone)
|
||||
|
||||
m.getRolesFromInstanceProfiles()
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.Pending++
|
||||
@@ -142,7 +141,6 @@ func (m *InstancesModule) printInstancesUserDataAttributesOnly(outputFormat stri
|
||||
|
||||
m.output.CallingModule = "instance-userdata"
|
||||
path := filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile, "loot")
|
||||
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
@@ -170,7 +168,8 @@ func (m *InstancesModule) printInstancesUserDataAttributesOnly(outputFormat stri
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(m.MappedInstances) > 0 {
|
||||
// only create a file if if there is at least one instance AND at least one instance had user-data.
|
||||
if (len(m.MappedInstances) > 0) && (userDataOut != "=============================================\n") {
|
||||
if m.output.Verbosity > 1 {
|
||||
fmt.Printf("%s", userDataOut)
|
||||
}
|
||||
@@ -179,7 +178,9 @@ func (m *InstancesModule) printInstancesUserDataAttributesOnly(outputFormat stri
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
fmt.Printf("[%s] Loot written to [%s]\n", cyan(m.output.CallingModule), userDataFileName)
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), userDataFileName)
|
||||
} else {
|
||||
fmt.Printf("[%s][%s] No user data found, skipping the creation of an output file\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,12 +190,14 @@ func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDi
|
||||
m.output.Headers = []string{
|
||||
//"ID",
|
||||
"Name",
|
||||
"Arn",
|
||||
//"Arn",
|
||||
"ID",
|
||||
"Zone",
|
||||
"State",
|
||||
"External IP",
|
||||
"Internal IP",
|
||||
"Profile",
|
||||
"Role",
|
||||
"isAdminRole?",
|
||||
}
|
||||
//Table rows
|
||||
for _, instance := range m.MappedInstances {
|
||||
@@ -204,12 +207,14 @@ func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDi
|
||||
[]string{
|
||||
//instance.ID,
|
||||
instance.Name,
|
||||
instance.Arn,
|
||||
//instance.Arn,
|
||||
instance.ID,
|
||||
instance.AvailabilityZone,
|
||||
instance.State,
|
||||
instance.ExternalIP,
|
||||
instance.PrivateIP,
|
||||
instance.Profile,
|
||||
instance.Role,
|
||||
instance.Admin,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -218,16 +223,16 @@ func (m *InstancesModule) printGeneralInstanceData(outputFormat string, outputDi
|
||||
////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)
|
||||
|
||||
m.writeLoot(outputDirectory)
|
||||
fmt.Printf("[%s] %s instances found.\n", cyan(m.output.CallingModule), strconv.Itoa(len(m.output.Body)))
|
||||
m.writeLoot(m.output.FilePath)
|
||||
fmt.Printf("[%s][%s] %s instances found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
|
||||
} else {
|
||||
fmt.Printf("[%s] No instances found, skipping the creation of an output file.\n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] No instances found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
}
|
||||
|
||||
func (m *InstancesModule) writeLoot(outputDirectory string) {
|
||||
path := filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile, "loot")
|
||||
path := filepath.Join(outputDirectory, "loot")
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
@@ -259,8 +264,8 @@ func (m *InstancesModule) writeLoot(outputDirectory string) {
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] Loot written to [%s]\n", cyan(m.output.CallingModule), privateIPsFilename)
|
||||
fmt.Printf("[%s] Loot written to [%s]\n", cyan(m.output.CallingModule), publicIPsFilename)
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), privateIPsFilename)
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), publicIPsFilename)
|
||||
|
||||
}
|
||||
|
||||
@@ -350,6 +355,9 @@ func (m *InstancesModule) loadInstanceData(instance types.Instance, region strin
|
||||
var profile string
|
||||
var externalIP string
|
||||
var name string = ""
|
||||
var adminRole string = ""
|
||||
localAdminMap := make(map[string]bool)
|
||||
var roleArn string = ""
|
||||
|
||||
// The name is in a tag so we have to do this to grab the value from the right tag
|
||||
for _, tag := range instance.Tags {
|
||||
@@ -359,15 +367,7 @@ func (m *InstancesModule) loadInstanceData(instance types.Instance, region strin
|
||||
|
||||
}
|
||||
|
||||
arn := fmt.Sprintf("arn:aws:ec2:%s:%s:instance/%s", region, aws.ToString(accountId), aws.ToString(instance.InstanceId))
|
||||
|
||||
if instance.IamInstanceProfile == nil {
|
||||
profile = "NoInstanceProfile"
|
||||
} else {
|
||||
// This returns only the role name without the preceding forward slash.
|
||||
profileARN := aws.ToString(instance.IamInstanceProfile.Arn)
|
||||
profile = strings.Split(profileARN, "/")[len(strings.Split(profileARN, "/"))-1]
|
||||
}
|
||||
//arn := fmt.Sprintf("arn:aws:ec2:%s:%s:instance/%s", region, aws.ToString(accountId), aws.ToString(instance.InstanceId))
|
||||
|
||||
if instance.PublicIpAddress == nil {
|
||||
externalIP = "NoExternalIP"
|
||||
@@ -375,16 +375,109 @@ func (m *InstancesModule) loadInstanceData(instance types.Instance, region strin
|
||||
externalIP = aws.ToString(instance.PublicIpAddress)
|
||||
}
|
||||
|
||||
if instance.IamInstanceProfile == nil {
|
||||
profile = "NoInstanceProfile"
|
||||
} else {
|
||||
// This returns only the role name without the preceding forward slash.
|
||||
profileARN := aws.ToString(instance.IamInstanceProfile.Arn)
|
||||
profileID := aws.ToString(instance.IamInstanceProfile.Id)
|
||||
profile = strings.Split(profileARN, "/")[len(strings.Split(profileARN, "/"))-1]
|
||||
|
||||
if roles, ok := m.InstanceProfileToRolesMap[profileID]; ok {
|
||||
for _, role := range roles {
|
||||
roleArn = aws.ToString(role.Arn)
|
||||
if role.Arn != nil {
|
||||
// If we've seen the function before, skip the isRoleAdmin function and just pull the value from the localAdminMap
|
||||
roleArn := aws.ToString(role.Arn)
|
||||
if val, ok := localAdminMap[roleArn]; ok {
|
||||
if val {
|
||||
// we've seen it before and it's an admin
|
||||
adminRole = "YES"
|
||||
} else {
|
||||
// we've seen it before and it's NOT an admin
|
||||
adminRole = "No"
|
||||
}
|
||||
} else {
|
||||
isRoleAdmin := m.isRoleAdmin(role.Arn)
|
||||
if isRoleAdmin {
|
||||
adminRole = "YES"
|
||||
localAdminMap[roleArn] = true
|
||||
} else {
|
||||
adminRole = "No"
|
||||
localAdminMap[roleArn] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
dataReceiver <- MappedInstance{
|
||||
aws.ToString(instance.InstanceId),
|
||||
aws.ToString(&name),
|
||||
arn,
|
||||
//arn,
|
||||
aws.ToString(instance.InstanceId),
|
||||
aws.ToString(instance.Placement.AvailabilityZone),
|
||||
string(instance.State.Name),
|
||||
externalIP,
|
||||
aws.ToString(instance.PrivateIpAddress),
|
||||
profile,
|
||||
roleArn,
|
||||
region,
|
||||
adminRole,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *InstancesModule) isRoleAdmin(principal *string) bool {
|
||||
iamSimMod := IamSimulatorModule{
|
||||
IAMClient: m.IAMClient,
|
||||
Caller: m.Caller,
|
||||
AWSProfile: m.AWSProfile,
|
||||
Goroutines: m.Goroutines,
|
||||
}
|
||||
|
||||
adminCheckResult := iamSimMod.isPrincipalAnAdmin(principal)
|
||||
|
||||
if adminCheckResult {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *InstancesModule) getRolesFromInstanceProfiles() {
|
||||
|
||||
// The "PaginationControl" value is nil when there's no more data to return.
|
||||
var PaginationMarker *string
|
||||
PaginationControl := true
|
||||
m.InstanceProfileToRolesMap = map[string][]iamTypes.Role{}
|
||||
|
||||
for PaginationControl {
|
||||
ListInstanceProfiles, err := m.IAMClient.ListInstanceProfiles(
|
||||
context.TODO(),
|
||||
&(iam.ListInstanceProfilesInput{
|
||||
Marker: PaginationMarker,
|
||||
}),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
for _, instanceProfile := range ListInstanceProfiles.InstanceProfiles {
|
||||
m.InstanceProfileToRolesMap[aws.ToString(instanceProfile.InstanceProfileId)] = instanceProfile.Roles
|
||||
}
|
||||
if aws.ToString(ListInstanceProfiles.Marker) != "" {
|
||||
PaginationMarker = ListInstanceProfiles.Marker
|
||||
} else {
|
||||
PaginationMarker = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// next step - debug and watch localAdminmap to see if it's working like it does on lambda
|
||||
}
|
||||
+292
-12
@@ -16,11 +16,13 @@ import (
|
||||
"github.com/aws/aws-sdk-go-v2/service/apprunner"
|
||||
"github.com/aws/aws-sdk-go-v2/service/cloudformation"
|
||||
"github.com/aws/aws-sdk-go-v2/service/cloudfront"
|
||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
|
||||
"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/eks"
|
||||
"github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing"
|
||||
"github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2"
|
||||
"github.com/aws/aws-sdk-go-v2/service/glue"
|
||||
"github.com/aws/aws-sdk-go-v2/service/grafana"
|
||||
"github.com/aws/aws-sdk-go-v2/service/iam"
|
||||
"github.com/aws/aws-sdk-go-v2/service/lambda"
|
||||
@@ -31,6 +33,8 @@ import (
|
||||
"github.com/aws/aws-sdk-go-v2/service/redshift"
|
||||
"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/ssm"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -59,10 +63,15 @@ type Inventory2Module struct {
|
||||
CloudfrontClient *cloudfront.Client
|
||||
AppRunnerClient *apprunner.Client
|
||||
LightsailClient *lightsail.Client
|
||||
GlueClient *glue.Client
|
||||
SNSClient *sns.Client
|
||||
SQSClient *sqs.Client
|
||||
DynamoDBClient *dynamodb.Client
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
|
||||
// Main module data
|
||||
@@ -92,17 +101,17 @@ func (m *Inventory2Module) PrintInventoryPerRegion(outputFormat string, outputDi
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "inventory"
|
||||
m.modLog = utils.TxtLogger.WithFields(logrus.Fields{
|
||||
m.modLog = utils.TxtLog.WithFields(logrus.Fields{
|
||||
"module": "inventory",
|
||||
},
|
||||
)
|
||||
// def change this to build dynamically in the future.
|
||||
m.services = []string{"total", "APIGateway RestAPIs", "APIGatewayv2 APIs", "AppRunner Services", "CloudFormation Stacks", "Cloudfront Distributions", "EC2 Instances", "ECS Tasks", "EKS Clusters", "ELB Load Balancers", "ELBv2 Load Balancers", "Grafana Workspaces", "Lambda Functions", "Lightsail Instances/Containers", "MQ Brokers", "OpenSearch DomainNames", "RDS DB Instances", "SecretsManager Secrets", "SSM Parameters"}
|
||||
m.services = []string{"total", "APIGateway RestAPIs", "APIGatewayv2 APIs", "AppRunner Services", "CloudFormation Stacks", "Cloudfront Distributions", "DynamoDB Tables", "EC2 Instances", "ECS Tasks", "EKS Clusters", "ELB Load Balancers", "ELBv2 Load Balancers", "Glue Dev Endpoints", "Glue Jobs", "Grafana Workspaces", "Lambda Functions", "Lightsail Instances/Containers", "MQ Brokers", "OpenSearch DomainNames", "RDS DB Instances", "SecretsManager Secrets", "SNS Topics", "SQS Queues", "SSM Parameters"}
|
||||
m.serviceMap = map[string]map[string]int{}
|
||||
m.totalRegionCounts = map[string]int{}
|
||||
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), aws.ToString(m.Caller.UserId))
|
||||
m.AWSProfile = utils.BuildAWSPath(m.Caller)
|
||||
}
|
||||
|
||||
//initialize servicemap and total
|
||||
@@ -114,12 +123,13 @@ func (m *Inventory2Module) PrintInventoryPerRegion(outputFormat string, outputDi
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] Enumerating selected services in all regions for account %s.\n", cyan(m.output.CallingModule), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s] Supported Services: ApiGateway, ApiGatewayv2, AppRunner, CloudFormation, Cloudfront, EC2, ECS, EKS, \n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s] \t\t\tELB, ELBv2, Grafana, IAM, Lambda, Lightsail, MQ, OpenSearch, RDS, S3, SecretsManager, SSM\n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] Enumerating selected services in all regions for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s][%s] Supported Services: ApiGateway, ApiGatewayv2, AppRunner, CloudFormation, Cloudfront, DynamoDB, \n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
fmt.Printf("[%s][%s] \t\t\tEC2, ECS, EKS, ELB, ELBv2, Glue, Grafana, IAM, Lambda, Lightsail, MQ, \n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
fmt.Printf("[%s][%s] \t\t\tOpenSearch, RDS, S3, SecretsManager, SNS, SQS, SSM\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
semaphore := make(chan struct{}, 50)
|
||||
semaphore := make(chan struct{}, m.Goroutines)
|
||||
|
||||
// Create a channel to signal the spinner aka task status goroutine to finish
|
||||
spinnerDone := make(chan bool)
|
||||
@@ -248,7 +258,7 @@ func (m *Inventory2Module) PrintInventoryPerRegion(outputFormat string, outputDi
|
||||
m.PrintGlobalResources(outputFormat, outputDirectory, verbosity, dataReceiver)
|
||||
m.PrintTotalResources(outputFormat)
|
||||
} else {
|
||||
fmt.Printf("[%s] No resources identified, skipping the creation of an output file.\n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] No resources identified, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
|
||||
// Send a message to the data receiver goroutine to close the channel and stop
|
||||
@@ -269,8 +279,6 @@ func (m *Inventory2Module) PrintGlobalResources(outputFormat string, outputDirec
|
||||
//m.globalOutput.CallingModule = fmt.Sprintf("%s-global", m.globalOutput.CallingModule)
|
||||
m.globalOutput.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
|
||||
|
||||
|
||||
|
||||
m.globalOutput.Headers = []string{
|
||||
"Resource Type",
|
||||
"Total",
|
||||
@@ -377,6 +385,25 @@ func (m *Inventory2Module) executeChecks(r string, wg *sync.WaitGroup, semaphore
|
||||
wg.Add(1)
|
||||
go m.getSSMParametersPerRegion(r, wg, semaphore)
|
||||
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getGlueDevEndpointsPerRegion(r, wg, semaphore)
|
||||
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getGlueJobsPerRegion(r, wg, semaphore)
|
||||
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getSNSTopicsPerRegion(r, wg, semaphore)
|
||||
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getSQSQueuesPerRegion(r, wg, semaphore)
|
||||
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getDynamoDBTablesPerRegion(r, wg, semaphore)
|
||||
}
|
||||
|
||||
func (m *Inventory2Module) getLambdaFunctionsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}) {
|
||||
@@ -1321,6 +1348,259 @@ func (m *Inventory2Module) getEcsTasksPerRegion(r string, wg *sync.WaitGroup, se
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Inventory2Module) getGlueDevEndpointsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}) {
|
||||
// Don't use this method as a template for future ones. There is a one off in the way the NextToken is handled.
|
||||
defer func() {
|
||||
wg.Done()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
var totalCountThisServiceThisRegion = 0
|
||||
var service = "Glue Dev Endpoints"
|
||||
var PaginationControl *string
|
||||
|
||||
for {
|
||||
ListDevEndpoints, err := m.GlueClient.ListDevEndpoints(
|
||||
context.TODO(),
|
||||
&(glue.ListDevEndpointsInput{
|
||||
NextToken: PaginationControl,
|
||||
}),
|
||||
func(o *glue.Options) {
|
||||
o.Region = r
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
// Add this page of resources to the total count
|
||||
totalCountThisServiceThisRegion = totalCountThisServiceThisRegion + len(ListDevEndpoints.DevEndpointNames)
|
||||
|
||||
// This next line is non-standard. For some reason this next token is an empty string instead of nil, so
|
||||
// as a result we had to change the comparison.
|
||||
if aws.ToString(ListDevEndpoints.NextToken) != "" {
|
||||
PaginationControl = ListDevEndpoints.NextToken
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
m.mu.Lock()
|
||||
m.serviceMap[service][r] = totalCountThisServiceThisRegion
|
||||
m.totalRegionCounts[r] = m.totalRegionCounts[r] + totalCountThisServiceThisRegion
|
||||
m.serviceMap["total"][r] = m.serviceMap["total"][r] + totalCountThisServiceThisRegion
|
||||
m.mu.Unlock()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Inventory2Module) getGlueJobsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}) {
|
||||
defer func() {
|
||||
wg.Done()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
|
||||
var PaginationControl *string
|
||||
var totalCountThisServiceThisRegion = 0
|
||||
var service = "Glue Jobs"
|
||||
|
||||
for {
|
||||
m.modLog.Info(fmt.Sprintf("Getting jobs %v\n", PaginationControl))
|
||||
ListJobs, err := m.GlueClient.ListJobs(
|
||||
context.TODO(),
|
||||
&(glue.ListJobsInput{
|
||||
NextToken: PaginationControl,
|
||||
}),
|
||||
func(o *glue.Options) {
|
||||
o.Region = r
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
// Add this page of resources to the total count
|
||||
totalCountThisServiceThisRegion = totalCountThisServiceThisRegion + len(ListJobs.JobNames)
|
||||
|
||||
// The "NextToken" value is nil when there's no more data to return.
|
||||
if ListJobs.NextToken != nil {
|
||||
PaginationControl = ListJobs.NextToken
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
// No more pages, update the module's service map
|
||||
m.mu.Lock()
|
||||
m.serviceMap[service][r] = totalCountThisServiceThisRegion
|
||||
m.totalRegionCounts[r] = m.totalRegionCounts[r] + totalCountThisServiceThisRegion
|
||||
m.serviceMap["total"][r] = m.serviceMap["total"][r] + totalCountThisServiceThisRegion
|
||||
m.mu.Unlock()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Inventory2Module) getSNSTopicsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}) {
|
||||
defer func() {
|
||||
wg.Done()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
|
||||
var PaginationControl *string
|
||||
var totalCountThisServiceThisRegion = 0
|
||||
var service = "SNS Topics"
|
||||
|
||||
for {
|
||||
ListTopics, err := m.SNSClient.ListTopics(
|
||||
context.TODO(),
|
||||
&(sns.ListTopicsInput{
|
||||
NextToken: PaginationControl,
|
||||
}),
|
||||
func(o *sns.Options) {
|
||||
o.Region = r
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
// Add this page of resources to the total count
|
||||
totalCountThisServiceThisRegion = totalCountThisServiceThisRegion + len(ListTopics.Topics)
|
||||
|
||||
// The "NextToken" value is nil when there's no more data to return.
|
||||
if ListTopics.NextToken != nil {
|
||||
PaginationControl = ListTopics.NextToken
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
// No more pages, update the module's service map
|
||||
m.mu.Lock()
|
||||
m.serviceMap[service][r] = totalCountThisServiceThisRegion
|
||||
m.totalRegionCounts[r] = m.totalRegionCounts[r] + totalCountThisServiceThisRegion
|
||||
m.serviceMap["total"][r] = m.serviceMap["total"][r] + totalCountThisServiceThisRegion
|
||||
m.mu.Unlock()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Inventory2Module) getSQSQueuesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}) {
|
||||
defer func() {
|
||||
wg.Done()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
|
||||
var PaginationControl *string
|
||||
var totalCountThisServiceThisRegion = 0
|
||||
var service = "SQS Queues"
|
||||
|
||||
for {
|
||||
ListQueues, err := m.SQSClient.ListQueues(
|
||||
context.TODO(),
|
||||
&(sqs.ListQueuesInput{
|
||||
NextToken: PaginationControl,
|
||||
}),
|
||||
func(o *sqs.Options) {
|
||||
o.Region = r
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
// Add this page of resources to the total count
|
||||
totalCountThisServiceThisRegion = totalCountThisServiceThisRegion + len(ListQueues.QueueUrls)
|
||||
|
||||
// The "NextToken" value is nil when there's no more data to return.
|
||||
if ListQueues.NextToken != nil {
|
||||
PaginationControl = ListQueues.NextToken
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
// No more pages, update the module's service map
|
||||
m.mu.Lock()
|
||||
m.serviceMap[service][r] = totalCountThisServiceThisRegion
|
||||
m.totalRegionCounts[r] = m.totalRegionCounts[r] + totalCountThisServiceThisRegion
|
||||
m.serviceMap["total"][r] = m.serviceMap["total"][r] + totalCountThisServiceThisRegion
|
||||
m.mu.Unlock()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Inventory2Module) getDynamoDBTablesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}) {
|
||||
defer func() {
|
||||
wg.Done()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
var totalCountThisServiceThisRegion = 0
|
||||
var service = "DynamoDB Tables"
|
||||
|
||||
ListTables, err := m.DynamoDBClient.ListTables(
|
||||
context.TODO(),
|
||||
&(dynamodb.ListTablesInput{}),
|
||||
func(o *dynamodb.Options) {
|
||||
o.Region = r
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
// Add this page of resources to the total count
|
||||
totalCountThisServiceThisRegion = totalCountThisServiceThisRegion + len(ListTables.TableNames)
|
||||
|
||||
// No more pages, update the module's service map
|
||||
m.mu.Lock()
|
||||
m.serviceMap[service][r] = totalCountThisServiceThisRegion
|
||||
m.totalRegionCounts[r] = m.totalRegionCounts[r] + totalCountThisServiceThisRegion
|
||||
m.serviceMap["total"][r] = m.serviceMap["total"][r] + totalCountThisServiceThisRegion
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// Global Resources
|
||||
|
||||
func (m *Inventory2Module) getBuckets(verbosity int, dataReceiver chan GlobalResourceCount2) {
|
||||
@@ -1468,8 +1748,8 @@ func (m *Inventory2Module) PrintTotalResources(outputFormat string) {
|
||||
}
|
||||
}
|
||||
|
||||
for i, _ := range m.GlobalResourceCounts {
|
||||
for i := range m.GlobalResourceCounts {
|
||||
totalResources = totalResources + m.GlobalResourceCounts[i].count
|
||||
}
|
||||
fmt.Printf("[%s] %d resources enumerated in the services we looked at. This is NOT the total number of resources in the account.\n", cyan(m.output.CallingModule), totalResources)
|
||||
fmt.Printf("[%s][%s] %d resources found in the services we looked at. This is NOT the total number of resources in the account.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), totalResources)
|
||||
}
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/BishopFox/cloudfox/console"
|
||||
"github.com/BishopFox/cloudfox/utils"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/iam"
|
||||
"github.com/aws/aws-sdk-go-v2/service/lambda"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type LambdasModule struct {
|
||||
// General configuration data
|
||||
LambdaClient *lambda.Client
|
||||
IAMClient *iam.Client
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
|
||||
// Main module data
|
||||
Lambdas []Lambda
|
||||
CommandCounter console.CommandCounter
|
||||
// Used to store output data for pretty printing
|
||||
output utils.OutputData2
|
||||
modLog *logrus.Entry
|
||||
}
|
||||
|
||||
type Lambda struct {
|
||||
AWSService string
|
||||
Region string
|
||||
Type string
|
||||
Name string
|
||||
Role string
|
||||
Admin string
|
||||
Public string
|
||||
}
|
||||
|
||||
func (m *LambdasModule) PrintLambdas(outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These stuct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "lambdas"
|
||||
m.modLog = utils.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = utils.BuildAWSPath(m.Caller)
|
||||
}
|
||||
|
||||
fmt.Printf("[%s][%s] Enumerating lambdas for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
semaphore := make(chan struct{}, m.Goroutines)
|
||||
|
||||
// Create a channel to signal the spinner aka task status goroutine to finish
|
||||
spinnerDone := make(chan bool)
|
||||
//fire up the the task status spinner/updated
|
||||
go console.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "regions")
|
||||
|
||||
//create a channel to receive the objects
|
||||
dataReceiver := make(chan Lambda)
|
||||
|
||||
// Create a channel to signal to stop
|
||||
receiverDone := make(chan bool)
|
||||
go m.Receiver(dataReceiver, receiverDone)
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
// Send a message to the spinner goroutine to close the channel and stop
|
||||
spinnerDone <- true
|
||||
<-spinnerDone
|
||||
// Send a message to the data receiver goroutine to close the channel and stop
|
||||
receiverDone <- true
|
||||
<-receiverDone
|
||||
|
||||
// add - if struct is not empty do this. otherwise, dont write anything.
|
||||
m.output.Headers = []string{
|
||||
"Service",
|
||||
"Region",
|
||||
//"Type",
|
||||
"Resource Arn",
|
||||
"Role",
|
||||
"isAdminRole?",
|
||||
//"Public",
|
||||
}
|
||||
|
||||
sort.Slice(m.Lambdas, func(i, j int) bool {
|
||||
return m.Lambdas[i].AWSService < m.Lambdas[j].AWSService
|
||||
})
|
||||
|
||||
// Table rows
|
||||
for i := range m.Lambdas {
|
||||
|
||||
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].Public,
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
if len(m.output.Body) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
|
||||
utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
|
||||
m.writeLoot(m.output.FilePath, verbosity)
|
||||
fmt.Printf("[%s][%s] %s lambdas found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
} else {
|
||||
fmt.Printf("[%s][%s] No lambdas found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *LambdasModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Lambda) {
|
||||
defer wg.Done()
|
||||
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getLambdasPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
|
||||
func (m *LambdasModule) Receiver(receiver chan Lambda, receiverDone chan bool) {
|
||||
defer close(receiverDone)
|
||||
for {
|
||||
select {
|
||||
case data := <-receiver:
|
||||
m.Lambdas = append(m.Lambdas, data)
|
||||
case <-receiverDone:
|
||||
receiverDone <- true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *LambdasModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
path := filepath.Join(outputDirectory, "loot")
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
pullFile := filepath.Join(path, "lambda-get-function-commands.txt")
|
||||
|
||||
var out string
|
||||
out = out + fmt.Sprintln("#############################################")
|
||||
out = out + fmt.Sprintln("# The profile you will use to perform these commands is most likely not the profile you used to run CloudFox")
|
||||
out = out + fmt.Sprintln("# Set the $profile environment variable to the profile you are going to use to inspect the buckets.")
|
||||
out = out + fmt.Sprintln("# E.g., export profile=dev-prod.")
|
||||
out = out + fmt.Sprintln("#############################################")
|
||||
out = out + fmt.Sprintln("")
|
||||
|
||||
for _, function := range m.Lambdas {
|
||||
out = out + fmt.Sprintf("=============================================\n")
|
||||
out = out + fmt.Sprintf("# Lambda Name: %s\n\n", function.Name)
|
||||
out = out + "# Get function metadata including download location\n"
|
||||
out = out + fmt.Sprintf("aws --profile $profile --region %s lambda get-function --function-name %s\n", function.Name)
|
||||
out = out + "# Download function code to to disk (requires jq and curl) \n"
|
||||
out = out + fmt.Sprintf("mkdir -p ./lambdas/%s\n", function.Name)
|
||||
out = out + fmt.Sprintf("url=`aws --profile $profile lambda get-function --region %s --function-name %s | jq .Code.Location | sed s/\"//g` && curl \"$url\" -o ./lambdas/%s.zip\n", function.Region, function.Name, function.Name)
|
||||
|
||||
}
|
||||
err = os.WriteFile(pullFile, []byte(out), 0644)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
if verbosity > 2 {
|
||||
fmt.Println()
|
||||
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("Beginning of loot file."))
|
||||
|
||||
fmt.Print(out)
|
||||
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("End of loot file."))
|
||||
}
|
||||
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), pullFile)
|
||||
|
||||
}
|
||||
|
||||
func (m *LambdasModule) getLambdasPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Lambda) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
|
||||
var PaginationControl *string
|
||||
var adminRole string = ""
|
||||
localAdminMap := make(map[string]bool)
|
||||
|
||||
for {
|
||||
ListFunctions, err := m.LambdaClient.ListFunctions(
|
||||
context.TODO(),
|
||||
&lambda.ListFunctionsInput{
|
||||
Marker: PaginationControl,
|
||||
},
|
||||
func(o *lambda.Options) {
|
||||
o.Region = r
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
//var parsedArn types.Arn
|
||||
for _, function := range ListFunctions.Functions {
|
||||
//arn := aws.ToString(function.FunctionArn)
|
||||
name := aws.ToString(function.FunctionName)
|
||||
role := aws.ToString(function.Role)
|
||||
if function.Role != nil {
|
||||
// If we've seen the function before, skip the isRoleAdmin function and just pull the value from the localAdminMap
|
||||
if val, ok := localAdminMap[role]; ok {
|
||||
if val {
|
||||
// we've seen it before and it's an admin
|
||||
adminRole = "YES"
|
||||
} else {
|
||||
// we've seen it before and it's NOT an admin
|
||||
adminRole = "No"
|
||||
}
|
||||
} else {
|
||||
isRoleAdmin := m.isRoleAdmin(function.Role)
|
||||
if isRoleAdmin {
|
||||
adminRole = "YES"
|
||||
localAdminMap[role] = true
|
||||
} else {
|
||||
adminRole = "No"
|
||||
localAdminMap[role] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dataReceiver <- Lambda{
|
||||
AWSService: "Lambda",
|
||||
Name: name,
|
||||
Region: r,
|
||||
Type: "",
|
||||
Role: role,
|
||||
Admin: adminRole,
|
||||
Public: "",
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if aws.ToString(ListFunctions.NextMarker) != "" {
|
||||
PaginationControl = ListFunctions.NextMarker
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *LambdasModule) isRoleAdmin(principal *string) bool {
|
||||
iamSimMod := IamSimulatorModule{
|
||||
IAMClient: m.IAMClient,
|
||||
Caller: m.Caller,
|
||||
AWSProfile: m.AWSProfile,
|
||||
Goroutines: m.Goroutines,
|
||||
}
|
||||
|
||||
adminCheckResult := iamSimMod.isPrincipalAnAdmin(principal)
|
||||
|
||||
if adminCheckResult {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,6 +25,7 @@ type OutboundAssumedRolesModule struct {
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
|
||||
// Main module data
|
||||
@@ -117,19 +118,19 @@ func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFor
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "outbound-assumed-roles"
|
||||
m.modLog = utils.TxtLogger.WithFields(logrus.Fields{
|
||||
m.modLog = utils.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), aws.ToString(m.Caller.UserId))
|
||||
m.AWSProfile = utils.BuildAWSPath(m.Caller)
|
||||
}
|
||||
|
||||
m.Days = days
|
||||
|
||||
fmt.Printf("[%s] Enumerating outbound assumed role entries in cloudtrail for account %s.\n", cyan(m.output.CallingModule), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s] Going back through %d days of cloudtrail events. (This command can be pretty slow, FYI)\n", cyan(m.output.CallingModule), days)
|
||||
fmt.Printf("[%s][%s] Enumerating outbound assumed role entries in cloudtrail for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s][%s] Going back through %d days of cloudtrail events. (This command can be pretty slow, FYI)\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), days)
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
semaphore := make(chan struct{}, m.Goroutines)
|
||||
|
||||
// Create a channel to signal the spinner aka task status goroutine to finish
|
||||
spinnerDone := make(chan bool)
|
||||
@@ -146,7 +147,7 @@ func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFor
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, dataReceiver)
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
|
||||
}
|
||||
|
||||
@@ -191,11 +192,11 @@ func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputFor
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", 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)
|
||||
fmt.Printf("[%s] %s log entries found.\n", cyan(m.output.CallingModule), strconv.Itoa(len(m.output.Body)))
|
||||
fmt.Printf("[%s][%s] %s log entries found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
|
||||
//m.writeLoot()
|
||||
} else {
|
||||
fmt.Printf("[%s] No matching log entries found, skipping the creation of an output file.\n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] No matching log entries found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -213,17 +214,25 @@ func (m *OutboundAssumedRolesModule) Receiver(receiver chan OutboundAssumeRoleEn
|
||||
}
|
||||
}
|
||||
|
||||
func (m *OutboundAssumedRolesModule) executeChecks(r string, wg *sync.WaitGroup, dataReceiver chan OutboundAssumeRoleEntry) {
|
||||
func (m *OutboundAssumedRolesModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan OutboundAssumeRoleEntry) {
|
||||
defer wg.Done()
|
||||
wg.Add(1)
|
||||
m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
m.getAssumeRoleLogEntriesPerRegion(r, dataReceiver)
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
m.getAssumeRoleLogEntriesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
|
||||
}
|
||||
|
||||
func (m *OutboundAssumedRolesModule) getAssumeRoleLogEntriesPerRegion(r string, dataReceiver chan OutboundAssumeRoleEntry) {
|
||||
func (m *OutboundAssumedRolesModule) getAssumeRoleLogEntriesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan OutboundAssumeRoleEntry) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
|
||||
var PaginationControl *string
|
||||
//var LookupAttributes []types.LookupAttributes
|
||||
|
||||
+6
-5
@@ -26,6 +26,7 @@ type IamPermissionsModule struct {
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
|
||||
// Main module data
|
||||
@@ -87,14 +88,14 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "permissions"
|
||||
m.output.FullFilename = m.output.CallingModule
|
||||
m.modLog = utils.TxtLogger.WithFields(logrus.Fields{
|
||||
m.modLog = utils.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), aws.ToString(m.Caller.UserId))
|
||||
m.AWSProfile = utils.BuildAWSPath(m.Caller)
|
||||
}
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
|
||||
fmt.Printf("[%s] Enumerating IAM permissions for account %s.\n", cyan(m.output.CallingModule), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s][%s] Enumerating IAM permissions for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
|
||||
if principal != "" {
|
||||
m.output.FullFilename = filepath.Join(fmt.Sprintf("%s-custom-%s", m.output.CallingModule, strconv.FormatInt((time.Now().Unix()), 10)))
|
||||
@@ -139,10 +140,10 @@ func (m *IamPermissionsModule) PrintIamPermissions(outputFormat string, outputDi
|
||||
//m.output.OutputSelector3(outputFormat)
|
||||
utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.FullFilename, m.output.CallingModule)
|
||||
|
||||
fmt.Printf("[%s] %s unique permissions identified.\n", cyan(m.output.CallingModule), strconv.Itoa(len(m.output.Body)))
|
||||
fmt.Printf("[%s][%s] %s unique permissions identified.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
|
||||
} else {
|
||||
fmt.Printf("[%s] No IAM permissions found. skipping the creation of an output file.\n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] No IAM permissions found. skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-5
@@ -22,6 +22,7 @@ type IamPrincipalsModule struct {
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
|
||||
// Main module data
|
||||
@@ -67,14 +68,14 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDire
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "principals"
|
||||
m.modLog = utils.TxtLogger.WithFields(logrus.Fields{
|
||||
m.modLog = utils.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), aws.ToString(m.Caller.UserId))
|
||||
m.AWSProfile = utils.BuildAWSPath(m.Caller)
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] Enumerating IAM Users and Roles for account %s.\n", cyan(m.output.CallingModule), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s][%s] Enumerating IAM Users and Roles for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
|
||||
// wg := new(sync.WaitGroup)
|
||||
|
||||
@@ -138,10 +139,10 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputFormat string, outputDire
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", 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)
|
||||
fmt.Printf("[%s] %s IAM principals found.\n", cyan(m.output.CallingModule), strconv.Itoa(len(m.output.Body)))
|
||||
fmt.Printf("[%s][%s] %s IAM principals found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
|
||||
} else {
|
||||
fmt.Printf("[%s] No IAM principals found, skipping the creation of an output file.\n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] No IAM principals found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+93
-44
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/BishopFox/cloudfox/utils"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ram"
|
||||
ramTypes "github.com/aws/aws-sdk-go-v2/service/ram/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -22,6 +23,7 @@ type RAMModule struct {
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
|
||||
// Main module data
|
||||
@@ -37,6 +39,8 @@ type Resource struct {
|
||||
Region string
|
||||
Name string
|
||||
Owner string
|
||||
Type string
|
||||
ShareType string
|
||||
}
|
||||
|
||||
func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbosity int) {
|
||||
@@ -44,14 +48,14 @@ func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbos
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "ram"
|
||||
m.modLog = utils.TxtLogger.WithFields(logrus.Fields{
|
||||
m.modLog = utils.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), aws.ToString(m.Caller.UserId))
|
||||
m.AWSProfile = utils.BuildAWSPath(m.Caller)
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] Enumerating shared resources for account %s.\n", cyan(m.output.CallingModule), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s][%s] Enumerating shared resources for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
|
||||
@@ -86,11 +90,10 @@ func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbos
|
||||
m.output.Headers = []string{
|
||||
"Service",
|
||||
"Region",
|
||||
"Name",
|
||||
"URI",
|
||||
"PushedAt",
|
||||
"ImageTags",
|
||||
"ImageSize",
|
||||
"Share Name",
|
||||
"Type",
|
||||
"Owner",
|
||||
"Share Type",
|
||||
}
|
||||
|
||||
// Table rows
|
||||
@@ -101,7 +104,9 @@ func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbos
|
||||
m.Resources[i].AWSService,
|
||||
m.Resources[i].Region,
|
||||
m.Resources[i].Name,
|
||||
m.Resources[i].Type,
|
||||
m.Resources[i].Owner,
|
||||
m.Resources[i].ShareType,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -110,9 +115,9 @@ func (m *RAMModule) PrintRAM(outputFormat string, outputDirectory string, verbos
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", 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)
|
||||
fmt.Printf("[%s] %s resources found.\n", cyan(m.output.CallingModule), strconv.Itoa(len(m.output.Body)))
|
||||
fmt.Printf("[%s][%s] %s resources found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
} else {
|
||||
fmt.Printf("[%s] No resources found, skipping the creation of an output file.\n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] No resources found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -143,44 +148,88 @@ func (m *RAMModule) Receiver(receiver chan Resource, receiverDone chan bool) {
|
||||
func (m *RAMModule) getRAMResourcesPerRegion(r string, dataReceiver chan Resource) {
|
||||
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
|
||||
var PaginationControl *string
|
||||
var PaginationControl2 *string
|
||||
var shareName string
|
||||
var resourceType string
|
||||
shareTypes := []ramTypes.ResourceOwner{"SELF", "OTHER-ACCOUNTS"}
|
||||
|
||||
for {
|
||||
GetResourceShares, err := m.RAMClient.GetResourceShares(
|
||||
context.TODO(),
|
||||
&ram.GetResourceSharesInput{
|
||||
NextToken: PaginationControl,
|
||||
},
|
||||
func(o *ram.Options) {
|
||||
o.Region = r
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
for _, resource := range GetResourceShares.ResourceShares {
|
||||
name := aws.ToString(resource.Name)
|
||||
ownerID := aws.ToString(resource.OwningAccountId)
|
||||
|
||||
dataReceiver <- Resource{
|
||||
AWSService: "RAM",
|
||||
Name: name,
|
||||
Region: r,
|
||||
Owner: ownerID,
|
||||
for _, shareType := range shareTypes {
|
||||
for {
|
||||
GetResourceShares, err := m.RAMClient.GetResourceShares(
|
||||
context.TODO(),
|
||||
&ram.GetResourceSharesInput{
|
||||
NextToken: PaginationControl,
|
||||
ResourceOwner: shareType,
|
||||
},
|
||||
func(o *ram.Options) {
|
||||
o.Region = r
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
// }
|
||||
break
|
||||
}
|
||||
for _, resourceShare := range GetResourceShares.ResourceShares {
|
||||
var resourceShareArns []string
|
||||
shareName = aws.ToString(resourceShare.Name)
|
||||
resourceShareArns = append(resourceShareArns, aws.ToString(resourceShare.ResourceShareArn))
|
||||
ownerID := aws.ToString(resourceShare.OwningAccountId)
|
||||
|
||||
// The "NextToken" value is nil when there's no more data to return.
|
||||
if GetResourceShares.NextToken != nil {
|
||||
PaginationControl = GetResourceShares.NextToken
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
break
|
||||
for {
|
||||
ListResources, err := m.RAMClient.ListResources(
|
||||
context.TODO(),
|
||||
&ram.ListResourcesInput{
|
||||
NextToken: PaginationControl2,
|
||||
ResourceOwner: shareType,
|
||||
ResourceShareArns: resourceShareArns,
|
||||
},
|
||||
func(o *ram.Options) {
|
||||
o.Region = r
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
for _, resource := range ListResources.Resources {
|
||||
resourceType = aws.ToString(resource.Type)
|
||||
var shareDirection string
|
||||
if string(shareType) == "OTHER-ACCOUNTS" {
|
||||
shareDirection = "Inbound share (Another account shared this with me)"
|
||||
} else {
|
||||
shareDirection = "Outbound share (I've shared this resource with others)"
|
||||
}
|
||||
|
||||
dataReceiver <- Resource{
|
||||
AWSService: "RAM",
|
||||
Name: shareName,
|
||||
Type: resourceType,
|
||||
Region: r,
|
||||
Owner: ownerID,
|
||||
ShareType: shareDirection,
|
||||
}
|
||||
}
|
||||
if ListResources.NextToken != nil {
|
||||
PaginationControl2 = ListResources.NextToken
|
||||
} else {
|
||||
PaginationControl2 = nil
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// The "NextToken" value is nil when there's no more data to return.
|
||||
if GetResourceShares.NextToken != nil {
|
||||
PaginationControl = GetResourceShares.NextToken
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-7
@@ -24,6 +24,7 @@ type RoleTrustsModule struct {
|
||||
IAMClient iam.ListRolesAPIClient
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSProfile string
|
||||
Goroutines int
|
||||
CommandCounter console.CommandCounter
|
||||
|
||||
// Main module data
|
||||
@@ -39,13 +40,13 @@ func (m *RoleTrustsModule) PrintRoleTrusts(outputFormat string, outputDirectory
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "role-trusts"
|
||||
m.modLog = utils.TxtLogger.WithFields(logrus.Fields{
|
||||
m.modLog = utils.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), aws.ToString(m.Caller.UserId))
|
||||
m.AWSProfile = utils.BuildAWSPath(m.Caller)
|
||||
}
|
||||
fmt.Printf("[%s] Enumerating role trusts for account %s.\n", cyan(m.output.CallingModule), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s][%s] Enumerating role trusts for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
|
||||
m.getAllRoleTrusts()
|
||||
m.printPrincipalTrusts(outputFormat, outputDirectory)
|
||||
@@ -78,10 +79,10 @@ func (m *RoleTrustsModule) printPrincipalTrusts(outputFormat string, outputDirec
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", 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)
|
||||
fmt.Printf("[%s] %s role trusts found.\n", cyan(m.output.CallingModule), strconv.Itoa(len(m.output.Body)))
|
||||
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] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,10 +113,10 @@ func (m *RoleTrustsModule) printServiceTrusts(outputFormat string, outputDirecto
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", 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)
|
||||
fmt.Printf("[%s] %s role trusts found.\n", cyan(m.output.CallingModule), strconv.Itoa(len(m.output.Body)))
|
||||
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] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] No role trusts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-13
@@ -25,6 +25,7 @@ type Route53Module struct {
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
CommandCounter console.CommandCounter
|
||||
|
||||
@@ -50,14 +51,14 @@ func (m *Route53Module) PrintRoute53(outputFormat string, outputDirectory string
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "route53"
|
||||
m.modLog = utils.TxtLogger.WithFields(logrus.Fields{
|
||||
m.modLog = utils.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), aws.ToString(m.Caller.UserId))
|
||||
m.AWSProfile = utils.BuildAWSPath(m.Caller)
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] Enumerating Route53 for account %s.\n", cyan(m.output.CallingModule), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s][%s] Enumerating Route53 for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
|
||||
m.getRoute53Records()
|
||||
|
||||
@@ -88,17 +89,17 @@ func (m *Route53Module) PrintRoute53(outputFormat string, outputDirectory string
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", 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)
|
||||
m.writeLoot(outputDirectory, verbosity)
|
||||
fmt.Printf("[%s] %s DNS records found.\n", cyan(m.output.CallingModule), strconv.Itoa(len(m.output.Body)))
|
||||
m.writeLoot(m.output.FilePath, verbosity)
|
||||
fmt.Printf("[%s][%s] %s DNS records found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
|
||||
} else {
|
||||
fmt.Printf("[%s] No DNS records found, skipping the creation of an output file.\n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] No DNS records found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *Route53Module) writeLoot(outputDirectory string, verbosity int) {
|
||||
path := filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile, "loot")
|
||||
path := filepath.Join(outputDirectory, "loot")
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
@@ -131,13 +132,13 @@ func (m *Route53Module) writeLoot(outputDirectory string, verbosity int) {
|
||||
if verbosity > 2 {
|
||||
if len(route53APublicRecords) > 0 {
|
||||
fmt.Println()
|
||||
fmt.Printf("[%s] %s \n", cyan(m.output.CallingModule), green("Feed these public zone A records into nmap and something like gowitness or aquatone."))
|
||||
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("Feed these public zone A records into nmap and something like gowitness or aquatone."))
|
||||
fmt.Print(route53APublicRecords)
|
||||
fmt.Printf("[%s] %s \n", cyan(m.output.CallingModule), green("End of loot file."))
|
||||
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("End of loot file."))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] Loot written to [%s]\n", cyan(m.output.CallingModule), route53ARecordsPublicZonesFileName)
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), route53ARecordsPublicZonesFileName)
|
||||
|
||||
err = os.WriteFile(route53ARecordsPrivateZonesFileName, []byte(route53APrivateRecords), 0644)
|
||||
if err != nil {
|
||||
@@ -149,13 +150,13 @@ func (m *Route53Module) writeLoot(outputDirectory string, verbosity int) {
|
||||
if verbosity > 2 {
|
||||
if len(route53APrivateRecords) > 0 {
|
||||
fmt.Println()
|
||||
fmt.Printf("[%s] %s \n", cyan(m.output.CallingModule), green("Feed these private zone A records into nmap and something like gowitness or aquatone."))
|
||||
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("Feed these private zone A records into nmap and something like gowitness or aquatone."))
|
||||
fmt.Print(route53APrivateRecords)
|
||||
fmt.Printf("[%s] %s \n", cyan(m.output.CallingModule), green("End of loot file."))
|
||||
fmt.Printf("[%s][%s] %s \n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("End of loot file."))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] Loot written to [%s]\n", cyan(m.output.CallingModule), route53ARecordsPrivateZonesFileName)
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), route53ARecordsPrivateZonesFileName)
|
||||
|
||||
}
|
||||
|
||||
|
||||
+52
-22
@@ -25,6 +25,7 @@ type SecretsModule struct {
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
AWSProfile string
|
||||
Goroutines int
|
||||
|
||||
// Main module data
|
||||
Secrets []Secret
|
||||
@@ -48,22 +49,23 @@ func (m *SecretsModule) PrintSecrets(outputFormat string, outputDirectory string
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "secrets"
|
||||
m.modLog = utils.TxtLogger.WithFields(logrus.Fields{
|
||||
m.modLog = utils.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = fmt.Sprintf("%s-%s", aws.ToString(m.Caller.Account), aws.ToString(m.Caller.UserId))
|
||||
m.AWSProfile = utils.BuildAWSPath(m.Caller)
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] Enumerating secrets for account %s.\n", cyan(m.output.CallingModule), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s] Supported Services: SecretsManager, SSM Parameters\n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] Enumerating secrets for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
fmt.Printf("[%s][%s] Supported Services: SecretsManager, SSM Parameters\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
semaphore := make(chan struct{}, m.Goroutines)
|
||||
|
||||
// Create a channel to signal the spinner aka task status goroutine to finish
|
||||
spinnerDone := make(chan bool)
|
||||
//fire up the the task status spinner/updated
|
||||
go console.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "regions")
|
||||
go console.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "tasks")
|
||||
|
||||
//create a channel to receive the objects
|
||||
dataReceiver := make(chan Secret)
|
||||
@@ -75,7 +77,7 @@ func (m *SecretsModule) PrintSecrets(outputFormat string, outputDirectory string
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, dataReceiver)
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
@@ -113,11 +115,11 @@ func (m *SecretsModule) PrintSecrets(outputFormat string, outputDirectory string
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
|
||||
//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)
|
||||
m.writeLoot(outputDirectory, verbosity)
|
||||
fmt.Printf("[%s] %s secrets found.\n", cyan(m.output.CallingModule), strconv.Itoa(len(m.output.Body)))
|
||||
m.writeLoot(m.output.FilePath, verbosity)
|
||||
fmt.Printf("[%s][%s] %s secrets found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
|
||||
} else {
|
||||
fmt.Printf("[%s] No secrets found, skipping the creation of an output file.\n", cyan(m.output.CallingModule))
|
||||
fmt.Printf("[%s][%s] No secrets found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -135,19 +137,21 @@ func (m *SecretsModule) Receiver(receiver chan Secret, receiverDone chan bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *SecretsModule) executeChecks(r string, wg *sync.WaitGroup, dataReceiver chan Secret) {
|
||||
func (m *SecretsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Secret) {
|
||||
defer wg.Done()
|
||||
|
||||
m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
m.getSecretsManagerSecretsPerRegion(r, dataReceiver)
|
||||
m.getSSMParametersPerRegion(r, dataReceiver)
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Add(1)
|
||||
go m.getSecretsManagerSecretsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getSSMParametersPerRegion(r, wg, semaphore, dataReceiver)
|
||||
|
||||
}
|
||||
|
||||
func (m *SecretsModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
path := filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile, "loot")
|
||||
path := filepath.Join(outputDirectory, "loot")
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
@@ -179,17 +183,30 @@ func (m *SecretsModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
|
||||
if verbosity > 2 {
|
||||
fmt.Println()
|
||||
fmt.Printf("[%s] %s \n\n", cyan(m.output.CallingModule), green("Use the commands below to retrieve the secrets that look interesting"))
|
||||
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("Use the commands below to retrieve the secrets that look interesting"))
|
||||
|
||||
fmt.Print(out)
|
||||
fmt.Printf("[%s] %s \n\n", cyan(m.output.CallingModule), green("End of loot file."))
|
||||
fmt.Printf("[%s][%s] %s \n\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), green("End of loot file."))
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] Loot written to [%s]\n", cyan(m.output.CallingModule), pullFile)
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), pullFile)
|
||||
|
||||
}
|
||||
|
||||
func (m *SecretsModule) getSecretsManagerSecretsPerRegion(r string, dataReceiver chan Secret) {
|
||||
func (m *SecretsModule) getSecretsManagerSecretsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Secret) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
|
||||
var PaginationControl *string
|
||||
for {
|
||||
@@ -234,7 +251,20 @@ func (m *SecretsModule) getSecretsManagerSecretsPerRegion(r string, dataReceiver
|
||||
}
|
||||
}
|
||||
|
||||
func (m *SecretsModule) getSSMParametersPerRegion(r string, dataReceiver chan Secret) {
|
||||
func (m *SecretsModule) getSSMParametersPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Secret) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
|
||||
var PaginationControl *string
|
||||
|
||||
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/BishopFox/cloudfox/console"
|
||||
"github.com/BishopFox/cloudfox/utils"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/aws/arn"
|
||||
"github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type TagsModule struct {
|
||||
// General configuration data
|
||||
ResourceGroupsTaggingApiClient *resourcegroupstaggingapi.Client
|
||||
|
||||
Caller sts.GetCallerIdentityOutput
|
||||
AWSRegions []string
|
||||
OutputFormat string
|
||||
Goroutines int
|
||||
AWSProfile string
|
||||
|
||||
// Main module data
|
||||
Tags []Tag
|
||||
CommandCounter console.CommandCounter
|
||||
ResourceTypeCounts map[string]int
|
||||
|
||||
// Used to store output data for pretty printing
|
||||
output utils.OutputData2
|
||||
modLog *logrus.Entry
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
AWSService string
|
||||
Region string
|
||||
Arn string
|
||||
Name string
|
||||
Type string
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (m *TagsModule) PrintTags(outputFormat string, outputDirectory string, verbosity int) {
|
||||
// These stuct values are used by the output module
|
||||
m.output.Verbosity = verbosity
|
||||
m.output.Directory = outputDirectory
|
||||
m.output.CallingModule = "tags"
|
||||
m.modLog = utils.TxtLog.WithFields(logrus.Fields{
|
||||
"module": m.output.CallingModule,
|
||||
})
|
||||
if m.AWSProfile == "" {
|
||||
m.AWSProfile = utils.BuildAWSPath(m.Caller)
|
||||
}
|
||||
|
||||
fmt.Printf("[%s][%s] Enumerating tags for account %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account))
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
semaphore := make(chan struct{}, m.Goroutines)
|
||||
|
||||
// Create a channel to signal the spinner aka task status goroutine to finish
|
||||
spinnerDone := make(chan bool)
|
||||
//fire up the the task status spinner/updated
|
||||
go console.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "regions")
|
||||
|
||||
//create a channel to receive the objects
|
||||
dataReceiver := make(chan Tag)
|
||||
|
||||
// Create a channel to signal to stop
|
||||
receiverDone := make(chan bool)
|
||||
go m.Receiver(dataReceiver, receiverDone)
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
// Send a message to the spinner goroutine to close the channel and stop
|
||||
spinnerDone <- true
|
||||
<-spinnerDone
|
||||
// Send a message to the data receiver goroutine to close the channel and stop
|
||||
receiverDone <- true
|
||||
<-receiverDone
|
||||
|
||||
// add - if struct is not empty do this. otherwise, dont write anything.
|
||||
m.output.Headers = []string{
|
||||
"Service",
|
||||
"Region",
|
||||
"Type",
|
||||
//"Name",
|
||||
//"Resource Arn",
|
||||
"Key",
|
||||
"Value",
|
||||
}
|
||||
|
||||
sort.Slice(m.Tags, func(i, j int) bool {
|
||||
return m.Tags[i].AWSService < m.Tags[j].AWSService
|
||||
})
|
||||
|
||||
// Table rows
|
||||
for i := range m.Tags {
|
||||
|
||||
m.output.Body = append(
|
||||
m.output.Body,
|
||||
[]string{
|
||||
m.Tags[i].AWSService,
|
||||
m.Tags[i].Region,
|
||||
m.Tags[i].Type,
|
||||
//m.Tags[i].Arn,
|
||||
//m.Tags[i].Name,
|
||||
m.Tags[i].Key,
|
||||
m.Tags[i].Value,
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
if len(m.output.Body) > 0 {
|
||||
m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile)
|
||||
utils.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule)
|
||||
//m.writeLoot(m.output.FilePath, verbosity)
|
||||
fmt.Printf("[%s][%s] %s tags found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), strconv.Itoa(len(m.output.Body)))
|
||||
count := m.countUniqueResourcesWithTags()
|
||||
fmt.Printf("[%s][%s] %d unique resources with tags found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), count)
|
||||
} else {
|
||||
fmt.Printf("[%s][%s] No tags found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *TagsModule) countUniqueResourcesWithTags() int {
|
||||
var uniqueResources []string
|
||||
for i := range m.Tags {
|
||||
if !utils.Contains(m.Tags[i].Name, uniqueResources) {
|
||||
uniqueResources = append(uniqueResources, m.Tags[i].Name)
|
||||
}
|
||||
}
|
||||
//
|
||||
return len(uniqueResources)
|
||||
}
|
||||
|
||||
// TODO: Make summary table
|
||||
|
||||
// func (m *TagsModule) createTagsSummary() {
|
||||
// var serviceMap map[string]map[string]int
|
||||
// // var services []string
|
||||
// // var totalRegionCounts map[string]int
|
||||
|
||||
// var uniqueResources []string
|
||||
// var uniqueServiceTypes []string
|
||||
|
||||
// for i := range m.Tags {
|
||||
// if !utils.Contains(m.Tags[i].Name, uniqueResources) {
|
||||
// uniqueResources = append(uniqueResources, m.serviceMap[m.Tags[i].Name][m.])
|
||||
// }
|
||||
|
||||
// if !utils.Contains(fmt.Sprintf("%s %s", m.Tags[i].AWSService, m.Tags[i].Type), uniqueServiceTypes) {
|
||||
// uniqueServiceTypes = append(uniqueServiceTypes, fmt.Sprintf("%s %s", m.Tags[i].AWSService, m.Tags[i].Type))
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
// for j, resource := range uniqueResources {
|
||||
// for k, ServiceType := range uniqueServiceTypes {
|
||||
// if fmt.Sprintf("%s %s", resource[j].AWSService, m.Tags[i].Type). == ServiceType[k]
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
func (m *TagsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Tag) {
|
||||
defer wg.Done()
|
||||
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getTagsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
|
||||
func (m *TagsModule) Receiver(receiver chan Tag, receiverDone chan bool) {
|
||||
defer close(receiverDone)
|
||||
for {
|
||||
select {
|
||||
case data := <-receiver:
|
||||
m.Tags = append(m.Tags, data)
|
||||
case <-receiverDone:
|
||||
receiverDone <- true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *TagsModule) getTagsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Tag) {
|
||||
defer func() {
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages.
|
||||
var PaginationControl *string
|
||||
|
||||
for {
|
||||
GetResources, err := m.ResourceGroupsTaggingApiClient.GetResources(
|
||||
context.TODO(),
|
||||
&resourcegroupstaggingapi.GetResourcesInput{
|
||||
PaginationToken: PaginationControl,
|
||||
},
|
||||
func(o *resourcegroupstaggingapi.Options) {
|
||||
o.Region = r
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
//var parsedArn types.Arn
|
||||
for _, resource := range GetResources.ResourceTagMappingList {
|
||||
var resourceType string
|
||||
resourceArn := aws.ToString(resource.ResourceARN)
|
||||
parsedArn, err := arn.Parse(resourceArn)
|
||||
if parsedArn.Service != "s3" {
|
||||
resourceType = strings.Split(parsedArn.Resource, ":")[0]
|
||||
resourceType = strings.Split(resourceType, "/")[0]
|
||||
} else {
|
||||
resourceType = "bucket"
|
||||
}
|
||||
//resourceName := strings.Split(parsedArn.Resource, ":")[]
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
for _, tag := range resource.Tags {
|
||||
key := aws.ToString(tag.Key)
|
||||
value := aws.ToString(tag.Value)
|
||||
|
||||
dataReceiver <- Tag{
|
||||
AWSService: parsedArn.Service,
|
||||
Arn: resourceArn,
|
||||
Name: parsedArn.Resource,
|
||||
Region: r,
|
||||
Type: resourceType,
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if aws.ToString(GetResources.PaginationToken) != "" {
|
||||
PaginationControl = GetResources.PaginationToken
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
+909
-488
File diff suppressed because it is too large.
Load diff
@@ -10,7 +10,7 @@ require (
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
|
||||
github.com/aquasecurity/table v1.8.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.11
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.16
|
||||
github.com/aws/aws-sdk-go-v2/config v1.13.0
|
||||
github.com/aws/aws-sdk-go-v2/service/apigateway v1.15.6
|
||||
github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.12.5
|
||||
@@ -18,6 +18,7 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudformation v1.18.0
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.18.2
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.16.1
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.17.1
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.26.0
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.17.5
|
||||
github.com/aws/aws-sdk-go-v2/service/ecs v1.18.7
|
||||
@@ -26,6 +27,7 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.14.5
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.5
|
||||
github.com/aws/aws-sdk-go-v2/service/fsx v1.24.1
|
||||
github.com/aws/aws-sdk-go-v2/service/glue v1.32.0
|
||||
github.com/aws/aws-sdk-go-v2/service/grafana v1.9.1
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.18.5
|
||||
github.com/aws/aws-sdk-go-v2/service/lambda v1.23.1
|
||||
@@ -35,13 +37,16 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/ram v1.16.15
|
||||
github.com/aws/aws-sdk-go-v2/service/rds v1.16.0
|
||||
github.com/aws/aws-sdk-go-v2/service/redshift v1.25.0
|
||||
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.13.19
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.20.5
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.24.0
|
||||
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.32.1
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.13.0
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.18.1
|
||||
github.com/aws/aws-sdk-go-v2/service/sqs v1.19.10
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.27.1
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.14.0
|
||||
github.com/aws/smithy-go v1.12.1
|
||||
github.com/aws/smithy-go v1.13.3
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/go-openapi/strfmt v0.21.3 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
|
||||
|
||||
@@ -75,8 +75,9 @@ github.com/aws/aws-sdk-go-v2 v1.11.2/go.mod h1:SQfA+m2ltnu1cA0soUkj4dRSsmITiVQUJ
|
||||
github.com/aws/aws-sdk-go-v2 v1.13.0/go.mod h1:L6+ZpqHaLbAaxsqV0L4cvxZY7QupWJB4fhkf8LXvC7w=
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.4/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.5/go.mod h1:Wh7MEsmEApyL5hrWzpDkba4gwAPc5/piwLVLFnCxp48=
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.11 h1:xM1ZPSvty3xVmdxiGr7ay/wlqv+MWhH0rMlyLdbC0YQ=
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.11/go.mod h1:WTACcleLz6VZTp7fak4EO5b9Q4foxbn+8PIz3PmyKlo=
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.16 h1:M1fj4FE2lB4NzRb9Y0xdWsn2P0+2UHVxwKyOa4YJNjk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.16.16/go.mod h1:SwiyXi/1zTUZ6KIAmLK5V5ll8SiURNUYOqTerZPaF9k=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.2.0 h1:scBthy70MB3m4LCMFaBcmYCyR2XWOz6MxSfdSu/+fQo=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.2.0/go.mod h1:oZHzg1OVbuCiRTY0oRPM+c2HQvwnFCGJwKeSqqAJ/yM=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.13.0 h1:1ij3YPk13RrIn1h+pH+dArh3lNPD5JSAP+ifOkNhnB0=
|
||||
@@ -89,14 +90,16 @@ github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.2/go.mod h1:SgKKNBIoDC/
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.4/go.mod h1:XHgQ7Hz2WY2GAn//UXHofLfPXWh+s62MbMOijrg12Lw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.11/go.mod h1:tmUB6jakq5DFNcXsXOA/ZQ7/C8VnSKYkx58OI7Fh79g=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.12/go.mod h1:Afj/U8svX6sJ77Q+FPWMzabJ9QjbwP32YlopgKALUpg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.18 h1:OmiwoVyLKEqqD5GvB683dbSqxiOfvx4U2lDZhG2Esc4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.18/go.mod h1:348MLhzV1GSlZSMusdwQpXKbhD7X2gbI/TxwAPKkYZQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23 h1:s4g/wnzMf+qepSNgTvaQQHNxyMLKSawNhKCPNy++2xY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23/go.mod h1:2DFxAQ9pfIRy0imBCJv+vZ2X6RKxves6fbnEuSry6b4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.2/go.mod h1:xT4XX6w5Sa3dhg50JrYyy3e4WPYo/+WjY/BXtqXVunU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.2.0/go.mod h1:BsCSJHx5DnDXIrOcqB8KN1/B+hXLG/bi4Y6Vjcx/x9E=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.5/go.mod h1:fV1AaS2gFc1tM0RCb015FJ0pvWVUfJZANzjwoO4YakM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.6/go.mod h1:FwpAKI+FBPIELJIdmQzlLtRe8LQSOreMcM2wBsPMvvc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.12 h1:5mvQDtNWtI6H56+E4LUnLWEmATMB7oEh+Z9RurtIuC0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.12/go.mod h1:ckaCVTEdGAxO6KwTGzgskxR1xM+iJW4lxMyDFVda2Fc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17 h1:/K482T5A3623WJgWT8w1yRAFK4RzGzEl7y39yhtn9eA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17/go.mod h1:pRwaTYCJemADaqCbUAxltMoHKata7hmB5PjEXeu0kfg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.4 h1:0NrDHIwS1LIR750ltj6ciiu4NZLpr9rgq8vHi/4QD4s=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.4/go.mod h1:R3sWUqPcfXSiF/LSFJhjyJmpg9uV6yP2yv3YZZjldVI=
|
||||
github.com/aws/aws-sdk-go-v2/service/apigateway v1.15.6 h1:GWrNZ3xtblr+J+oCYtJY67YPqR5nzObOaXWWKplDMag=
|
||||
@@ -111,6 +114,8 @@ github.com/aws/aws-sdk-go-v2/service/cloudfront v1.18.2 h1:uhpcmaxRKPt/VHGmzJ4aa
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.18.2/go.mod h1:TRXCqcApTM1LhOJcLNolqFEDr3InJoYBieb1qqPcCko=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.16.1 h1:1dALZzDXZmZIgVX6N+oOSwSBH+BBWC1RCjUCvgQUQfs=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.16.1/go.mod h1:ddq0bwfwHQ0QxV6vU9arKg31qfmg8fjcYZt/ef01AcI=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.17.1 h1:1QpTkQIAaZpR387it1L+erjB5bStGFCJRvmXsodpPEU=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.17.1/go.mod h1:BZhn/C3z13ULTSstVi2Kymc62bgjFh/JwLO9Tm2OFYI=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.26.0 h1:Q++veaxis1Dg7is9yi+aEPsIBRAgdkUxoIvyud7jOyo=
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.26.0/go.mod h1:cIbz+b70nxJafXf9lT07Xj03pef6CsVdYTCCR0DQEQc=
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.17.5 h1:W9vzPbvX7rOa/FacbQIDfnNrwxHkn5O+DdfmiIS4cHc=
|
||||
@@ -127,12 +132,17 @@ github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.5 h1:OR1FrOPrN
|
||||
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.5/go.mod h1:v+U73J9xr5bwN0IYXIUlQnfWGVrcIGlY1wuq+vDsCyg=
|
||||
github.com/aws/aws-sdk-go-v2/service/fsx v1.24.1 h1:VKo7a7XJbqzzELwaLzjpGm7uPPtO+ZKCs/oHonYk7lE=
|
||||
github.com/aws/aws-sdk-go-v2/service/fsx v1.24.1/go.mod h1:MQeMcZxXxWr8VHc/8f8Hspg5D5dCm7wchXHYaiBntDw=
|
||||
github.com/aws/aws-sdk-go-v2/service/glue v1.32.0 h1:7Kacs7LCbDmMSwW5nHk4OExP9JlYK5P9r3iQ853yoOE=
|
||||
github.com/aws/aws-sdk-go-v2/service/glue v1.32.0/go.mod h1:aupHsCJmK66t1MQ542c6qBSuJYEA2IwKmwi4M3jdT1M=
|
||||
github.com/aws/aws-sdk-go-v2/service/grafana v1.9.1 h1:qrd9evf91w8s7TIIvJakNzRJLW7HRiMDpQ9nmI7cOgo=
|
||||
github.com/aws/aws-sdk-go-v2/service/grafana v1.9.1/go.mod h1:ckNYD0WIFojv8yOx5QiTp2tg64kTJdMFDPjR4y4mboY=
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.18.5 h1:NUT8HreIoLAfPAP7eEYN8jwidvY+4TjZ86xqS/iGDQI=
|
||||
github.com/aws/aws-sdk-go-v2/service/iam v1.18.5/go.mod h1:0YrrF3m5qWi/EItvW0wmZCydlgr8lzJ882P2mXoHo7U=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.7.0 h1:F1diQIOkNn8jcez4173r+PLPdkWK7chy74r3fKpDrLI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.7.0/go.mod h1:8ctElVINyp+SjhoZZceUAZw78glZH6R8ox5MVNu5j2s=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9 h1:Lh1AShsuIJTwMkoxVCAYPJgNG5H+eN6SmoUn8nOZ5wE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9/go.mod h1:a9j48l6yL5XINLHLcOKInjdvknN+vWqPBxqeIDw7ktw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.17 h1:o0Ia3nb56m8+8NvhbCDiSBiZRNUwIknVWobx5vks0Vk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.17/go.mod h1:WJD9FbkwzM2a1bZ36ntH6+5Jc+x41Q4K2AcLeHDLAS8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.2/go.mod h1:FgR1tCsn8C6+Hf+N5qkfrE4IXvUL1RgW87sunJ+5J4I=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.7.0 h1:4QAOB3KrvI1ApJK14sliGr3Ie2pjyvNypn/lfzDHfUw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.7.0/go.mod h1:K/qPe6AP2TGYv4l6n7c88zh9jWBDf6nHhvg1fx/EWfU=
|
||||
@@ -152,6 +162,8 @@ github.com/aws/aws-sdk-go-v2/service/rds v1.16.0 h1:xYxIpmqlnc+U/miylJaNmEty34MC
|
||||
github.com/aws/aws-sdk-go-v2/service/rds v1.16.0/go.mod h1:U1tzFmWLyt4AqSRLONL0RXcYsQg0huiInDdRmCecz1w=
|
||||
github.com/aws/aws-sdk-go-v2/service/redshift v1.25.0 h1:sD7lJ9EchUyaZXjXyBtK0TwaGULzTmtV1xo+wrNT6hw=
|
||||
github.com/aws/aws-sdk-go-v2/service/redshift v1.25.0/go.mod h1:7vdpNDFFcV8yDaDTPpwy5wv28w1815yVJxHaCX4hmmM=
|
||||
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.13.19 h1:1KQhU01IDvg4fohFIBGlITT4OM/Q99QY6FRj23M1MUg=
|
||||
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.13.19/go.mod h1:tTgBdzibiIxq4r4+ZopTWLk4rh9U5imMsdKPALItjH8=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.20.5 h1:Vex6D07/CmahT0LIaiRk+j9xyVAsvQxBa8iv38p3Ajc=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.20.5/go.mod h1:QZWV7sxHUg/qsPJcAtAI9JyLPKZ78weHmdILmYMCqEE=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.24.0 h1:REKac2iT0HYxUSzqOSuncnmsZnE3m4MlGfo1dOUN3vg=
|
||||
@@ -160,6 +172,10 @@ github.com/aws/aws-sdk-go-v2/service/sagemaker v1.32.1 h1:nOjJ5PevzE1ymPIYnJk6X/
|
||||
github.com/aws/aws-sdk-go-v2/service/sagemaker v1.32.1/go.mod h1:nF9ZnJeXLYw8PPRAtsBbG3aY1P8c5bUH2zqA/IAzaks=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.13.0 h1:VKvs4yx3nrcyBJcj4iSy5UI/Awdsa0fbDKesiNwPuZY=
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.13.0/go.mod h1:5Oibvfj4kc6CE70qamrlOU+KSO/JWANgxIVbesvSMCE=
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.18.1 h1:nxfBH9r3VUyybIOWdbIBJ/d5I1wdG7FwIoZ/BH/EhS8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.18.1/go.mod h1:sIIc12m8ASRbCgOERccSSkTFeekFfHKEM4TKAvzJpG0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sqs v1.19.10 h1:Y4civ9pg5cbQkSf/YGMfFZaIPAAAK61JV+NIzO8Ri4k=
|
||||
github.com/aws/aws-sdk-go-v2/service/sqs v1.19.10/go.mod h1:65Z/rmGw/6usiOFI0Tk4ddNUmPbjjPER1WLZwnFqxFM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.27.1 h1:w/HlW+NGK5EU5jf/qekDZ56kg9jhvP/1Egh3bMRTdgo=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.27.1/go.mod h1:Ej87mQA2lDTOyPL/ZCjoChhTCU/fwPKg5Em62pOIqVc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.9.0 h1:1qLJeQGBmNQW3mBNzK2CFmrQNmoXWrscPqsrAaU1aTA=
|
||||
@@ -170,8 +186,9 @@ github.com/aws/smithy-go v1.9.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAm
|
||||
github.com/aws/smithy-go v1.10.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
||||
github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM=
|
||||
github.com/aws/smithy-go v1.11.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/aws/smithy-go v1.12.1 h1:yQRC55aXN/y1W10HgwHle01DRuV9Dpf31iGkotjt3Ag=
|
||||
github.com/aws/smithy-go v1.12.1/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/aws/smithy-go v1.13.3 h1:l7LYxGuzK6/K+NzJ2mC+VvLUbae0sL3bXU//04MkmnA=
|
||||
github.com/aws/smithy-go v1.13.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
var (
|
||||
rootCmd = &cobra.Command{
|
||||
Use: os.Args[0],
|
||||
Version: "1.7.0",
|
||||
Version: "1.8.0",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -5,10 +5,13 @@
|
||||
"Action": [
|
||||
"apigateway:GET",
|
||||
"apprunner:DescribeService",
|
||||
"apprunner:ListServices",
|
||||
"apprunner:ListServices",
|
||||
"cloudformation:DescribeStacks",
|
||||
"cloudformation:GetTemplate",
|
||||
"cloudformation:ListStacks",
|
||||
"cloudtrail:LookupEvents",
|
||||
"cloudfront:ListDistributions",
|
||||
"dynamodb:ListTables",
|
||||
"ec2:DescribeInstanceAttribute",
|
||||
"ec2:DescribeInstances",
|
||||
"ec2:DescribeRegions",
|
||||
@@ -27,6 +30,8 @@
|
||||
"elasticloadbalancing:DescribeListeners",
|
||||
"fsx:DescribeFileSystems",
|
||||
"fsx:DescribeVolumes",
|
||||
"glue:ListDevEndpoints",
|
||||
"glue:ListJobs",
|
||||
"grafana:ListWorkspaces",
|
||||
"iam:GetAccountAuthorizationDetails",
|
||||
"iam:ListAccessKeys",
|
||||
@@ -41,6 +46,8 @@
|
||||
"mq:ListBrokers",
|
||||
"es:DescribeDomain",
|
||||
"es:ListDomainNames",
|
||||
"ram:GetResourceShares",
|
||||
"ram:ListResources",
|
||||
"rds:DescribeDBInstances",
|
||||
"redshift:DescribeClusters",
|
||||
"route53:ListHostedZones",
|
||||
@@ -57,11 +64,15 @@
|
||||
"sagemaker:DescribeTrainingJob",
|
||||
"sagemaker:ListModels",
|
||||
"sagemaker:DescribeModel",
|
||||
"secretsmanager:ListSecrets"
|
||||
"secretsmanager:ListSecrets",
|
||||
"sns:ListTopics",
|
||||
"sqs:ListQueues",
|
||||
"tag:GetResources"
|
||||
|
||||
],
|
||||
"Resource": "*",
|
||||
"Effect": "Allow",
|
||||
"Sid": "AllowMoreReadForCloudFox"
|
||||
"Sid": "AllowMoreReadForCloudFox180"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+154
-44
@@ -1,27 +1,30 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/aws/retry"
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/service/ssm"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"github.com/kyokomi/emoji"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
var (
|
||||
TxtLoggerName = "root"
|
||||
TxtLogger = txtLogger()
|
||||
TxtLog = TxtLogger()
|
||||
UtilsFs = afero.NewOsFs()
|
||||
)
|
||||
|
||||
func AWSConfigFileLoader(AWSProfile string) aws.Config {
|
||||
func AWSConfigFileLoader(AWSProfile string, version string) aws.Config {
|
||||
// Ensures the profile in the aws config file meets all requirements (valid keys and a region defined). I noticed some calls fail without a default region.
|
||||
cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithSharedConfigProfile(AWSProfile), config.WithDefaultRegion("us-east-1"), config.WithRetryer(
|
||||
func() aws.Retryer {
|
||||
@@ -29,69 +32,68 @@ func AWSConfigFileLoader(AWSProfile string) aws.Config {
|
||||
}))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
log.Fatal(err)
|
||||
TxtLog.Println(err)
|
||||
}
|
||||
|
||||
_, err = cfg.Credentials.Retrieve(context.TODO())
|
||||
if err != nil {
|
||||
fmt.Printf("[%s] Error retrieving credentials from the specified profile, environment variables, or the instance metadata service.\n", cyan(emoji.Sprintf(":fox:cloudfox :fox:")))
|
||||
log.Fatalf("Could not retrieve the specified profile name %s", err)
|
||||
fmt.Printf("[%s][%s] Error retrieving credentials from environment variables, or the instance metadata service.\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), cyan(AWSProfile))
|
||||
TxtLog.Printf("Could not retrieve the specified profile name %s", err)
|
||||
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func AWSWhoami(awsProfile string) sts.GetCallerIdentityOutput {
|
||||
|
||||
func AWSWhoami(awsProfile string, version string) (*sts.GetCallerIdentityOutput, error) {
|
||||
// Connects to STS and checks caller identity. Same as running "aws sts get-caller-identity"
|
||||
//fmt.Printf("[%s] Retrieving caller's identity\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)))
|
||||
|
||||
STSService := sts.NewFromConfig(AWSConfigFileLoader(awsProfile))
|
||||
STSService := sts.NewFromConfig(AWSConfigFileLoader(awsProfile, version))
|
||||
CallerIdentity, err := STSService.GetCallerIdentity(context.TODO(), &sts.GetCallerIdentityInput{})
|
||||
if err != nil {
|
||||
fmt.Printf("[%s] Could not get caller's identity\n\nError: %s", cyan(emoji.Sprintf(":fox:cloudfox :fox:")), err)
|
||||
log.Fatalf("Could not get caller's identity: %s", err)
|
||||
fmt.Printf("[%s][%s] Could not get caller's identity\n\nError: %s\n\n", cyan(emoji.Sprintf(":fox:cloudfox v%s :fox:", version)), cyan(awsProfile), err)
|
||||
TxtLog.Printf("Could not get caller's identity: %s", err)
|
||||
return CallerIdentity, err
|
||||
|
||||
}
|
||||
return *CallerIdentity
|
||||
return CallerIdentity, err
|
||||
}
|
||||
|
||||
func GetRegionsForService(awsProfile string, service string) []string {
|
||||
SSMClient := ssm.NewFromConfig(AWSConfigFileLoader(awsProfile))
|
||||
var PaginationControl *string
|
||||
var supportedRegions []string
|
||||
path := fmt.Sprintf("/aws/service/global-infrastructure/services/%s/regions", service)
|
||||
// func GetRegionsForService(awsProfile string, service string) []string {
|
||||
// SSMClient := ssm.NewFromConfig(AWSConfigFileLoader(awsProfile))
|
||||
// var PaginationControl *string
|
||||
// var supportedRegions []string
|
||||
// path := fmt.Sprintf("/aws/service/global-infrastructure/services/%s/regions", service)
|
||||
|
||||
ServiceRegions, err := SSMClient.GetParametersByPath(
|
||||
context.TODO(),
|
||||
&(ssm.GetParametersByPathInput{
|
||||
NextToken: PaginationControl,
|
||||
Path: &path,
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
// ServiceRegions, err := SSMClient.GetParametersByPath(
|
||||
// context.TODO(),
|
||||
// &(ssm.GetParametersByPathInput{
|
||||
// NextToken: PaginationControl,
|
||||
// Path: &path,
|
||||
// }),
|
||||
// )
|
||||
// if err != nil {
|
||||
// fmt.Println(err.Error())
|
||||
|
||||
}
|
||||
// }
|
||||
|
||||
if ServiceRegions.Parameters != nil {
|
||||
for _, region := range ServiceRegions.Parameters {
|
||||
name := *region.Value
|
||||
supportedRegions = append(supportedRegions, name)
|
||||
}
|
||||
// if ServiceRegions.Parameters != nil {
|
||||
// for _, region := range ServiceRegions.Parameters {
|
||||
// name := *region.Value
|
||||
// supportedRegions = append(supportedRegions, name)
|
||||
// }
|
||||
|
||||
// The "NextToken" value is nil when there's no more data to return.
|
||||
if ServiceRegions.NextToken != nil {
|
||||
PaginationControl = ServiceRegions.NextToken
|
||||
} else {
|
||||
PaginationControl = nil
|
||||
}
|
||||
}
|
||||
return supportedRegions
|
||||
}
|
||||
// // The "NextToken" value is nil when there's no more data to return.
|
||||
// if ServiceRegions.NextToken != nil {
|
||||
// PaginationControl = ServiceRegions.NextToken
|
||||
// } else {
|
||||
// PaginationControl = nil
|
||||
// }
|
||||
// }
|
||||
// return supportedRegions
|
||||
// }
|
||||
|
||||
// txtLogger - Returns the txt logger
|
||||
func txtLogger() *logrus.Logger {
|
||||
func TxtLogger() *logrus.Logger {
|
||||
txtLogger := logrus.New()
|
||||
txtFile, err := os.OpenFile(fmt.Sprintf("%s/cloudfox-error.log", ptr.ToString(GetLogDirPath())), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
@@ -103,3 +105,111 @@ func txtLogger() *logrus.Logger {
|
||||
|
||||
return txtLogger
|
||||
}
|
||||
|
||||
func CheckErr(e error, msg string) {
|
||||
if e != nil {
|
||||
TxtLog.Printf("[-] Error %s", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func GetAllAWSProfiles(AWSConfirm bool) []string {
|
||||
var AWSProfiles []string
|
||||
|
||||
credentialsFile, err := UtilsFs.Open(config.DefaultSharedCredentialsFilename())
|
||||
CheckErr(err, "could not open default AWS credentials file")
|
||||
if err == nil {
|
||||
defer credentialsFile.Close()
|
||||
scanner := bufio.NewScanner(credentialsFile)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
text := strings.TrimSpace(scanner.Text())
|
||||
if strings.HasPrefix(text, "[") && strings.HasSuffix(text, "]") {
|
||||
text = strings.TrimPrefix(text, "[")
|
||||
text = strings.TrimSuffix(text, "]")
|
||||
if !Contains(text, AWSProfiles) {
|
||||
AWSProfiles = append(AWSProfiles, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configFile, err := UtilsFs.Open(config.DefaultSharedConfigFilename())
|
||||
CheckErr(err, "could not open default AWS credentials file")
|
||||
if err == nil {
|
||||
defer configFile.Close()
|
||||
scanner2 := bufio.NewScanner(configFile)
|
||||
scanner2.Split(bufio.ScanLines)
|
||||
for scanner2.Scan() {
|
||||
text := strings.TrimSpace(scanner2.Text())
|
||||
if strings.HasPrefix(text, "[") && strings.HasSuffix(text, "]") {
|
||||
text = strings.TrimPrefix(text, "[profile ")
|
||||
text = strings.TrimPrefix(text, "[")
|
||||
text = strings.TrimSuffix(text, "]")
|
||||
if !Contains(text, AWSProfiles) {
|
||||
AWSProfiles = append(AWSProfiles, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !AWSConfirm {
|
||||
result := ConfirmSelectedProfiles(AWSProfiles)
|
||||
if !result {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
return AWSProfiles
|
||||
|
||||
}
|
||||
|
||||
func ConfirmSelectedProfiles(AWSProfiles []string) bool {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Printf("[ %s] Identified profiles:\n\n", cyan(emoji.Sprintf(":fox:cloudfox :fox:")))
|
||||
for _, profile := range AWSProfiles {
|
||||
fmt.Printf("\t* %s\n", profile)
|
||||
}
|
||||
fmt.Printf("\n[ %s] Are you sure you'd like to run this command against the [%d] listed profile(s)? (Y\\n): ", cyan(emoji.Sprintf(":fox:cloudfox :fox:")), len(AWSProfiles))
|
||||
text, _ := reader.ReadString('\n')
|
||||
switch text {
|
||||
case "\n", "Y\n", "y\n":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
func GetSelectedAWSProfiles(AWSProfilesListPath string) []string {
|
||||
AWSProfilesListFile, err := UtilsFs.Open(AWSProfilesListPath)
|
||||
CheckErr(err, fmt.Sprintf("could not open given file %s", AWSProfilesListPath))
|
||||
if err != nil {
|
||||
fmt.Printf("\nError loading profiles. Could not open file at location[%s]\n", AWSProfilesListPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer AWSProfilesListFile.Close()
|
||||
var AWSProfiles []string
|
||||
scanner := bufio.NewScanner(AWSProfilesListFile)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
profile := strings.TrimSpace(scanner.Text())
|
||||
if len(profile) != 0 {
|
||||
AWSProfiles = append(AWSProfiles, profile)
|
||||
}
|
||||
}
|
||||
return AWSProfiles
|
||||
}
|
||||
|
||||
func removeBadPathChars(receivedPath *string) string {
|
||||
var path string
|
||||
var bannedPathChars *regexp.Regexp = regexp.MustCompile(`[<>:"'|?*]`)
|
||||
path = bannedPathChars.ReplaceAllString(aws.ToString(receivedPath), "_")
|
||||
|
||||
return path
|
||||
|
||||
}
|
||||
|
||||
func BuildAWSPath(Caller sts.GetCallerIdentityOutput) string {
|
||||
var callerAccount = removeBadPathChars(Caller.Account)
|
||||
var callerUserID = removeBadPathChars(Caller.UserId)
|
||||
|
||||
return fmt.Sprintf("%s-%s", callerAccount, callerUserID)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
func compareSlice(a []string, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for idx, elem := range a {
|
||||
if elem != b[idx] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Case empty file.
|
||||
// Case expected format.
|
||||
func TestGetAllAWSProfiles(t *testing.T) {
|
||||
var tests = []struct {
|
||||
fileData []byte
|
||||
expectedOutput []string
|
||||
AWSConfirm bool
|
||||
caseName string
|
||||
}{
|
||||
{[]byte(""), []string{}, true, "empty file"},
|
||||
{[]byte("[default]\naws_access_key=abc\naws_secret_access_key=123\n[123]\naws_access_key=abc\naws_secret_access_key=123"), []string{"default", "123"}, true, "expected format"},
|
||||
}
|
||||
credentialsFile := config.DefaultSharedCredentialsFilename()
|
||||
|
||||
for _, test := range tests {
|
||||
UtilsFs = afero.NewMemMapFs()
|
||||
fmt.Printf("[*] Testing %s\n", test.caseName)
|
||||
UtilsFs.Create(credentialsFile)
|
||||
afero.WriteFile(UtilsFs, credentialsFile, test.fileData, 0755)
|
||||
output := GetAllAWSProfiles(test.AWSConfirm)
|
||||
if !compareSlice(output, test.expectedOutput) {
|
||||
t.Errorf("Test Failed: %v inputted, %v expected, received: %v", test.fileData, test.expectedOutput, output)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Case empty file.
|
||||
// Case special characters \r \n \t.
|
||||
// Case with extra new lines at the end.
|
||||
// Case expected format.
|
||||
func TestGetSelectedAWSProfiles(t *testing.T) {
|
||||
var tests = []struct {
|
||||
fileData []byte
|
||||
expectedOutput []string
|
||||
AWSConfirm bool
|
||||
caseName string
|
||||
}{
|
||||
{[]byte(""), []string{}, true, "empty file"},
|
||||
{[]byte("abcd\r\nxyz\t\n123\r\t"), []string{"abcd", "xyz", "123"}, true, "special characters \\r \\n \\t"},
|
||||
{[]byte("qwerty\nxyz\n456\n\n\n"), []string{"qwerty", "xyz", "456"}, true, "extra new lines at the end"},
|
||||
{[]byte("nmhj\nyuioy\n098"), []string{"nmhj", "yuioy", "098"}, true, "expected format"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
UtilsFs = afero.NewMemMapFs()
|
||||
fmt.Printf("[*] Testing %s\n", test.caseName)
|
||||
UtilsFs.Create("/tmp/myfile.txt")
|
||||
afero.WriteFile(UtilsFs, "/tmp/myfile.txt", test.fileData, 0755)
|
||||
output := GetSelectedAWSProfiles("/tmp/myfile.txt")
|
||||
if !compareSlice(output, test.expectedOutput) {
|
||||
t.Errorf("Test Failed: %v inputted, %v expected, recieved: %v", test.fileData, test.expectedOutput, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user