mirror of
https://github.com/BishopFox/cloudfox.git
synced 2026-11-07 17:05:53 -10:00
Compare commits
25
Commits
main
..
jb/gcp-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0801228b72 | ||
|
|
b47d4b0b45 | ||
|
|
5dbb1a9c32 | ||
|
|
d573ca6fb6 | ||
|
|
79aa3f48ef | ||
|
|
adc9a27232 | ||
|
|
3d58404721 | ||
|
|
531a2ed572 | ||
|
|
5d5c021c20 | ||
|
|
182e26c7bc | ||
|
|
1cf3d52855 | ||
|
|
dc040c3f94 | ||
|
|
454ca1f81e | ||
|
|
521422f5d6 | ||
|
|
45197cf89d | ||
|
|
b8bb8473e2 | ||
|
|
29c9a2e7a5 | ||
|
|
462dba9c10 | ||
|
|
2275e5872c | ||
|
|
9d5c7b79f2 | ||
|
|
5a04bef828 | ||
|
|
3887531d12 | ||
|
|
332a5df61c | ||
|
|
2c4a83017c | ||
|
|
bde29aa2d6 |
No files matched your search
@@ -15,10 +15,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
|
||||
- name: Go 1.25
|
||||
uses: actions/setup-go@v5
|
||||
- name: Go 1.21
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ^1.25
|
||||
go-version: ^1.20
|
||||
id: go
|
||||
|
||||
- id: install-secret-key
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
gpg --list-secret-keys --keyid-format LONG
|
||||
|
||||
- name: Check Out Code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Git Fetch Tags
|
||||
run: git fetch --prune --unshallow --tags -f
|
||||
@@ -39,6 +39,9 @@ jobs:
|
||||
|
||||
|
||||
- name: Release binaries
|
||||
uses: softprops/action-gh-release@v2
|
||||
uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
files: ./cloudfox/*
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
prerelease: false
|
||||
files: |
|
||||
./cloudfox/*
|
||||
@@ -11,6 +11,6 @@ jobs:
|
||||
codespell:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
- run: pip install --user codespell
|
||||
- run: codespell --ignore-words-list="aks,referers,invokable" --skip="*.sum"
|
||||
@@ -110,7 +110,7 @@ Additional policy notes (as of 09/2022):
|
||||
|
||||
### GCP
|
||||
* Google Cloud SDK installed and authenticated
|
||||
* Application Default Credentials configured (`gcloud auth application-default login`)
|
||||
* Application Default Credentials configured (`gcloud auth application-default login`), or use `--key-file` / `--access-token` / `--impersonate-sa` flags
|
||||
* Recommended permissions at appropriate hierarchy levels (see below)
|
||||
|
||||
#### GCP Permissions: Minimal vs Comprehensive
|
||||
@@ -204,6 +204,50 @@ For detailed setup instructions, see the [GCP Setup Guide](https://github.com/Bi
|
||||
| Azure | [vms](https://github.com/BishopFox/cloudfox/wiki/Azure-Commands#vms) | Enumerates useful information for Compute instances in all available resource groups and subscriptions |
|
||||
|
||||
|
||||
# GCP Global Flags
|
||||
|
||||
All GCP commands support these flags:
|
||||
|
||||
### Authentication
|
||||
| Flag | Description |
|
||||
| - | - |
|
||||
| `--access-token` | Raw GCP access token for authentication (non-refreshable) |
|
||||
| `-i, --impersonate-sa` | Service account email to impersonate (requires `roles/iam.serviceAccountTokenCreator`) |
|
||||
| `-k, --key-file` | Path to a service account JSON key file for authentication |
|
||||
|
||||
If no authentication flags are provided, CloudFox uses Application Default Credentials (ADC). You can combine `--key-file` or `--access-token` with `--impersonate-sa` to impersonate using a specific base credential.
|
||||
|
||||
### Query Filtering
|
||||
| Flag | Description |
|
||||
| - | - |
|
||||
| `-q, --query` | Filter output rows by substring match or column-aware key:value syntax |
|
||||
| `--sqlquery` | Filter output with SQL-like WHERE clause syntax |
|
||||
|
||||
**`--query` examples:**
|
||||
```bash
|
||||
# Plain substring (matches any column)
|
||||
cloudfox gcp -p my-project iam --query "privesc02"
|
||||
|
||||
# Column-specific: Role contains "owner" AND Principal contains "privesc02"
|
||||
cloudfox gcp -p my-project serviceaccounts --query "Role:owner,Principal:privesc02"
|
||||
|
||||
# Negation: Role contains "owner" but Principal does NOT contain "compute"
|
||||
cloudfox gcp -p my-project serviceaccounts --query "Role:owner,!Principal:compute"
|
||||
|
||||
# Exact match on column value
|
||||
cloudfox gcp -p my-project iam --query "Role=roles/owner"
|
||||
```
|
||||
|
||||
**`--sqlquery` examples:**
|
||||
```bash
|
||||
cloudfox gcp -p my-project serviceaccounts \
|
||||
--sqlquery "Role CONTAINS owner AND Principal CONTAINS privesc02"
|
||||
|
||||
cloudfox gcp -p my-project iam --sqlquery "Source != Project"
|
||||
```
|
||||
|
||||
For full query filtering documentation, see the [GCP Commands Wiki - Query Filtering](https://github.com/BishopFox/cloudfox/wiki/GCP-Commands#query-filtering).
|
||||
|
||||
# GCP Commands
|
||||
|
||||
For detailed documentation on each GCP command, see the [GCP Commands Wiki](https://github.com/BishopFox/cloudfox/wiki/GCP-Commands).
|
||||
|
||||
+2
-2
@@ -161,7 +161,7 @@ func (m *AccessKeysModule) getAccessKeysForAllUsers() {
|
||||
ListUsers, err := sdk.CachedIamListUsers(m.IAMClient, aws.ToString(m.Caller.Account))
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
// added this to break out if there no users
|
||||
@@ -171,7 +171,7 @@ func (m *AccessKeysModule) getAccessKeysForAllUsers() {
|
||||
results, err := sdk.CachedIamListAccessKeys(m.IAMClient, aws.ToString(m.Caller.Account), aws.ToString(user.UserName))
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
+47
-47
@@ -191,19 +191,19 @@ func (m *ApiGwModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getAPIGatewayAPIsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getAPIGatewayVIPsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getAPIGatewayv2APIsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getAPIGatewayv2VIPsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -244,7 +244,7 @@ func (m *ApiGwModule) writeLoot(outputDirectory string, verbosity int) string {
|
||||
// err = os.WriteFile(f, []byte(out), 0644)
|
||||
// if err != nil {
|
||||
// m.modLog.Error(err.Error())
|
||||
// m.CommandCounter.IncrError()
|
||||
// m.CommandCounter.Error++
|
||||
// panic(err.Error())
|
||||
// }
|
||||
|
||||
@@ -263,8 +263,8 @@ func (m *ApiGwModule) writeLoot(outputDirectory string, verbosity int) string {
|
||||
|
||||
func (m *ApiGwModule) getAPIGatewayAPIsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan ApiGateway) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -272,21 +272,21 @@ func (m *ApiGwModule) getAPIGatewayAPIsPerRegion(r string, wg *sync.WaitGroup, s
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// 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.
|
||||
|
||||
Items, err := sdk.CachedApiGatewayGetRestAPIs(m.APIGatewayClient, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
for _, api := range Items {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
for _, endpoint := range m.getEndpointsPerAPIGateway(r, api) {
|
||||
dataReceiver <- endpoint
|
||||
}
|
||||
@@ -295,8 +295,8 @@ func (m *ApiGwModule) getAPIGatewayAPIsPerRegion(r string, wg *sync.WaitGroup, s
|
||||
|
||||
func (m *ApiGwModule) getAPIGatewayVIPsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan ApiGateway) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -304,16 +304,16 @@ func (m *ApiGwModule) getAPIGatewayVIPsPerRegion(r string, wg *sync.WaitGroup, s
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// 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.
|
||||
|
||||
Items, err := sdk.CachedApiGatewayGetRestAPIs(m.APIGatewayClient, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -322,7 +322,7 @@ func (m *ApiGwModule) getAPIGatewayVIPsPerRegion(r string, wg *sync.WaitGroup, s
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@ func (m *ApiGwModule) getAPIGatewayVIPsPerRegion(r string, wg *sync.WaitGroup, s
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -347,7 +347,7 @@ func (m *ApiGwModule) getAPIGatewayVIPsPerRegion(r string, wg *sync.WaitGroup, s
|
||||
|
||||
for _, api := range Items {
|
||||
if api.Id != nil && aws.ToString(api.Id) == aws.ToString(mapping.RestApiId) {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
|
||||
endpoints := m.getEndpointsPerAPIGateway(r, api)
|
||||
for _, endpoint := range endpoints {
|
||||
@@ -376,8 +376,8 @@ func (m *ApiGwModule) getAPIGatewayVIPsPerRegion(r string, wg *sync.WaitGroup, s
|
||||
|
||||
func (m *ApiGwModule) getEndpointsPerAPIGateway(r string, api apigatewayTypes.RestApi) []ApiGateway {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}()
|
||||
var gateways []ApiGateway
|
||||
|
||||
@@ -402,7 +402,7 @@ func (m *ApiGwModule) getEndpointsPerAPIGateway(r string, api apigatewayTypes.Re
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return gateways
|
||||
}
|
||||
|
||||
@@ -411,7 +411,7 @@ func (m *ApiGwModule) getEndpointsPerAPIGateway(r string, api apigatewayTypes.Re
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
for _, stage := range GetStages.Item {
|
||||
@@ -427,7 +427,7 @@ func (m *ApiGwModule) getEndpointsPerAPIGateway(r string, api apigatewayTypes.Re
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,8 +454,8 @@ func (m *ApiGwModule) getEndpointsPerAPIGateway(r string, api apigatewayTypes.Re
|
||||
|
||||
func (m *ApiGwModule) getAPIGatewayv2APIsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan ApiGateway) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -463,20 +463,20 @@ func (m *ApiGwModule) getAPIGatewayv2APIsPerRegion(r string, wg *sync.WaitGroup,
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// 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.
|
||||
|
||||
Items, err := sdk.CachedAPIGatewayv2GetAPIs(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
for _, api := range Items {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
for _, endpoint := range m.getEndpointsPerAPIGatewayv2(r, api) {
|
||||
dataReceiver <- endpoint
|
||||
}
|
||||
@@ -486,8 +486,8 @@ func (m *ApiGwModule) getAPIGatewayv2APIsPerRegion(r string, wg *sync.WaitGroup,
|
||||
|
||||
func (m *ApiGwModule) getAPIGatewayv2VIPsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan ApiGateway) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -495,16 +495,16 @@ func (m *ApiGwModule) getAPIGatewayv2VIPsPerRegion(r string, wg *sync.WaitGroup,
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// 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.
|
||||
|
||||
Items, err := sdk.CachedAPIGatewayv2GetAPIs(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -513,7 +513,7 @@ func (m *ApiGwModule) getAPIGatewayv2VIPsPerRegion(r string, wg *sync.WaitGroup,
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
for _, item := range GetDomainNames {
|
||||
@@ -524,7 +524,7 @@ func (m *ApiGwModule) getAPIGatewayv2VIPsPerRegion(r string, wg *sync.WaitGroup,
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -537,7 +537,7 @@ func (m *ApiGwModule) getAPIGatewayv2VIPsPerRegion(r string, wg *sync.WaitGroup,
|
||||
|
||||
for _, api := range Items {
|
||||
if api.ApiId != nil && aws.ToString(api.ApiId) == aws.ToString(mapping.ApiId) {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
endpoints := m.getEndpointsPerAPIGatewayv2(r, api)
|
||||
for _, endpoint := range endpoints {
|
||||
var old string
|
||||
@@ -568,8 +568,8 @@ func (m *ApiGwModule) getAPIGatewayv2VIPsPerRegion(r string, wg *sync.WaitGroup,
|
||||
|
||||
func (m *ApiGwModule) getEndpointsPerAPIGatewayv2(r string, api apigatewayV2Types.Api) []ApiGateway {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}()
|
||||
|
||||
var gateways []ApiGateway
|
||||
@@ -588,7 +588,7 @@ func (m *ApiGwModule) getEndpointsPerAPIGatewayv2(r string, api apigatewayV2Type
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
for _, stage := range GetStages {
|
||||
@@ -603,7 +603,7 @@ func (m *ApiGwModule) getEndpointsPerAPIGatewayv2(r string, api apigatewayV2Type
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
for _, stage := range stages {
|
||||
@@ -643,7 +643,7 @@ func (m *ApiGwModule) ApiGatewayApiKeyRequired(r string, ApiId *string, Resource
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s", r))
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
} else {
|
||||
return aws.ToBool(GetMethod.ApiKeyRequired)
|
||||
}
|
||||
|
||||
+7
-7
@@ -88,7 +88,7 @@ func (m *BucketsModule) PrintBuckets(outputDirectory string, verbosity int) {
|
||||
go m.Receiver(dataReceiver, receiverDone)
|
||||
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrPending()
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(wg, semaphore, dataReceiver)
|
||||
|
||||
wg.Wait()
|
||||
@@ -203,11 +203,11 @@ func (m *BucketsModule) Receiver(receiver chan BucketRow, receiverDone chan bool
|
||||
func (m *BucketsModule) executeChecks(wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan BucketRow) {
|
||||
defer wg.Done()
|
||||
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.createBucketsRows(m.output.Verbosity, wg, semaphore, dataReceiver)
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}
|
||||
|
||||
func (m *BucketsModule) writeLoot(outputDirectory string, verbosity int, profile string) {
|
||||
@@ -256,8 +256,8 @@ func (m *BucketsModule) writeLoot(outputDirectory string, verbosity int, profile
|
||||
|
||||
func (m *BucketsModule) createBucketsRows(verbosity int, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan BucketRow) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -357,7 +357,7 @@ func (m *BucketsModule) storeAccessPolicy(bucket *BucketRow) {
|
||||
|
||||
if err := m.storeFile(f, bucket.PolicyJSON); err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ func (m *CloudformationModule) PrintCloudformationStacks(outputDirectory string,
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrPending()
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
|
||||
}
|
||||
@@ -206,7 +206,7 @@ func (m *CloudformationModule) executeChecks(r string, wg *sync.WaitGroup, semap
|
||||
}
|
||||
for _, serviceRegion := range serviceRegions {
|
||||
if r == serviceRegion {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.createCFStackRowsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -231,7 +231,7 @@ func (m *CloudformationModule) writeLoot(outputDirectory string, verbosity int)
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
pullFile := filepath.Join(path, "cloudformation-data.txt")
|
||||
|
||||
@@ -271,7 +271,7 @@ func (m *CloudformationModule) writeLoot(outputDirectory string, verbosity int)
|
||||
err = os.WriteFile(pullFile, []byte(out), 0644)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
if verbosity > 2 {
|
||||
@@ -288,8 +288,8 @@ func (m *CloudformationModule) writeLoot(outputDirectory string, verbosity int)
|
||||
|
||||
func (m *CloudformationModule) createCFStackRowsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan CFStack) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -302,7 +302,7 @@ func (m *CloudformationModule) createCFStackRowsPerRegion(r string, wg *sync.Wai
|
||||
DescribeStacks, err := sdk.CachedCloudFormationDescribeStacks(m.CloudFormationClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
for _, stack := range DescribeStacks {
|
||||
@@ -314,7 +314,7 @@ func (m *CloudformationModule) createCFStackRowsPerRegion(r string, wg *sync.Wai
|
||||
stackTemplateBody, err = sdk.CachedCloudFormationGetTemplate(m.CloudFormationClient, aws.ToString(m.Caller.Account), r, stackName)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
dataReceiver <- CFStack{
|
||||
|
||||
+2
-2
@@ -87,7 +87,7 @@ func (m *CodeBuildModule) PrintCodeBuildProjects(outputDirectory string, verbosi
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrPending()
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
|
||||
}
|
||||
@@ -224,7 +224,7 @@ func (m *CodeBuildModule) executeChecks(r string, wg *sync.WaitGroup, semaphore
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getcodeBuildProjectsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
|
||||
+25
-25
@@ -260,7 +260,7 @@ func (m *DatabasesModule) executeCheck(check check) {
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
check.wg.Add(1)
|
||||
go check.executor(check.region, check.wg, check.semaphore, check.dataReceiver)
|
||||
}
|
||||
@@ -324,7 +324,7 @@ func (m *DatabasesModule) writeLoot(outputDirectory string, verbosity int) strin
|
||||
// err = os.WriteFile(f, []byte(out), 0644)
|
||||
// if err != nil {
|
||||
// m.modLog.Error(err.Error())
|
||||
// m.CommandCounter.IncrError()
|
||||
// m.CommandCounter.Error++
|
||||
// panic(err.Error())
|
||||
// }
|
||||
|
||||
@@ -343,8 +343,8 @@ func (m *DatabasesModule) writeLoot(outputDirectory string, verbosity int) strin
|
||||
|
||||
func (m *DatabasesModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -352,13 +352,13 @@ func (m *DatabasesModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup,
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
|
||||
DBClusters, err := sdk.CachedRDSDescribeDBClusters(m.RDSClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -411,8 +411,8 @@ func (m *DatabasesModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup,
|
||||
|
||||
func (m *DatabasesModule) getRdsInstancesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -420,14 +420,14 @@ func (m *DatabasesModule) getRdsInstancesPerRegion(r string, wg *sync.WaitGroup,
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
|
||||
DBInstances, err := sdk.CachedRDSDescribeDBInstances(m.RDSClient, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -480,8 +480,8 @@ func (m *DatabasesModule) getRdsInstancesPerRegion(r string, wg *sync.WaitGroup,
|
||||
|
||||
func (m *DatabasesModule) getRedshiftDatabasesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -489,9 +489,9 @@ func (m *DatabasesModule) getRedshiftDatabasesPerRegion(r string, wg *sync.WaitG
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
awsService := "Redshift"
|
||||
protocol := "https"
|
||||
|
||||
@@ -502,7 +502,7 @@ func (m *DatabasesModule) getRedshiftDatabasesPerRegion(r string, wg *sync.WaitG
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation()))
|
||||
}
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -532,8 +532,8 @@ func (m *DatabasesModule) getRedshiftDatabasesPerRegion(r string, wg *sync.WaitG
|
||||
|
||||
func (m *DatabasesModule) getDynamoDBTablesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Database) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -541,15 +541,15 @@ func (m *DatabasesModule) getDynamoDBTablesPerRegion(r string, wg *sync.WaitGrou
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
awsService := "DynamoDB"
|
||||
|
||||
Tables, err := sdk.CachedDynamoDBListTables(m.DynamoDBClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -558,7 +558,7 @@ func (m *DatabasesModule) getDynamoDBTablesPerRegion(r string, wg *sync.WaitGrou
|
||||
TableOutput, err := sdk.CachedDynamoDBDescribeTable(m.DynamoDBClient, aws.ToString(m.Caller.Account), r, table)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
size := aws.ToInt64(TableOutput.TableSizeBytes)
|
||||
|
||||
@@ -84,7 +84,7 @@ func (m *DirectoryModule) PrintDirectories(outputDirectory string, verbosity int
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrPending()
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
|
||||
}
|
||||
@@ -203,7 +203,7 @@ func (m *DirectoryModule) executeChecks(r string, wg *sync.WaitGroup, semaphore
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getDirectoriesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -223,8 +223,8 @@ func (m *DirectoryModule) Receiver(receiver chan Directory, receiverDone chan bo
|
||||
}
|
||||
func (m *DirectoryModule) getDirectoriesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Directory) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
|
||||
+11
-11
@@ -84,7 +84,7 @@ func (m *ECRModule) PrintECR(outputDirectory string, verbosity int) {
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrPending()
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
|
||||
}
|
||||
@@ -210,7 +210,7 @@ func (m *ECRModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan s
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getECRRecordsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -234,7 +234,7 @@ func (m *ECRModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
pullFile := filepath.Join(path, "ecr-pull-commands.txt")
|
||||
|
||||
@@ -259,7 +259,7 @@ func (m *ECRModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
err = os.WriteFile(pullFile, []byte(out), 0644)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
if verbosity > 2 {
|
||||
@@ -277,8 +277,8 @@ func (m *ECRModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
|
||||
func (m *ECRModule) getECRRecordsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Repository) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -290,7 +290,7 @@ func (m *ECRModule) getECRRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
|
||||
Repositories, err := sdk.CachedECRDescribeRepositories(m.ECRClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -302,7 +302,7 @@ func (m *ECRModule) getECRRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
|
||||
images, err := sdk.CachedECRDescribeImages(m.ECRClient, aws.ToString(m.Caller.Account), r, repoName)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ func (m *ECRModule) getECRRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
|
||||
// var repositories []types.Repository
|
||||
// Repositories, err := sdk.CachedECRDescribeRepositories(m.ECRClient, aws.ToString(m.Caller.Account), r)
|
||||
// if err != nil {
|
||||
// m.CommandCounter.IncrError()
|
||||
// m.CommandCounter.Error++
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
@@ -364,7 +364,7 @@ func (m *ECRModule) getECRRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
|
||||
|
||||
// ImageDetails, err := sdk.CachedECRDescribeImages(m.ECRClient, aws.ToString(m.Caller.Account), r, repoName)
|
||||
// if err != nil {
|
||||
// m.CommandCounter.IncrError()
|
||||
// m.CommandCounter.Error++
|
||||
// return nil, err
|
||||
// }
|
||||
// images = append(images, ImageDetails...)
|
||||
@@ -375,7 +375,7 @@ func (m *ECRModule) getECRRepositoryPolicy(r string, repository string) (policy.
|
||||
var repoPolicy policy.Policy
|
||||
Policy, err := sdk.CachedECRGetRepositoryPolicy(m.ECRClient, aws.ToString(m.Caller.Account), r, repository)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return repoPolicy, err
|
||||
}
|
||||
repoPolicy, err = policy.ParseJSONPolicy([]byte(Policy))
|
||||
|
||||
+18
-18
@@ -100,7 +100,7 @@ func (m *ECSTasksModule) ECSTasks(outputDirectory string, verbosity int) {
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrPending()
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, dataReceiver)
|
||||
|
||||
}
|
||||
@@ -254,7 +254,7 @@ func (m *ECSTasksModule) writeLoot(outputDirectory string) {
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
privateIPsFilename := filepath.Join(path, "ecs-tasks-PrivateIPs.txt")
|
||||
publicIPsFilename := filepath.Join(path, "ecs-tasks-PublicIPs.txt")
|
||||
@@ -274,12 +274,12 @@ func (m *ECSTasksModule) writeLoot(outputDirectory string) {
|
||||
err = os.WriteFile(privateIPsFilename, []byte(privateIPs), 0644)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
err = os.WriteFile(publicIPsFilename, []byte(publicIPs), 0644)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
for _, task := range m.MappedECSTasks {
|
||||
@@ -288,14 +288,14 @@ func (m *ECSTasksModule) writeLoot(outputDirectory string) {
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
taskDefinitionFilename := filepath.Join(path, task.TaskDefinitionName+".json")
|
||||
|
||||
err = os.WriteFile(taskDefinitionFilename, []byte(task.TaskDefinitionContent), 0644)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -321,12 +321,12 @@ func (m *ECSTasksModule) executeChecks(r string, wg *sync.WaitGroup, dataReceive
|
||||
}
|
||||
if res {
|
||||
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
m.getListClusters(r, dataReceiver)
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,7 +335,7 @@ func (m *ECSTasksModule) getListClusters(region string, dataReceiver chan Mapped
|
||||
ClusterArns, err := sdk.CachedECSListClusters(m.ECSClient, aws.ToString(m.Caller.Account), region)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -349,7 +349,7 @@ func (m *ECSTasksModule) getListTasks(clusterARN string, region string, dataRece
|
||||
TaskArns, err := sdk.CachedECSListTasks(m.ECSClient, aws.ToString(m.Caller.Account), region, clusterARN)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -374,7 +374,7 @@ func (m *ECSTasksModule) loadTasksData(clusterARN string, taskARNs []string, reg
|
||||
Tasks, err := sdk.CachedECSDescribeTasks(m.ECSClient, aws.ToString(m.Caller.Account), region, clusterARN, taskARNs)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -388,7 +388,7 @@ func (m *ECSTasksModule) loadTasksData(clusterARN string, taskARNs []string, reg
|
||||
publicIPs, err := m.loadPublicIPs(eniIDs, region)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -397,7 +397,7 @@ func (m *ECSTasksModule) loadTasksData(clusterARN string, taskARNs []string, reg
|
||||
taskDefinition, err := sdk.CachedECSDescribeTaskDefinition(m.ECSClient, aws.ToString(m.Caller.Account), region, aws.ToString(task.TaskDefinitionArn))
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
mappedTask := MappedECSTask{
|
||||
@@ -446,7 +446,7 @@ func (m *ECSTasksModule) describeTaskDefinition(taskDefinitionArn string, region
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return types.TaskDefinition{}, err
|
||||
}
|
||||
return *DescribeTaskDefinition.TaskDefinition, nil
|
||||
@@ -466,7 +466,7 @@ func (m *ECSTasksModule) loadAllPublicIPs(eniIDs []string, region string) (map[s
|
||||
publicIPs, err := m.loadPublicIPs(eniIDs[i:j], region)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return nil, fmt.Errorf("getting elastic network interfaces: %s", err)
|
||||
}
|
||||
|
||||
|
||||
+9
-9
@@ -102,7 +102,7 @@ func (m *EKSModule) EKS(outputDirectory string, verbosity int) {
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrPending()
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
|
||||
}
|
||||
@@ -255,7 +255,7 @@ func (m *EKSModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan s
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getEKSRecordsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -279,7 +279,7 @@ func (m *EKSModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
pullFile := filepath.Join(path, "eks-kubeconfig-commands.txt")
|
||||
|
||||
@@ -303,7 +303,7 @@ func (m *EKSModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
err = os.WriteFile(pullFile, []byte(out), 0644)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
if verbosity > 2 {
|
||||
@@ -322,8 +322,8 @@ func (m *EKSModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
|
||||
func (m *EKSModule) getEKSRecordsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Cluster) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -336,7 +336,7 @@ func (m *EKSModule) getEKSRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
|
||||
clusters, err := sdk.CachedEKSListClusters(m.EKSClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -347,7 +347,7 @@ func (m *EKSModule) getEKSRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
|
||||
clusterDetails, err := sdk.CachedEKSDescribeCluster(m.EKSClient, aws.ToString(m.Caller.Account), clusterName, r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
//nodeGroups = append(nodeGroups, DescribeCluster.Cluster.)
|
||||
@@ -373,7 +373,7 @@ func (m *EKSModule) getEKSRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
|
||||
nodeGroupDetails, err := sdk.CachedEKSDescribeNodeGroup(m.EKSClient, aws.ToString(m.Caller.Account), clusterName, nodeGroup, r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
role = aws.ToString(nodeGroupDetails.NodeRole)
|
||||
|
||||
@@ -73,7 +73,7 @@ func (m *ElasticNetworkInterfacesModule) ElasticNetworkInterfaces(outputDirector
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrPending()
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, dataReceiver)
|
||||
|
||||
}
|
||||
@@ -199,7 +199,7 @@ func (m *ElasticNetworkInterfacesModule) writeLoot(outputDirectory string) {
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
privateIPsFilename := filepath.Join(path, "elastic-network-interfaces-PrivateIPs.txt")
|
||||
publicIPsFilename := filepath.Join(path, "elastic-network-interfaces-PublicIPs.txt")
|
||||
@@ -219,12 +219,12 @@ func (m *ElasticNetworkInterfacesModule) writeLoot(outputDirectory string) {
|
||||
err = os.WriteFile(privateIPsFilename, []byte(privateIPs), 0644)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
err = os.WriteFile(publicIPsFilename, []byte(publicIPs), 0644)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), privateIPsFilename)
|
||||
@@ -246,12 +246,12 @@ func (m *ElasticNetworkInterfacesModule) executeChecks(r string, wg *sync.WaitGr
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
m.getDescribeNetworkInterfaces(r, dataReceiver)
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ func (m *ElasticNetworkInterfacesModule) getDescribeNetworkInterfaces(region str
|
||||
// )
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+134
-134
@@ -280,7 +280,7 @@ func (m *EndpointsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getLambdaFunctionsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -289,7 +289,7 @@ func (m *EndpointsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getEksClustersPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -298,7 +298,7 @@ func (m *EndpointsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getMqBrokersPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -307,7 +307,7 @@ func (m *EndpointsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getOpenSearchPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -316,7 +316,7 @@ func (m *EndpointsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getGrafanaEndPointsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -325,11 +325,11 @@ func (m *EndpointsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getELBv2ListenersPerRegion(r, wg, semaphore, dataReceiver)
|
||||
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getELBListenersPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -338,19 +338,19 @@ func (m *EndpointsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getAPIGatewayAPIsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getAPIGatewayVIPsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getAPIGatewayv2APIsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getAPIGatewayv2VIPsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -359,7 +359,7 @@ func (m *EndpointsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getRdsClustersPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -368,13 +368,13 @@ func (m *EndpointsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getRedshiftEndPointsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
|
||||
//apprunner is not supported by the aws json so we have to call it in every region
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getAppRunnerEndpointsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
|
||||
@@ -383,7 +383,7 @@ func (m *EndpointsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getLightsailContainerEndpointsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -394,7 +394,7 @@ func (m *EndpointsModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
panic(err.Error())
|
||||
}
|
||||
f := filepath.Join(path, "endpoints-UrlsOnly.txt")
|
||||
@@ -408,7 +408,7 @@ func (m *EndpointsModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
err = os.WriteFile(f, []byte(out), 0644)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
@@ -425,8 +425,8 @@ func (m *EndpointsModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
|
||||
func (m *EndpointsModule) getLambdaFunctionsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Endpoint) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -434,16 +434,16 @@ func (m *EndpointsModule) getLambdaFunctionsPerRegion(r string, wg *sync.WaitGro
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// 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 public string
|
||||
|
||||
Functions, err := sdk.CachedLambdaListFunctions(m.LambdaClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -455,7 +455,7 @@ func (m *EndpointsModule) getLambdaFunctionsPerRegion(r string, wg *sync.WaitGro
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation()))
|
||||
}
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
continue
|
||||
}
|
||||
endpoint := aws.ToString(FunctionDetails.FunctionUrl)
|
||||
@@ -482,8 +482,8 @@ func (m *EndpointsModule) getLambdaFunctionsPerRegion(r string, wg *sync.WaitGro
|
||||
|
||||
func (m *EndpointsModule) getEksClustersPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Endpoint) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -491,15 +491,15 @@ func (m *EndpointsModule) getEksClustersPerRegion(r string, wg *sync.WaitGroup,
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// 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.
|
||||
|
||||
Clusters, err := sdk.CachedEKSListClusters(m.EKSClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -511,7 +511,7 @@ func (m *EndpointsModule) getEksClustersPerRegion(r string, wg *sync.WaitGroup,
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation()))
|
||||
}
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
continue
|
||||
}
|
||||
var endpoint string
|
||||
@@ -545,8 +545,8 @@ func (m *EndpointsModule) getEksClustersPerRegion(r string, wg *sync.WaitGroup,
|
||||
|
||||
func (m *EndpointsModule) getMqBrokersPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Endpoint) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -554,14 +554,14 @@ func (m *EndpointsModule) getMqBrokersPerRegion(r string, wg *sync.WaitGroup, se
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
|
||||
BrokerSummaries, err := sdk.CachedMQListBrokers(m.MQClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -584,7 +584,7 @@ func (m *EndpointsModule) getMqBrokersPerRegion(r string, wg *sync.WaitGroup, se
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation()))
|
||||
}
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
continue
|
||||
}
|
||||
if aws.ToBool(BrokerDetails.PubliclyAccessible) {
|
||||
@@ -611,8 +611,8 @@ func (m *EndpointsModule) getMqBrokersPerRegion(r string, wg *sync.WaitGroup, se
|
||||
|
||||
func (m *EndpointsModule) getOpenSearchPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Endpoint) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -620,14 +620,14 @@ func (m *EndpointsModule) getOpenSearchPerRegion(r string, wg *sync.WaitGroup, s
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
|
||||
DomainNames, err := sdk.CachedOpenSearchListDomainNames(m.OpenSearchClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -641,7 +641,7 @@ func (m *EndpointsModule) getOpenSearchPerRegion(r string, wg *sync.WaitGroup, s
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation()))
|
||||
}
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -663,7 +663,7 @@ func (m *EndpointsModule) getOpenSearchPerRegion(r string, wg *sync.WaitGroup, s
|
||||
domainConfig, err := sdk.CachedOpenSearchDescribeDomainConfig(m.OpenSearchClient, aws.ToString(m.Caller.Account), r, name)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
if aws.ToBool(domainConfig.AdvancedSecurityOptions.Options.Enabled) {
|
||||
@@ -697,8 +697,8 @@ func (m *EndpointsModule) getOpenSearchPerRegion(r string, wg *sync.WaitGroup, s
|
||||
|
||||
func (m *EndpointsModule) getGrafanaEndPointsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Endpoint) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -706,14 +706,14 @@ func (m *EndpointsModule) getGrafanaEndPointsPerRegion(r string, wg *sync.WaitGr
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
|
||||
ListWorkspaces, err := sdk.CachedGrafanaListWorkspaces(m.GrafanaClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -743,8 +743,8 @@ func (m *EndpointsModule) getGrafanaEndPointsPerRegion(r string, wg *sync.WaitGr
|
||||
|
||||
func (m *EndpointsModule) getELBv2ListenersPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Endpoint) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -752,16 +752,16 @@ func (m *EndpointsModule) getELBv2ListenersPerRegion(r string, wg *sync.WaitGrou
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// 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.
|
||||
awsService := "ELBv2"
|
||||
|
||||
LoadBalancers, err := sdk.CachedELBv2DescribeLoadBalancers(m.ELBv2Client, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -787,7 +787,7 @@ func (m *EndpointsModule) getELBv2ListenersPerRegion(r string, wg *sync.WaitGrou
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation()))
|
||||
}
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
continue
|
||||
}
|
||||
if scheme == "internet-facing" {
|
||||
@@ -823,8 +823,8 @@ func (m *EndpointsModule) getELBv2ListenersPerRegion(r string, wg *sync.WaitGrou
|
||||
|
||||
func (m *EndpointsModule) getELBListenersPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Endpoint) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -832,16 +832,16 @@ func (m *EndpointsModule) getELBListenersPerRegion(r string, wg *sync.WaitGroup,
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
awsService := "ELB"
|
||||
|
||||
LoadBalancerDescriptions, err := sdk.CachedELBDescribeLoadBalancers(m.ELBClient, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
var public string
|
||||
@@ -883,8 +883,8 @@ func (m *EndpointsModule) getELBListenersPerRegion(r string, wg *sync.WaitGroup,
|
||||
|
||||
func (m *EndpointsModule) getAPIGatewayAPIsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Endpoint) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -892,16 +892,16 @@ func (m *EndpointsModule) getAPIGatewayAPIsPerRegion(r string, wg *sync.WaitGrou
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// 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.
|
||||
|
||||
Items, err := sdk.CachedApiGatewayGetRestAPIs(m.APIGatewayClient, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -914,8 +914,8 @@ func (m *EndpointsModule) getAPIGatewayAPIsPerRegion(r string, wg *sync.WaitGrou
|
||||
|
||||
func (m *EndpointsModule) getAPIGatewayVIPsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Endpoint) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -923,15 +923,15 @@ func (m *EndpointsModule) getAPIGatewayVIPsPerRegion(r string, wg *sync.WaitGrou
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
|
||||
Items, err := sdk.CachedApiGatewayGetRestAPIs(m.APIGatewayClient, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -942,7 +942,7 @@ func (m *EndpointsModule) getAPIGatewayVIPsPerRegion(r string, wg *sync.WaitGrou
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation()))
|
||||
}
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
for _, item := range GetDomainNames {
|
||||
@@ -955,7 +955,7 @@ func (m *EndpointsModule) getAPIGatewayVIPsPerRegion(r string, wg *sync.WaitGrou
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation()))
|
||||
}
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1020,7 +1020,7 @@ func (m *EndpointsModule) getEndpointsPerAPIGateway(r string, api apigatewayType
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation()))
|
||||
}
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
GetResources, err := sdk.CachedApiGatewayGetResources(m.APIGatewayClient, aws.ToString(m.Caller.Account), r, id)
|
||||
@@ -1030,7 +1030,7 @@ func (m *EndpointsModule) getEndpointsPerAPIGateway(r string, api apigatewayType
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation()))
|
||||
}
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
for _, stage := range GetStages.Item {
|
||||
@@ -1059,8 +1059,8 @@ func (m *EndpointsModule) getEndpointsPerAPIGateway(r string, api apigatewayType
|
||||
|
||||
func (m *EndpointsModule) getAPIGatewayv2APIsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Endpoint) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -1068,16 +1068,16 @@ func (m *EndpointsModule) getAPIGatewayv2APIsPerRegion(r string, wg *sync.WaitGr
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// 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.
|
||||
|
||||
Items, err := sdk.CachedAPIGatewayv2GetAPIs(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
for _, api := range Items {
|
||||
@@ -1090,8 +1090,8 @@ func (m *EndpointsModule) getAPIGatewayv2APIsPerRegion(r string, wg *sync.WaitGr
|
||||
|
||||
func (m *EndpointsModule) getAPIGatewayv2VIPsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Endpoint) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -1099,15 +1099,15 @@ func (m *EndpointsModule) getAPIGatewayv2VIPsPerRegion(r string, wg *sync.WaitGr
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
|
||||
Items, err := sdk.CachedAPIGatewayv2GetAPIs(m.APIGatewayv2Client, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1118,7 +1118,7 @@ func (m *EndpointsModule) getAPIGatewayv2VIPsPerRegion(r string, wg *sync.WaitGr
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation()))
|
||||
}
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
for _, item := range GetDomainNames {
|
||||
@@ -1131,7 +1131,7 @@ func (m *EndpointsModule) getAPIGatewayv2VIPsPerRegion(r string, wg *sync.WaitGr
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation()))
|
||||
}
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1193,7 +1193,7 @@ func (m *EndpointsModule) getEndpointsPerAPIGatewayv2(r string, api apigatewayV2
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation()))
|
||||
}
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
for _, stage := range GetStages {
|
||||
@@ -1211,7 +1211,7 @@ func (m *EndpointsModule) getEndpointsPerAPIGatewayv2(r string, api apigatewayV2
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation()))
|
||||
}
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
for _, stage := range stages {
|
||||
@@ -1246,8 +1246,8 @@ func (m *EndpointsModule) getEndpointsPerAPIGatewayv2(r string, api apigatewayV2
|
||||
|
||||
func (m *EndpointsModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Endpoint) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -1255,9 +1255,9 @@ func (m *EndpointsModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup,
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
|
||||
DBInstances, err := sdk.CachedRDSDescribeDBInstances(m.RDSClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
@@ -1265,7 +1265,7 @@ func (m *EndpointsModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup,
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation()))
|
||||
}
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1300,8 +1300,8 @@ func (m *EndpointsModule) getRdsClustersPerRegion(r string, wg *sync.WaitGroup,
|
||||
|
||||
func (m *EndpointsModule) getRedshiftEndPointsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Endpoint) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -1309,9 +1309,9 @@ func (m *EndpointsModule) getRedshiftEndPointsPerRegion(r string, wg *sync.WaitG
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
awsService := "Redshift"
|
||||
protocol := "https"
|
||||
|
||||
@@ -1323,7 +1323,7 @@ func (m *EndpointsModule) getRedshiftEndPointsPerRegion(r string, wg *sync.WaitG
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation()))
|
||||
}
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1360,8 +1360,8 @@ UNUSED CODE - PLEASE REVIEW AND DELETE IF IT DOESN'T APPLY
|
||||
|
||||
func (m *EndpointsModule) getS3EndpointsPerRegion(wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Endpoint) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -1369,9 +1369,9 @@ UNUSED CODE - PLEASE REVIEW AND DELETE IF IT DOESN'T APPLY
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
|
||||
// This for loop exits at the end dependeding on whether the output hits its last page (see pagination control block at the end of the loop).
|
||||
ListBuckets, _ := m.S3Client.ListBuckets(
|
||||
@@ -1434,8 +1434,8 @@ UNUSED CODE - PLEASE REVIEW AND DELETE IF IT DOESN'T APPLY
|
||||
*/
|
||||
func (m *EndpointsModule) getCloudfrontEndpoints(wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Endpoint) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -1443,9 +1443,9 @@ func (m *EndpointsModule) getCloudfrontEndpoints(wg *sync.WaitGroup, semaphore c
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// 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 awsService = "Cloudfront"
|
||||
@@ -1466,7 +1466,7 @@ func (m *EndpointsModule) getCloudfrontEndpoints(wg *sync.WaitGroup, semaphore c
|
||||
m.Errors = append(m.Errors, fmt.Sprintf(" Error: Region: %s, Service: %s, Operation: %s", r, oe.Service(), oe.Operation()))
|
||||
}
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
if ListDistributions.DistributionList.Quantity == nil {
|
||||
@@ -1546,8 +1546,8 @@ func (m *EndpointsModule) getCloudfrontEndpoints(wg *sync.WaitGroup, semaphore c
|
||||
|
||||
func (m *EndpointsModule) getAppRunnerEndpointsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Endpoint) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -1555,15 +1555,15 @@ func (m *EndpointsModule) getAppRunnerEndpointsPerRegion(r string, wg *sync.Wait
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
|
||||
ServiceSummaryList, err := sdk.CachedAppRunnerListServices(m.AppRunnerClient, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
for _, service := range ServiceSummaryList {
|
||||
@@ -1590,7 +1590,7 @@ func (m *EndpointsModule) getAppRunnerEndpointsPerRegion(r string, wg *sync.Wait
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1614,7 +1614,7 @@ func (m *EndpointsModule) getAppRunnerEndpointsPerRegion(r string, wg *sync.Wait
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
if DescribeCustomDomains.DNSTarget != nil {
|
||||
@@ -1667,8 +1667,8 @@ func (m *EndpointsModule) appRunnerDescribeCustomDomain(r string, serviceArn str
|
||||
|
||||
func (m *EndpointsModule) getLightsailContainerEndpointsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Endpoint) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -1676,9 +1676,9 @@ func (m *EndpointsModule) getLightsailContainerEndpointsPerRegion(r string, wg *
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
var public = "True"
|
||||
var protocol = "https"
|
||||
var port int32 = 443
|
||||
@@ -1687,7 +1687,7 @@ func (m *EndpointsModule) getLightsailContainerEndpointsPerRegion(r string, wg *
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
if len(containerServices) > 0 {
|
||||
|
||||
+45
-45
@@ -105,7 +105,7 @@ func (m *EnvsModule) PrintEnvs(outputDirectory string, verbosity int) {
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrPending()
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
|
||||
}
|
||||
@@ -290,7 +290,7 @@ func (m *EnvsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan
|
||||
}
|
||||
res, _ := servicemap.IsServiceInRegion("ecs", r)
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getECSEnvironmentVariablesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -300,13 +300,13 @@ func (m *EnvsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getLambdaEnvironmentVariablesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
|
||||
// AppRunner is not supported in the aws service region catalog so we have to run it in all regions
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getAppRunnerEnvironmentVariablesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
|
||||
@@ -315,7 +315,7 @@ func (m *EnvsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getLightsailEnvironmentVariablesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -325,7 +325,7 @@ func (m *EnvsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getSagemakerEnvironmentVariablesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -334,8 +334,8 @@ func (m *EnvsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan
|
||||
|
||||
func (m *EnvsModule) getECSEnvironmentVariablesPerRegion(region string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan EnvironmentVariable) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -343,9 +343,9 @@ func (m *EnvsModule) getECSEnvironmentVariablesPerRegion(region string, wg *sync
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// 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 PaginationMarker *string
|
||||
|
||||
@@ -364,7 +364,7 @@ func (m *EnvsModule) getECSEnvironmentVariablesPerRegion(region string, wg *sync
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
for _, containerDefinition := range DescribeTaskDefinition.TaskDefinition.ContainerDefinitions {
|
||||
@@ -390,7 +390,7 @@ func (m *EnvsModule) getTaskDefinitionFamilies(region string) []string {
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -425,8 +425,8 @@ func (m *EnvsModule) getECSEnvironmentVariablesPerDefinition(taskDefinition ecsT
|
||||
|
||||
func (m *EnvsModule) getLambdaEnvironmentVariablesPerRegion(region string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan EnvironmentVariable) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -434,14 +434,14 @@ func (m *EnvsModule) getLambdaEnvironmentVariablesPerRegion(region string, wg *s
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
|
||||
Functions, err := sdk.CachedLambdaListFunctions(m.LambdaClient, aws.ToString(m.Caller.Account), region)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -467,8 +467,8 @@ func (m *EnvsModule) getLambdaEnvironmentVariablesPerFunction(function lambdaTyp
|
||||
|
||||
func (m *EnvsModule) getAppRunnerEnvironmentVariablesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan EnvironmentVariable) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -476,16 +476,16 @@ func (m *EnvsModule) getAppRunnerEnvironmentVariablesPerRegion(r string, wg *syn
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
|
||||
ServiceSummaryList, err := sdk.CachedAppRunnerListServices(m.AppRunnerClient, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
if err != nil {
|
||||
//modLog.Error(err.Error())
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
if len(ServiceSummaryList) > 0 {
|
||||
@@ -506,7 +506,7 @@ func (m *EnvsModule) getAppRunnerEnvironmentVariablesPerRegion(r string, wg *syn
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
|
||||
}
|
||||
@@ -530,8 +530,8 @@ func (m *EnvsModule) getAppRunnerEnvironmentVariablesPerRegion(r string, wg *syn
|
||||
|
||||
func (m *EnvsModule) getLightsailEnvironmentVariablesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan EnvironmentVariable) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -539,16 +539,16 @@ func (m *EnvsModule) getLightsailEnvironmentVariablesPerRegion(r string, wg *syn
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
awsService := "Lightsail [Container]"
|
||||
|
||||
ContainerServices, err := sdk.CachedLightsailGetContainerServices(m.LightsailClient, aws.ToString(m.Caller.Account), r)
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -574,8 +574,8 @@ func (m *EnvsModule) getLightsailEnvironmentVariablesPerRegion(r string, wg *syn
|
||||
|
||||
func (m *EnvsModule) getSagemakerEnvironmentVariablesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan EnvironmentVariable) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -583,9 +583,9 @@ func (m *EnvsModule) getSagemakerEnvironmentVariablesPerRegion(r string, wg *syn
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
awsService := "Sagemaker"
|
||||
|
||||
var PaginationControl *string
|
||||
@@ -603,7 +603,7 @@ func (m *EnvsModule) getSagemakerEnvironmentVariablesPerRegion(r string, wg *syn
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -621,7 +621,7 @@ func (m *EnvsModule) getSagemakerEnvironmentVariablesPerRegion(r string, wg *syn
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -665,7 +665,7 @@ func (m *EnvsModule) getSagemakerEnvironmentVariablesPerRegion(r string, wg *syn
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -683,7 +683,7 @@ func (m *EnvsModule) getSagemakerEnvironmentVariablesPerRegion(r string, wg *syn
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -726,7 +726,7 @@ func (m *EnvsModule) getSagemakerEnvironmentVariablesPerRegion(r string, wg *syn
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -744,7 +744,7 @@ func (m *EnvsModule) getSagemakerEnvironmentVariablesPerRegion(r string, wg *syn
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -788,7 +788,7 @@ func (m *EnvsModule) getSagemakerEnvironmentVariablesPerRegion(r string, wg *syn
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -805,7 +805,7 @@ func (m *EnvsModule) getSagemakerEnvironmentVariablesPerRegion(r string, wg *syn
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
+16
-16
@@ -245,7 +245,7 @@ func (m *FilesystemsModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
f := filepath.Join(path, "filesystems-mount-commands.txt")
|
||||
|
||||
@@ -283,7 +283,7 @@ func (m *FilesystemsModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), f)
|
||||
if verbosity > 2 {
|
||||
@@ -297,20 +297,20 @@ func (m *FilesystemsModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
|
||||
func (m *FilesystemsModule) getEFSSharesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan FilesystemObject) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
var policy string
|
||||
|
||||
DescribeFileSystems, err := sdk.CachedDescribeFileSystems(m.EFSClient, aws.ToString(m.Caller.Account), r)
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -336,7 +336,7 @@ func (m *FilesystemsModule) getEFSSharesPerRegion(r string, wg *sync.WaitGroup,
|
||||
DescribeMountTargets, err := sdk.CachedDescribeMountTargets(m.EFSClient, aws.ToString(m.Caller.Account), r, id)
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -349,7 +349,7 @@ func (m *FilesystemsModule) getEFSSharesPerRegion(r string, wg *sync.WaitGroup,
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
dataReceiver <- FilesystemObject{
|
||||
AWSService: awsService,
|
||||
Region: r,
|
||||
@@ -381,14 +381,14 @@ func (m *FilesystemsModule) getEFSSharesPerRegion(r string, wg *sync.WaitGroup,
|
||||
|
||||
func (m *FilesystemsModule) getFSxSharesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan FilesystemObject) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
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 PaginationMarker *string
|
||||
var PaginationMarker2 *string
|
||||
@@ -410,7 +410,7 @@ func (m *FilesystemsModule) getFSxSharesPerRegion(r string, wg *sync.WaitGroup,
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
+16
-16
@@ -276,7 +276,7 @@ func (m *IamSimulatorModule) writeLoot(outputDirectory string, verbosity int, pm
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
outFile := filepath.Join(path, "iam-simulator-pmapper-commands.txt")
|
||||
@@ -292,7 +292,7 @@ func (m *IamSimulatorModule) writeLoot(outputDirectory string, verbosity int, pm
|
||||
err = os.WriteFile(outFile, []byte(out), 0644)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
if verbosity > 2 {
|
||||
@@ -329,18 +329,18 @@ func (m *IamSimulatorModule) executeChecks(wg *sync.WaitGroup, resource string,
|
||||
func (m *IamSimulatorModule) getIAMUsers(wg *sync.WaitGroup, actions []string, resource string, dataReceiver chan SimulatorResult) {
|
||||
defer func() {
|
||||
wg.Done()
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}()
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
|
||||
ListUsers, err := sdk.CachedIamListUsers(m.IAMClient, aws.ToString(m.Caller.Account))
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -369,19 +369,19 @@ func (m *IamSimulatorModule) getIAMUsers(wg *sync.WaitGroup, actions []string, r
|
||||
func (m *IamSimulatorModule) getIAMRoles(wg *sync.WaitGroup, actions []string, resource string, dataReceiver chan SimulatorResult) {
|
||||
defer func() {
|
||||
wg.Done()
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}()
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
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.
|
||||
|
||||
ListRoles, err := sdk.CachedIamListRoles(m.IAMClient, aws.ToString(m.Caller.Account))
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
|
||||
}
|
||||
@@ -419,7 +419,7 @@ func (m *IamSimulatorModule) getPolicySimulatorResult(principal *string, actionN
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
m.modLog.Error(fmt.Sprintf("Failed to query actions for %s\n\n", aws.ToString(principal)))
|
||||
return
|
||||
}
|
||||
@@ -464,7 +464,7 @@ func (m *IamSimulatorModule) isPrincipalAnAdmin(principal *string) bool {
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
m.modLog.Errorf("Failed admin check on %s", aws.ToString(principal))
|
||||
return false
|
||||
}
|
||||
|
||||
+21
-23
@@ -122,19 +122,15 @@ func (m *InstancesModule) Instances(filter string, outputDirectory string, verbo
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrPending()
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(instancesToSearch, region, wg, dataReceiver)
|
||||
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
//time.Sleep(time.Second * 2)
|
||||
|
||||
// Signal the receiver to stop and wait for it to finish draining the channel.
|
||||
// This must happen BEFORE role analysis to ensure all MappedInstances are collected.
|
||||
receiverDone <- true
|
||||
<-receiverDone
|
||||
|
||||
// Perform role analysis (all instances are now in m.MappedInstances)
|
||||
// Perform role analysis
|
||||
if m.pmapperError == nil {
|
||||
for i := range m.MappedInstances {
|
||||
m.MappedInstances[i].Admin, m.MappedInstances[i].CanPrivEsc = GetPmapperResults(m.SkipAdminCheck, m.pmapperMod, &m.MappedInstances[i].Role)
|
||||
@@ -148,6 +144,8 @@ func (m *InstancesModule) Instances(filter string, outputDirectory string, verbo
|
||||
// Send a message to the spinner goroutine to close the channel and stop
|
||||
spinnerDone <- true
|
||||
<-spinnerDone
|
||||
receiverDone <- true
|
||||
<-receiverDone
|
||||
|
||||
// This conditional block will either dump the userData attribute content or the general instances data, depending on what you select via command line.
|
||||
//fmt.Printf("\n[*] Preparing output...\n\n")
|
||||
@@ -182,7 +180,7 @@ func (m *InstancesModule) printInstancesUserDataAttributesOnly(outputDirectory s
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
userDataFileName := filepath.Join(path, fmt.Sprintf("%s.txt", m.output.CallingModule))
|
||||
|
||||
@@ -214,7 +212,7 @@ func (m *InstancesModule) printInstancesUserDataAttributesOnly(outputDirectory s
|
||||
err = os.WriteFile(userDataFileName, []byte(userDataOut), 0644)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), userDataFileName)
|
||||
} else {
|
||||
@@ -345,7 +343,7 @@ func (m *InstancesModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
privateIPsFilename := filepath.Join(path, "instances-ec2PrivateIPs.txt")
|
||||
publicIPsFilename := filepath.Join(path, "instances-ec2PublicIPs.txt")
|
||||
@@ -396,22 +394,22 @@ func (m *InstancesModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
err = os.WriteFile(privateIPsFilename, []byte(privateIPs), 0644)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
err = os.WriteFile(publicIPsFilename, []byte(publicIPs), 0644)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
err = os.WriteFile(ssmCommandsFilename, []byte(ssmCommands), 0644)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
err = os.WriteFile(ec2InstanceConnectCommandsFilename, []byte(ec2InstanceConnectCommands), 0644)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
fmt.Printf("[%s][%s] Loot written to [%s]\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), privateIPsFilename)
|
||||
@@ -448,17 +446,17 @@ func (m *InstancesModule) executeChecks(instancesToSearch []string, r string, wg
|
||||
res, err := servicemap.IsServiceInRegion("ec2", r)
|
||||
if err != nil {
|
||||
m.modLog.Errorf("Error checking if EC2 is available in region %s: %v", r, err)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
m.modLog.Debugf("ServiceMap check for EC2 in region %s: result=%v, error=%v", r, res, err)
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
m.getDescribeInstances(instancesToSearch, r, dataReceiver)
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
} else {
|
||||
m.modLog.Infof("Skipping region %s: EC2 not available or check failed", r)
|
||||
}
|
||||
@@ -469,7 +467,7 @@ func (m *InstancesModule) getInstanceUserDataAttribute(instanceID *string, regio
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return nil, err
|
||||
} else {
|
||||
if UserData == "" {
|
||||
@@ -489,7 +487,7 @@ func (m *InstancesModule) getDescribeInstances(instancesToSearch []string, regio
|
||||
Instances, err := sdk.CachedEC2DescribeInstances(m.EC2Client, aws.ToString(m.Caller.Account), region)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -588,7 +586,7 @@ func (m *InstancesModule) getRolesFromInstanceProfiles() {
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
for _, instanceProfile := range ListInstanceProfiles.InstanceProfiles {
|
||||
|
||||
+335
-335
File diff suppressed because it is too large.
Load diff
+9
-9
@@ -105,7 +105,7 @@ func (m *LambdasModule) PrintLambdas(outputDirectory string, verbosity int) {
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrPending()
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
|
||||
}
|
||||
@@ -241,7 +241,7 @@ func (m *LambdasModule) executeChecks(r string, wg *sync.WaitGroup, semaphore ch
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getLambdasPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -265,7 +265,7 @@ func (m *LambdasModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
pullFile := filepath.Join(path, "lambda-get-function-commands.txt")
|
||||
|
||||
@@ -289,7 +289,7 @@ func (m *LambdasModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
err = os.WriteFile(pullFile, []byte(out), 0644)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
if verbosity > 2 {
|
||||
@@ -306,8 +306,8 @@ func (m *LambdasModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
|
||||
func (m *LambdasModule) getLambdasPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Lambda) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -319,7 +319,7 @@ func (m *LambdasModule) getLambdasPerRegion(r string, wg *sync.WaitGroup, semaph
|
||||
functions, err := m.listFunctions(r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
for _, function := range functions {
|
||||
@@ -360,7 +360,7 @@ func (m *LambdasModule) listFunctions(r string) ([]types.FunctionConfiguration,
|
||||
)
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return functions, err
|
||||
}
|
||||
functions = append(functions, ListFunctions.Functions...)
|
||||
@@ -389,7 +389,7 @@ func (m *LambdasModule) getResourcePolicy(r string, functionName string) (policy
|
||||
)
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return projectPolicy, err
|
||||
}
|
||||
|
||||
|
||||
+61
-66
@@ -50,6 +50,9 @@ var (
|
||||
# Make sure the host you scan IPv6 from has an IPv6 network interface.
|
||||
#############################################
|
||||
`
|
||||
|
||||
naclsMutex = sync.RWMutex{}
|
||||
securityGroupsMutex = sync.RWMutex{}
|
||||
)
|
||||
|
||||
type NetworkPortsModule struct {
|
||||
@@ -74,13 +77,11 @@ type NetworkPortsModule struct {
|
||||
ServiceMap *awsservicemap.AwsServiceMap // Shared service map to avoid repeated HTTP requests
|
||||
|
||||
// Main module data
|
||||
IPv4_Private []NetworkService
|
||||
IPv4_Public []NetworkService
|
||||
IPv6 []NetworkService
|
||||
nacls map[string]*[]ec2_types.NetworkAcl
|
||||
securityGroups map[string]*[]ec2_types.SecurityGroup
|
||||
naclsMutex sync.RWMutex
|
||||
securityGroupsMutex sync.RWMutex
|
||||
IPv4_Private []NetworkService
|
||||
IPv4_Public []NetworkService
|
||||
IPv6 []NetworkService
|
||||
nacls map[string]*[]ec2_types.NetworkAcl
|
||||
securityGroups map[string]*[]ec2_types.SecurityGroup
|
||||
|
||||
CommandCounter internal.CommandCounter
|
||||
// Used to store output data for pretty printing
|
||||
@@ -171,7 +172,7 @@ func (m *NetworkPortsModule) PrintNetworkPorts(outputDirectory string) {
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrPending()
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, dataReceiver)
|
||||
|
||||
}
|
||||
@@ -286,7 +287,7 @@ func (m *NetworkPortsModule) executeChecks(r string, wg *sync.WaitGroup, dataRec
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
m.getEC2NetworkPortsPerRegion(r, dataReceiver)
|
||||
}
|
||||
|
||||
@@ -295,7 +296,7 @@ func (m *NetworkPortsModule) executeChecks(r string, wg *sync.WaitGroup, dataRec
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
m.getECSNetworkPortsPerRegion(r, dataReceiver)
|
||||
}
|
||||
|
||||
@@ -304,7 +305,7 @@ func (m *NetworkPortsModule) executeChecks(r string, wg *sync.WaitGroup, dataRec
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
m.getEFSNetworkPortsPerRegion(r, dataReceiver)
|
||||
}
|
||||
|
||||
@@ -313,7 +314,7 @@ func (m *NetworkPortsModule) executeChecks(r string, wg *sync.WaitGroup, dataRec
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
m.getElastiCacheNetworkPortsPerRegion(r, dataReceiver)
|
||||
}
|
||||
|
||||
@@ -322,7 +323,7 @@ func (m *NetworkPortsModule) executeChecks(r string, wg *sync.WaitGroup, dataRec
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
m.getLBNetworkPortsPerRegion(r, dataReceiver)
|
||||
}
|
||||
|
||||
@@ -331,7 +332,7 @@ func (m *NetworkPortsModule) executeChecks(r string, wg *sync.WaitGroup, dataRec
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
m.getLightsailNetworkPortsPerRegion(r, dataReceiver)
|
||||
}
|
||||
|
||||
@@ -340,7 +341,7 @@ func (m *NetworkPortsModule) executeChecks(r string, wg *sync.WaitGroup, dataRec
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
m.getRdsNetworkPortsPerRegion(r, dataReceiver)
|
||||
}
|
||||
|
||||
@@ -428,8 +429,8 @@ func (m *NetworkPortsModule) writeLootFile(filename string, bannner string, ipv4
|
||||
|
||||
func (m *NetworkPortsModule) getEC2NetworkPortsPerRegion(r string, dataReceiver chan NetworkServices) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}()
|
||||
securityGroups := m.getEC2SecurityGroupsPerRegion(r)
|
||||
nacls := m.getEC2NACLsPerRegion(r)
|
||||
@@ -532,8 +533,8 @@ func (m *NetworkPortsModule) getEC2NetworkPortsPerRegion(r string, dataReceiver
|
||||
|
||||
func (m *NetworkPortsModule) getECSNetworkPortsPerRegion(r string, dataReceiver chan NetworkServices) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}()
|
||||
securityGroups := m.getEC2SecurityGroupsPerRegion(r)
|
||||
nacls := m.getEC2NACLsPerRegion(r)
|
||||
@@ -547,7 +548,7 @@ func (m *NetworkPortsModule) getECSNetworkPortsPerRegion(r string, dataReceiver
|
||||
service, err := m.describeECSService(serviceArn, &clusterArn, r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -584,7 +585,7 @@ func (m *NetworkPortsModule) getECSNetworkPortsPerRegion(r string, dataReceiver
|
||||
task, err := m.describeECSTask(taskArn, &clusterArn, r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -667,8 +668,8 @@ func (m *NetworkPortsModule) getECSNetworkPortsPerRegion(r string, dataReceiver
|
||||
|
||||
func (m *NetworkPortsModule) getEFSNetworkPortsPerRegion(r string, dataReceiver chan NetworkServices) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}()
|
||||
securityGroups := m.getEC2SecurityGroupsPerRegion(r)
|
||||
nacls := m.getEC2NACLsPerRegion(r)
|
||||
@@ -730,8 +731,8 @@ func (m *NetworkPortsModule) getEFSNetworkPortsPerRegion(r string, dataReceiver
|
||||
|
||||
func (m *NetworkPortsModule) getElastiCacheNetworkPortsPerRegion(r string, dataReceiver chan NetworkServices) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}()
|
||||
securityGroups := m.getEC2SecurityGroupsPerRegion(r)
|
||||
nacls := m.getEC2NACLsPerRegion(r)
|
||||
@@ -842,8 +843,8 @@ func (m *NetworkPortsModule) getElastiCacheNetworkPortsPerRegion(r string, dataR
|
||||
|
||||
func (m *NetworkPortsModule) getLBNetworkPortsPerRegion(r string, dataReceiver chan NetworkServices) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}()
|
||||
securityGroups := m.getEC2SecurityGroupsPerRegion(r)
|
||||
nacls := m.getEC2NACLsPerRegion(r)
|
||||
@@ -958,8 +959,8 @@ func (m *NetworkPortsModule) getLBNetworkPortsPerRegion(r string, dataReceiver c
|
||||
|
||||
func (m *NetworkPortsModule) getLightsailNetworkPortsPerRegion(r string, dataReceiver chan NetworkServices) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}()
|
||||
instances := m.getLightsailInstancesPerRegion(r)
|
||||
|
||||
@@ -1069,8 +1070,8 @@ func (m *NetworkPortsModule) getLightsailNetworkPortsPerRegion(r string, dataRec
|
||||
|
||||
func (m *NetworkPortsModule) getRdsNetworkPortsPerRegion(r string, dataReceiver chan NetworkServices) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}()
|
||||
securityGroups := m.getEC2SecurityGroupsPerRegion(r)
|
||||
nacls := m.getEC2NACLsPerRegion(r)
|
||||
@@ -1142,16 +1143,13 @@ func (m *NetworkPortsModule) getRdsNetworkPortsPerRegion(r string, dataReceiver
|
||||
}
|
||||
|
||||
func (m *NetworkPortsModule) getEC2SecurityGroupsPerRegion(region string) []ec2_types.SecurityGroup {
|
||||
m.securityGroupsMutex.RLock()
|
||||
cached, ok := m.securityGroups[region]
|
||||
m.securityGroupsMutex.RUnlock()
|
||||
if ok && cached != nil {
|
||||
return *cached
|
||||
}
|
||||
|
||||
var securityGroups []ec2_types.SecurityGroup
|
||||
var PaginationControl *string
|
||||
|
||||
if m.securityGroups[region] != nil {
|
||||
return *m.securityGroups[region]
|
||||
}
|
||||
|
||||
for {
|
||||
DescribeSecurityGroups, err := m.EC2Client.DescribeSecurityGroups(
|
||||
context.TODO(),
|
||||
@@ -1164,7 +1162,7 @@ func (m *NetworkPortsModule) getEC2SecurityGroupsPerRegion(region string) []ec2_
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1178,9 +1176,9 @@ func (m *NetworkPortsModule) getEC2SecurityGroupsPerRegion(region string) []ec2_
|
||||
}
|
||||
}
|
||||
|
||||
m.securityGroupsMutex.Lock()
|
||||
securityGroupsMutex.Lock()
|
||||
m.securityGroups[region] = &securityGroups
|
||||
m.securityGroupsMutex.Unlock()
|
||||
securityGroupsMutex.Unlock()
|
||||
return securityGroups
|
||||
}
|
||||
|
||||
@@ -1217,16 +1215,13 @@ func (m *NetworkPortsModule) parseSecurityGroup(group ec2_types.SecurityGroup) S
|
||||
}
|
||||
|
||||
func (m *NetworkPortsModule) getEC2NACLsPerRegion(region string) []ec2_types.NetworkAcl {
|
||||
m.naclsMutex.RLock()
|
||||
cached, ok := m.nacls[region]
|
||||
m.naclsMutex.RUnlock()
|
||||
if ok && cached != nil {
|
||||
return *cached
|
||||
}
|
||||
|
||||
var nacls []ec2_types.NetworkAcl
|
||||
var PaginationControl *string
|
||||
|
||||
if m.nacls[region] != nil {
|
||||
return *m.nacls[region]
|
||||
}
|
||||
|
||||
for {
|
||||
DescribeNetworkAcls, err := m.EC2Client.DescribeNetworkAcls(
|
||||
context.TODO(),
|
||||
@@ -1239,7 +1234,7 @@ func (m *NetworkPortsModule) getEC2NACLsPerRegion(region string) []ec2_types.Net
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1253,9 +1248,9 @@ func (m *NetworkPortsModule) getEC2NACLsPerRegion(region string) []ec2_types.Net
|
||||
}
|
||||
}
|
||||
|
||||
m.naclsMutex.Lock()
|
||||
naclsMutex.Lock()
|
||||
m.nacls[region] = &nacls
|
||||
m.naclsMutex.Unlock()
|
||||
naclsMutex.Unlock()
|
||||
return nacls
|
||||
}
|
||||
|
||||
@@ -1325,7 +1320,7 @@ func (m *NetworkPortsModule) getEC2InstancesPerRegion(region string) []ec2_types
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1364,7 +1359,7 @@ func (m *NetworkPortsModule) getEC2NetworkInterfacesPerRegion(interfaceId string
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1399,7 +1394,7 @@ func (m *NetworkPortsModule) getECSClustersPerRegion(region string) []string {
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1435,7 +1430,7 @@ func (m *NetworkPortsModule) getECSServicesPerRegion(clusterArn *string, region
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1498,7 +1493,7 @@ func (m *NetworkPortsModule) getECSTasksPerRegion(service *string, clusterArn *s
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1558,7 +1553,7 @@ func (m *NetworkPortsModule) getEFSSharesPerRegion(region string) []efs_types.Fi
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1594,7 +1589,7 @@ func (m *NetworkPortsModule) getEFSMountTargetsPerRegion(filesystem *string, reg
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1629,7 +1624,7 @@ func (m *NetworkPortsModule) getElastiCacheClustersPerRegion(region string) []el
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1664,7 +1659,7 @@ func (m *NetworkPortsModule) getElastiCacheSubnetGroupsPerRegion(region string)
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1699,7 +1694,7 @@ func (m *NetworkPortsModule) getElastiCacheReplicationGroupsPerRegion(region str
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1734,7 +1729,7 @@ func (m *NetworkPortsModule) getLightsailInstancesPerRegion(region string) []lig
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1769,7 +1764,7 @@ func (m *NetworkPortsModule) getLoadBalancersPerRegion(region string) []elbv2_ty
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1805,7 +1800,7 @@ func (m *NetworkPortsModule) getLBListenerPorts(arn *string, region string) ([]i
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1855,7 +1850,7 @@ func (m *NetworkPortsModule) getRdsClustersPerRegion(region string) []rds_types.
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1890,7 +1885,7 @@ func (m *NetworkPortsModule) getRdsInstancesPerRegion(region string) []rds_types
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
ec2_types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
||||
)
|
||||
|
||||
// TestNetworkPortsCacheConcurrentAccess is a regression test for issue #129.
|
||||
// Pre-fix, the cache lookup in getEC2NACLsPerRegion and getEC2SecurityGroupsPerRegion
|
||||
// read m.nacls / m.securityGroups without holding a mutex while writes from
|
||||
// other goroutines were locked, producing a "concurrent map read and map write"
|
||||
// panic under load. Run with `go test -race ./aws/...` to verify the lock fix.
|
||||
func TestNetworkPortsCacheConcurrentAccess(t *testing.T) {
|
||||
m := &NetworkPortsModule{
|
||||
nacls: make(map[string]*[]ec2_types.NetworkAcl),
|
||||
securityGroups: make(map[string]*[]ec2_types.SecurityGroup),
|
||||
}
|
||||
|
||||
// Pre-populate one region so reader goroutines hit the cached fast-path
|
||||
// without needing a live EC2 client.
|
||||
cachedNACLs := []ec2_types.NetworkAcl{}
|
||||
m.nacls["cached"] = &cachedNACLs
|
||||
cachedSGs := []ec2_types.SecurityGroup{}
|
||||
m.securityGroups["cached"] = &cachedSGs
|
||||
|
||||
var wg sync.WaitGroup
|
||||
const readers = 100
|
||||
for i := 0; i < readers; i++ {
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
_ = m.getEC2NACLsPerRegion("cached")
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
_ = m.getEC2SecurityGroupsPerRegion("cached")
|
||||
}()
|
||||
}
|
||||
|
||||
// Writer goroutines populate uncached regions directly through the mutex,
|
||||
// mirroring what the live functions do after a successful AWS fetch. The
|
||||
// readers above must not race with these writes.
|
||||
const writers = 10
|
||||
for i := 0; i < writers; i++ {
|
||||
region := fmt.Sprintf("uncached-%d", i)
|
||||
wg.Add(2)
|
||||
go func(r string) {
|
||||
defer wg.Done()
|
||||
sl := []ec2_types.NetworkAcl{}
|
||||
m.naclsMutex.Lock()
|
||||
m.nacls[r] = &sl
|
||||
m.naclsMutex.Unlock()
|
||||
}(region)
|
||||
go func(r string) {
|
||||
defer wg.Done()
|
||||
sl := []ec2_types.SecurityGroup{}
|
||||
m.securityGroupsMutex.Lock()
|
||||
m.securityGroups[r] = &sl
|
||||
m.securityGroupsMutex.Unlock()
|
||||
}(region)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
+4
-4
@@ -191,7 +191,7 @@ func (m *OrgModule) ProcessMultipleAccounts(AWSProfiles []string, version string
|
||||
|
||||
for _, profile := range AWSProfiles {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrPending()
|
||||
m.CommandCounter.Pending++
|
||||
go m.FindMgmtAccounts(profile, version, wg, semaphore, dataReceiver)
|
||||
|
||||
wg.Wait()
|
||||
@@ -244,7 +244,7 @@ func (m *OrgModule) FindMgmtAccounts(profile string, version string, wg *sync.Wa
|
||||
DescribeOrganization, err := sdk.CachedOrganizationsDescribeOrganization(m.OrganizationsClient, aws.ToString(m.Caller.Account))
|
||||
if err != nil {
|
||||
m.modLog.Errorf("Failed to describe organization: %s", err)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@ func (m *OrgModule) addOrgAccounts() {
|
||||
accounts, err := sdk.CachedOrganizationsListAccounts(m.OrganizationsClient, aws.ToString(m.Caller.Account))
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
for _, account := range accounts {
|
||||
@@ -292,7 +292,7 @@ func (m *OrgModule) addOrgAccount() {
|
||||
DescribeOrganization, err := sdk.CachedOrganizationsDescribeOrganization(m.OrganizationsClient, aws.ToString(m.Caller.Account))
|
||||
if err != nil {
|
||||
sharedLogger.Errorf("Failed to describe organization: %s", err)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
m.Accounts = append(m.Accounts, Account{
|
||||
|
||||
@@ -171,7 +171,7 @@ func (m *OutboundAssumedRolesModule) PrintOutboundRoleTrusts(days int, outputDir
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrPending()
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
|
||||
}
|
||||
@@ -308,10 +308,10 @@ func (m *OutboundAssumedRolesModule) executeChecks(r string, wg *sync.WaitGroup,
|
||||
}
|
||||
if res {
|
||||
// wg.Add(1)
|
||||
// m.CommandCounter.IncrTotal()
|
||||
// m.CommandCounter.Total++
|
||||
// m.getAssumeRoleLogEntriesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
m.getCrossAccountBatchGetImageEntriesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
|
||||
@@ -319,8 +319,8 @@ func (m *OutboundAssumedRolesModule) executeChecks(r string, wg *sync.WaitGroup,
|
||||
|
||||
func (m *OutboundAssumedRolesModule) getAssumeRoleLogEntriesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan OutboundAssumeRoleEntry) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -358,7 +358,7 @@ func (m *OutboundAssumedRolesModule) getAssumeRoleLogEntriesPerRegion(r string,
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -412,8 +412,8 @@ func (m *OutboundAssumedRolesModule) getAssumeRoleLogEntriesPerRegion(r string,
|
||||
// get cross account batch get image entries
|
||||
func (m *OutboundAssumedRolesModule) getCrossAccountBatchGetImageEntriesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan OutboundAssumeRoleEntry) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -451,7 +451,7 @@ func (m *OutboundAssumedRolesModule) getCrossAccountBatchGetImageEntriesPerRegio
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -197,7 +197,7 @@ func (m *IamPermissionsModule) GetGAAD() {
|
||||
GAAD, err := sdk.CachedIAMGetAccountAuthorizationDetails(m.IAMClient, aws.ToString(m.Caller.Account))
|
||||
if err != nil {
|
||||
m.modLog.Error(err)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -446,7 +446,7 @@ func (m *PmapperModule) writeLoot(outputDirectory string, verbosity int) string
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
panic(err.Error())
|
||||
}
|
||||
lootFilePath := filepath.Join(path, "pmapper.txt")
|
||||
|
||||
+8
-8
@@ -97,7 +97,7 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi
|
||||
// done := make(chan bool)
|
||||
// go internal.SpinUntil(m.output.CallingModule, &m.CommandCounter, done)
|
||||
// wg.Add(1)
|
||||
// m.CommandCounter.IncrPending()
|
||||
// m.CommandCounter.Pending++
|
||||
//m.executeChecks(wg)
|
||||
// wg.Wait()
|
||||
// done <- true
|
||||
@@ -237,13 +237,13 @@ func (m *IamPrincipalsModule) PrintIamPrincipals(outputDirectory string, verbosi
|
||||
/* UNUSED CODE BLOCK - PLEASE REVIEW AND DELETE IF APPLICABLE
|
||||
func (m *IamPrincipalsModule) executeChecks(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
m.getIAMUsers()
|
||||
m.getIAMRoles()
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -256,7 +256,7 @@ func (m *IamPrincipalsModule) addIAMUsersToTable() {
|
||||
ListUsers, err := sdk.CachedIamListUsers(m.IAMClient, aws.ToString(m.Caller.Account))
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
for _, user := range ListUsers {
|
||||
@@ -288,7 +288,7 @@ func (m *IamPrincipalsModule) addIAMRolesToTable() {
|
||||
ListRoles, err := sdk.CachedIamListRoles(m.IAMClient, aws.ToString(m.Caller.Account))
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
for _, role := range ListRoles {
|
||||
|
||||
+8
-8
@@ -79,7 +79,7 @@ func (m *RAMModule) PrintRAM(outputDirectory string, verbosity int) {
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrPending()
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, dataReceiver)
|
||||
|
||||
}
|
||||
@@ -191,12 +191,12 @@ func (m *RAMModule) executeChecks(r string, wg *sync.WaitGroup, dataReceiver cha
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
m.CommandCounter.Total++
|
||||
m.CommandCounter.Pending--
|
||||
m.CommandCounter.Executing++
|
||||
m.getRAMResourcesPerRegion(r, dataReceiver)
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ func (m *RAMModule) getRAMResourcesPerRegion(r string, dataReceiver chan Resourc
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ func (m *RAMModule) getRAMResourcesPerRegion(r string, dataReceiver chan Resourc
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
+52
-52
@@ -104,7 +104,7 @@ func (m *ResourceTrustsModule) PrintResources(outputDirectory string, verbosity
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrPending()
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver, includeKms)
|
||||
|
||||
}
|
||||
@@ -217,7 +217,7 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getSNSTopicsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -227,7 +227,7 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getSQSQueuesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -237,7 +237,7 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getECRRecordsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -247,7 +247,7 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getCodeBuildResourcePoliciesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -257,7 +257,7 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getLambdaPolicyPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -267,7 +267,7 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getEFSfilesystemPoliciesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -277,7 +277,7 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getSecretsManagerSecretsPoliciesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -287,7 +287,7 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getGlueResourcePoliciesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -298,7 +298,7 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getKMSPoliciesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -310,7 +310,7 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getAPIGatewayPoliciesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -322,7 +322,7 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getVPCEndpointPoliciesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -334,7 +334,7 @@ func (m *ResourceTrustsModule) executeChecks(r string, wg *sync.WaitGroup, semap
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getOpenSearchPoliciesPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -356,8 +356,8 @@ func (m *ResourceTrustsModule) Receiver(receiver chan Resource2, receiverDone ch
|
||||
|
||||
func (m *ResourceTrustsModule) getSNSTopicsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -378,7 +378,7 @@ func (m *ResourceTrustsModule) getSNSTopicsPerRegion(r string, wg *sync.WaitGrou
|
||||
topic, err := cloudFoxSNSClient.getTopicWithAttributes(aws.ToString(t.TopicArn), r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
continue
|
||||
}
|
||||
parsedArn, err := arn.Parse(aws.ToString(t.TopicArn))
|
||||
@@ -433,8 +433,8 @@ func (m *ResourceTrustsModule) getSNSTopicsPerRegion(r string, wg *sync.WaitGrou
|
||||
|
||||
func (m *ResourceTrustsModule) getS3Buckets(wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -516,8 +516,8 @@ func (m *ResourceTrustsModule) getS3Buckets(wg *sync.WaitGroup, semaphore chan s
|
||||
|
||||
func (m *ResourceTrustsModule) getSQSQueuesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -539,7 +539,7 @@ func (m *ResourceTrustsModule) getSQSQueuesPerRegion(r string, wg *sync.WaitGrou
|
||||
queue, err := cloudFoxSQSClient.getQueueWithAttributes(q, r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -578,8 +578,8 @@ func (m *ResourceTrustsModule) getSQSQueuesPerRegion(r string, wg *sync.WaitGrou
|
||||
|
||||
func (m *ResourceTrustsModule) getECRRecordsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -601,7 +601,7 @@ func (m *ResourceTrustsModule) getECRRecordsPerRegion(r string, wg *sync.WaitGro
|
||||
repoPolicy, err := cloudFoxECRClient.getECRRepositoryPolicy(r, aws.ToString(repo.RepositoryName))
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -642,8 +642,8 @@ func (m *ResourceTrustsModule) getECRRecordsPerRegion(r string, wg *sync.WaitGro
|
||||
|
||||
func (m *ResourceTrustsModule) getCodeBuildResourcePoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -666,7 +666,7 @@ func (m *ResourceTrustsModule) getCodeBuildResourcePoliciesPerRegion(r string, w
|
||||
project, err := sdk.CachedCodeBuildBatchGetProjects(cloudFoxCodeBuildClient.CodeBuildClient, aws.ToString(cloudFoxCodeBuildClient.Caller.Account), r, p)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -716,8 +716,8 @@ func (m *ResourceTrustsModule) getCodeBuildResourcePoliciesPerRegion(r string, w
|
||||
|
||||
func (m *ResourceTrustsModule) getLambdaPolicyPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -739,7 +739,7 @@ func (m *ResourceTrustsModule) getLambdaPolicyPerRegion(r string, wg *sync.WaitG
|
||||
functionPolicy, err := cloudFoxLambdaClient.getResourcePolicy(r, aws.ToString(f.FunctionName))
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -781,8 +781,8 @@ func (m *ResourceTrustsModule) getLambdaPolicyPerRegion(r string, wg *sync.WaitG
|
||||
|
||||
func (m *ResourceTrustsModule) getEFSfilesystemPoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -804,7 +804,7 @@ func (m *ResourceTrustsModule) getEFSfilesystemPoliciesPerRegion(r string, wg *s
|
||||
fsPolicy, err := sdk.CachedDescribeFileSystemPolicy(cloudFoxEFSClient.EFSClient, aws.ToString(fs.FileSystemId), r, aws.ToString(m.Caller.Account))
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -851,8 +851,8 @@ func (m *ResourceTrustsModule) getEFSfilesystemPoliciesPerRegion(r string, wg *s
|
||||
// It takes the region to search in, the WaitGroup to use, the semaphore to use, and the dataReceiver channel to send results to.
|
||||
func (m *ResourceTrustsModule) getSecretsManagerSecretsPoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -874,7 +874,7 @@ func (m *ResourceTrustsModule) getSecretsManagerSecretsPoliciesPerRegion(r strin
|
||||
secretPolicy, err := sdk.CachedSecretsManagerGetResourcePolicy(cloudFoxSecretsManagerClient, aws.ToString(s.ARN), r, aws.ToString(m.Caller.Account))
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -921,8 +921,8 @@ func (m *ResourceTrustsModule) getSecretsManagerSecretsPoliciesPerRegion(r strin
|
||||
// It takes the region to search in, the WaitGroup to use, the semaphore to use, and the dataReceiver channel to send results to.
|
||||
func (m *ResourceTrustsModule) getKMSPoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
@@ -942,7 +942,7 @@ func (m *ResourceTrustsModule) getKMSPoliciesPerRegion(r string, wg *sync.WaitGr
|
||||
keyPolicy, err := sdk.CachedKMSGetKeyPolicy(*m.KMSClient, aws.ToString(m.Caller.Account), r, aws.ToString(key.KeyId))
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -985,8 +985,8 @@ func (m *ResourceTrustsModule) getKMSPoliciesPerRegion(r string, wg *sync.WaitGr
|
||||
|
||||
func (m *ResourceTrustsModule) getAPIGatewayPoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
@@ -1016,7 +1016,7 @@ func (m *ResourceTrustsModule) getAPIGatewayPoliciesPerRegion(r string, wg *sync
|
||||
restAPIPolicy, err := policy.ParseJSONPolicy([]byte(policyJson))
|
||||
if err != nil {
|
||||
sharedLogger.Error(fmt.Errorf("parsing policy (%s) as JSON: %s", aws.ToString(restAPI.Name), err))
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1063,8 +1063,8 @@ func (m *ResourceTrustsModule) getAPIGatewayPoliciesPerRegion(r string, wg *sync
|
||||
|
||||
func (m *ResourceTrustsModule) getVPCEndpointPoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
@@ -1086,7 +1086,7 @@ func (m *ResourceTrustsModule) getVPCEndpointPoliciesPerRegion(r string, wg *syn
|
||||
vpcEndpointPolicy, err := policy.ParseJSONPolicy([]byte(vpcEndpointPolicyJson))
|
||||
if err != nil {
|
||||
sharedLogger.Error(fmt.Errorf("parsing policy (%s) as JSON: %s", aws.ToString(vpcEndpoint.VpcEndpointId), err))
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1133,8 +1133,8 @@ func (m *ResourceTrustsModule) getVPCEndpointPoliciesPerRegion(r string, wg *syn
|
||||
|
||||
func (m *ResourceTrustsModule) getOpenSearchPoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
}()
|
||||
semaphore <- struct{}{}
|
||||
@@ -1153,7 +1153,7 @@ func (m *ResourceTrustsModule) getOpenSearchPoliciesPerRegion(r string, wg *sync
|
||||
openSearchDomainConfig, err := sdk.CachedOpenSearchDescribeDomainConfig(*m.OpenSearchClient, aws.ToString(m.Caller.Account), r, aws.ToString(openSearchDomain.DomainName))
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1167,7 +1167,7 @@ func (m *ResourceTrustsModule) getOpenSearchPoliciesPerRegion(r string, wg *sync
|
||||
openSearchDomainStatus, err := sdk.CachedOpenSearchDescribeDomain(*m.OpenSearchClient, aws.ToString(m.Caller.Account), r, aws.ToString(openSearchDomain.DomainName))
|
||||
if err != nil {
|
||||
sharedLogger.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1179,7 +1179,7 @@ func (m *ResourceTrustsModule) getOpenSearchPoliciesPerRegion(r string, wg *sync
|
||||
openSearchDomainPolicy, err := policy.ParseJSONPolicy([]byte(policyJson))
|
||||
if err != nil {
|
||||
sharedLogger.Error(fmt.Errorf("parsing policy (%s) as JSON: %s", aws.ToString(openSearchDomainStatus.ARN), err))
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1226,8 +1226,8 @@ func (m *ResourceTrustsModule) getOpenSearchPoliciesPerRegion(r string, wg *sync
|
||||
|
||||
func (m *ResourceTrustsModule) getGlueResourcePoliciesPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Resource2) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
|
||||
+2
-2
@@ -590,14 +590,14 @@ func (m *RoleTrustsModule) getAllRoleTrusts() {
|
||||
ListRoles, err := sdk.CachedIamListRoles(m.IAMClient, aws.ToString(m.Caller.Account))
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
for _, role := range ListRoles {
|
||||
trustsdoc, err := policy.ParseRoleTrustPolicyDocument(role)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -146,7 +146,7 @@ func (m *Route53Module) writeLoot(outputDirectory string, verbosity int) {
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
panic(err.Error())
|
||||
}
|
||||
route53ARecordsPublicZonesFileName := filepath.Join(path, "route53-A-records-public-Zones.txt")
|
||||
@@ -168,7 +168,7 @@ func (m *Route53Module) writeLoot(outputDirectory string, verbosity int) {
|
||||
err = os.WriteFile(route53ARecordsPublicZonesFileName, []byte(route53APublicRecords), 0644)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ func (m *Route53Module) writeLoot(outputDirectory string, verbosity int) {
|
||||
err = os.WriteFile(route53ARecordsPrivateZonesFileName, []byte(route53APrivateRecords), 0644)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ func (m *Route53Module) getRoute53Records() {
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@ func (m *Route53Module) getRoute53Records() {
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
for _, record := range Records {
|
||||
|
||||
+17
-17
@@ -82,7 +82,7 @@ func (m *SecretsModule) PrintSecrets(outputDirectory string, verbosity int) {
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrPending()
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ func (m *SecretsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore ch
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getSecretsManagerSecretsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -214,7 +214,7 @@ func (m *SecretsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore ch
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getSSMParametersPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -226,7 +226,7 @@ func (m *SecretsModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
pullFile := filepath.Join(path, "pull-secrets-commands.txt")
|
||||
|
||||
@@ -249,7 +249,7 @@ func (m *SecretsModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
err = os.WriteFile(pullFile, []byte(out), 0644)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
|
||||
if verbosity > 2 {
|
||||
@@ -266,8 +266,8 @@ func (m *SecretsModule) writeLoot(outputDirectory string, verbosity int) {
|
||||
|
||||
func (m *SecretsModule) getSecretsManagerSecretsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Secret) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -275,9 +275,9 @@ func (m *SecretsModule) getSecretsManagerSecretsPerRegion(r string, wg *sync.Wai
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// 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 {
|
||||
@@ -292,7 +292,7 @@ func (m *SecretsModule) getSecretsManagerSecretsPerRegion(r string, wg *sync.Wai
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -324,8 +324,8 @@ func (m *SecretsModule) getSecretsManagerSecretsPerRegion(r string, wg *sync.Wai
|
||||
|
||||
func (m *SecretsModule) getSSMParametersPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Secret) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -333,9 +333,9 @@ func (m *SecretsModule) getSSMParametersPerRegion(r string, wg *sync.WaitGroup,
|
||||
defer func() {
|
||||
<-semaphore
|
||||
}()
|
||||
// m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.DecrPending()
|
||||
m.CommandCounter.IncrExecuting()
|
||||
// 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
|
||||
|
||||
@@ -351,7 +351,7 @@ func (m *SecretsModule) getSSMParametersPerRegion(r string, wg *sync.WaitGroup,
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -19,7 +19,7 @@ var yellow = color.New(color.FgRed).SprintFunc()
|
||||
var blue = color.New(color.FgBlue).SprintFunc()
|
||||
var magenta = color.New(color.FgMagenta).SprintFunc()
|
||||
var green = color.New(color.FgGreen).SprintFunc()
|
||||
var AWSRegions = []string{"us-east-1", "us-east-2", "us-west-1", "us-west-2", "af-south-1", "ap-east-1", "ap-south-1", "ap-northeast-3", "ap-northeast-2", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1", "ca-central-1", "eu-central-1", "eu-west-1", "eu-west-2", "eu-south-1", "eu-west-3", "eu-north-1", "sa-east-1"}
|
||||
var AWSRegions = []string{"us-east-1", "us-east-2", "us-west-1", "us-west-2", "af-south-1", "ap-east-1", "ap-south-1", "ap-northeast-3", "ap-northeast-2", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1", "ca-central-1", "eu-central-1", "eu-west-1", "eu-west-2", "eu-south-1", "eu-west-3", "eu-north-1", "me-south-1", "sa-east-1"}
|
||||
var sharedLogger = internal.TxtLogger()
|
||||
|
||||
func GetIamSimResult(SkipAdminCheck bool, roleArnPtr *string, iamSimulatorMod IamSimulatorModule, localAdminMap map[string]bool) (string, string) {
|
||||
@@ -54,6 +54,12 @@ func GetIamSimResult(SkipAdminCheck bool, roleArnPtr *string, iamSimulatorMod Ia
|
||||
}
|
||||
}
|
||||
|
||||
if isRoleAdminBool {
|
||||
adminRole = "YES"
|
||||
} else {
|
||||
adminRole = "No"
|
||||
}
|
||||
|
||||
} else {
|
||||
adminRole = "Skipped"
|
||||
canRolePrivEsc = "Skipped"
|
||||
|
||||
+6
-6
@@ -96,7 +96,7 @@ func (m *SNSModule) PrintSNS(outputDirectory string, verbosity int) {
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrPending()
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
|
||||
}
|
||||
@@ -205,7 +205,7 @@ func (m *SNSModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan s
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getSNSTopicsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -288,8 +288,8 @@ func (m *SNSModule) writeLoot(outputDirectory string, verbosity int, profile str
|
||||
|
||||
func (m *SNSModule) getSNSTopicsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan SNSTopic) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -308,7 +308,7 @@ func (m *SNSModule) getSNSTopicsPerRegion(r string, wg *sync.WaitGroup, semaphor
|
||||
topic, err := m.getTopicWithAttributes(aws.ToString(t.TopicArn), r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
parsedArn, err := arn.Parse(aws.ToString(t.TopicArn))
|
||||
@@ -427,7 +427,7 @@ func (m *SNSModule) storeAccessPolicy(topic *SNSTopic) {
|
||||
|
||||
if err := m.storeFile(f, topic.PolicyJSON); err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-7
@@ -99,7 +99,7 @@ func (m *SQSModule) PrintSQS(outputDirectory string, verbosity int) {
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrPending()
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
|
||||
}
|
||||
@@ -207,7 +207,7 @@ func (m *SQSModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan s
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getSQSRecordsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -287,8 +287,8 @@ func (m *SQSModule) writeLoot(outputDirectory string, verbosity int, profile str
|
||||
|
||||
func (m *SQSModule) getSQSRecordsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Queue) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -301,7 +301,7 @@ func (m *SQSModule) getSQSRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -309,7 +309,7 @@ func (m *SQSModule) getSQSRecordsPerRegion(r string, wg *sync.WaitGroup, semapho
|
||||
queue, err := m.getQueueWithAttributes(url, r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
|
||||
@@ -437,7 +437,7 @@ func (m *SQSModule) storeAccessPolicy(queue *Queue) {
|
||||
|
||||
if err := m.storeFile(f, queue.PolicyJSON); err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -88,7 +88,7 @@ func (m *TagsModule) PrintTags(outputDirectory string, verbosity int) {
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrPending()
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
|
||||
}
|
||||
@@ -246,7 +246,7 @@ func (m *TagsModule) countUniqueResourcesWithTags() int {
|
||||
func (m *TagsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Tag) {
|
||||
defer wg.Done()
|
||||
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
m.getTagsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -266,8 +266,8 @@ func (m *TagsModule) Receiver(receiver chan Tag, receiverDone chan bool) {
|
||||
|
||||
func (m *TagsModule) getTagsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Tag) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -280,7 +280,7 @@ func (m *TagsModule) getTagsPerRegion(r string, wg *sync.WaitGroup, semaphore ch
|
||||
resources, err := m.getResources(r)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
//var parsedArn types.Arn
|
||||
for _, resource := range resources {
|
||||
@@ -337,7 +337,7 @@ func (m *TagsModule) getResources(r string) ([]types.ResourceTagMapping, error)
|
||||
)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return resources, err
|
||||
}
|
||||
|
||||
|
||||
+15
-15
@@ -109,7 +109,7 @@ func (m *WorkloadsModule) PrintWorkloads(outputDirectory string, verbosity int)
|
||||
|
||||
for _, region := range m.AWSRegions {
|
||||
wg.Add(1)
|
||||
m.CommandCounter.IncrPending()
|
||||
m.CommandCounter.Pending++
|
||||
go m.executeChecks(region, wg, semaphore, dataReceiver)
|
||||
|
||||
}
|
||||
@@ -281,14 +281,14 @@ func (m *WorkloadsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore
|
||||
|
||||
res, _ := servicemap.IsServiceInRegion("ec2", r)
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getEC2WorkloadsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
|
||||
res, _ = servicemap.IsServiceInRegion("ecs", r)
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getECSWorkloadsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
@@ -298,13 +298,13 @@ func (m *WorkloadsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore
|
||||
m.modLog.Error(err)
|
||||
}
|
||||
if res {
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getLambdaWorkloadsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
}
|
||||
|
||||
// AppRunner is not supported in the aws service region catalog so we have to run it in all regions
|
||||
m.CommandCounter.IncrTotal()
|
||||
m.CommandCounter.Total++
|
||||
wg.Add(1)
|
||||
go m.getAppRunnerWorkloadsPerRegion(r, wg, semaphore, dataReceiver)
|
||||
|
||||
@@ -312,8 +312,8 @@ func (m *WorkloadsModule) executeChecks(r string, wg *sync.WaitGroup, semaphore
|
||||
|
||||
func (m *WorkloadsModule) getEC2WorkloadsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Workload) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -369,8 +369,8 @@ func (m *WorkloadsModule) getEC2WorkloadsPerRegion(r string, wg *sync.WaitGroup,
|
||||
|
||||
func (m *WorkloadsModule) getECSWorkloadsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Workload) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -395,7 +395,7 @@ func (m *WorkloadsModule) getECSWorkloadsPerRegion(r string, wg *sync.WaitGroup,
|
||||
Tasks, err := sdk.CachedECSDescribeTasks(m.ECSClient, aws.ToString(m.Caller.Account), r, cluster, taskARNs)
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
return
|
||||
}
|
||||
|
||||
@@ -424,8 +424,8 @@ func (m *WorkloadsModule) getECSWorkloadsPerRegion(r string, wg *sync.WaitGroup,
|
||||
|
||||
func (m *WorkloadsModule) getLambdaWorkloadsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Workload) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -457,8 +457,8 @@ func (m *WorkloadsModule) getLambdaWorkloadsPerRegion(r string, wg *sync.WaitGro
|
||||
|
||||
func (m *WorkloadsModule) getAppRunnerWorkloadsPerRegion(r string, wg *sync.WaitGroup, semaphore chan struct{}, dataReceiver chan Workload) {
|
||||
defer func() {
|
||||
m.CommandCounter.DecrExecuting()
|
||||
m.CommandCounter.IncrComplete()
|
||||
m.CommandCounter.Executing--
|
||||
m.CommandCounter.Complete++
|
||||
wg.Done()
|
||||
|
||||
}()
|
||||
@@ -543,7 +543,7 @@ func (m *WorkloadsModule) getRolesFromInstanceProfiles() {
|
||||
|
||||
if err != nil {
|
||||
m.modLog.Error(err.Error())
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
break
|
||||
}
|
||||
for _, instanceProfile := range ListInstanceProfiles.InstanceProfiles {
|
||||
|
||||
-167
@@ -1,167 +0,0 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions"
|
||||
containerregistry "github.com/Azure/azure-sdk-for-go/services/preview/containerregistry/mgmt/2022-02-01-preview/containerregistry"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
)
|
||||
|
||||
const acrDockerTokenUsername = "00000000-0000-0000-0000-000000000000"
|
||||
|
||||
var getContainerRegistries = getContainerRegistriesOriginal
|
||||
|
||||
func AzACRCommand(AzTenantID, AzSubscription, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error {
|
||||
if AzTenantID != "" && AzSubscription == "" {
|
||||
tenantInfo := populateTenant(AzTenantID)
|
||||
if ptr.ToString(tenantInfo.ID) == "" {
|
||||
return fmt.Errorf("could not find Azure tenant %q", AzTenantID)
|
||||
}
|
||||
if !AzMergedTable {
|
||||
for _, subscription := range GetSubscriptionsPerTenantID(ptr.ToString(tenantInfo.ID)) {
|
||||
if err := runACRCommandForSingleSubscription(ptr.ToString(subscription.SubscriptionID), AzOutputDirectory, AzVerbosity, AzWrapTable, Version); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
azureLogger.InfoM(fmt.Sprintf("Enumerating container registries for tenant %s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID)), globals.AZ_ACR_MODULE_NAME)
|
||||
|
||||
var body [][]string
|
||||
var loot strings.Builder
|
||||
for _, subscription := range GetSubscriptionsPerTenantID(ptr.ToString(tenantInfo.ID)) {
|
||||
rows, commands, err := getACRData(subscription)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body = append(body, rows...)
|
||||
loot.WriteString(commands)
|
||||
}
|
||||
writeACROutput(AzOutputDirectory, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level", ptr.ToString(tenantInfo.DefaultDomain), body, loot.String(), AzVerbosity, AzWrapTable)
|
||||
return nil
|
||||
}
|
||||
|
||||
if AzTenantID == "" && AzSubscription != "" {
|
||||
return runACRCommandForSingleSubscription(AzSubscription, AzOutputDirectory, AzVerbosity, AzWrapTable, Version)
|
||||
}
|
||||
|
||||
return fmt.Errorf("please specify either --tenant or --subscription")
|
||||
}
|
||||
|
||||
func runACRCommandForSingleSubscription(AzSubscription, AzOutputDirectory string, AzVerbosity int, AzWrapTable bool, Version string) error {
|
||||
subscriptionInfo := PopulateSubsriptionType(AzSubscription)
|
||||
if subscriptionInfo.ID == "" {
|
||||
return fmt.Errorf("could not find Azure subscription %q", AzSubscription)
|
||||
}
|
||||
tenantID := ptr.ToString(GetTenantIDPerSubscription(subscriptionInfo.ID))
|
||||
tenantInfo := populateTenant(tenantID)
|
||||
|
||||
azureLogger.InfoM(fmt.Sprintf("Enumerating container registries for subscription %s (%s)", subscriptionInfo.Name, subscriptionInfo.ID), globals.AZ_ACR_MODULE_NAME)
|
||||
|
||||
var selectedSubscription subscriptions.Subscription
|
||||
for _, subscription := range GetSubscriptions() {
|
||||
if ptr.ToString(subscription.SubscriptionID) == subscriptionInfo.ID {
|
||||
selectedSubscription = subscription
|
||||
break
|
||||
}
|
||||
}
|
||||
rows, loot, err := getACRData(selectedSubscription)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeACROutput(AzOutputDirectory, ptr.ToString(tenantInfo.DefaultDomain), subscriptionInfo.Name, subscriptionInfo.Name, rows, loot, AzVerbosity, AzWrapTable)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getACRData(subscription subscriptions.Subscription) ([][]string, string, error) {
|
||||
subscriptionID := ptr.ToString(subscription.SubscriptionID)
|
||||
registries, err := getContainerRegistries(subscriptionID)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("could not enumerate container registries for subscription %s: %w", subscriptionID, err)
|
||||
}
|
||||
return getACRRelevantData(subscription, registries)
|
||||
}
|
||||
|
||||
func getACRRelevantData(subscription subscriptions.Subscription, registries []containerregistry.Registry) ([][]string, string, error) {
|
||||
var body [][]string
|
||||
var loot strings.Builder
|
||||
subscriptionID := ptr.ToString(subscription.SubscriptionID)
|
||||
subscriptionName := ptr.ToString(subscription.DisplayName)
|
||||
|
||||
for _, registry := range registries {
|
||||
name := ptr.ToString(registry.Name)
|
||||
loginServer := ""
|
||||
anonymousPulls := false
|
||||
if registry.RegistryProperties != nil {
|
||||
loginServer = ptr.ToString(registry.LoginServer)
|
||||
anonymousPulls = ptr.ToBool(registry.AnonymousPullEnabled)
|
||||
}
|
||||
body = append(body, []string{name, subscriptionName, subscriptionID, loginServer, fmt.Sprintf("%t", anonymousPulls)})
|
||||
loot.WriteString(acrLootCommands(name, subscriptionID, loginServer))
|
||||
}
|
||||
|
||||
sort.Slice(body, func(i, j int) bool {
|
||||
if body[i][1] == body[j][1] {
|
||||
return body[i][0] < body[j][0]
|
||||
}
|
||||
return body[i][1] < body[j][1]
|
||||
})
|
||||
return body, loot.String(), nil
|
||||
}
|
||||
|
||||
func acrLootCommands(registryName, subscriptionID, loginServer string) string {
|
||||
if registryName == "" || subscriptionID == "" || loginServer == "" {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("# %s (%s)\nACR_TOKEN=$(az acr login -n %s --subscription %s --expose-token --output tsv --query accessToken)\nprintf '%%s' \"$ACR_TOKEN\" | docker login %s --username %s --password-stdin\n\n",
|
||||
registryName, loginServer, registryName, subscriptionID, loginServer, acrDockerTokenUsername)
|
||||
}
|
||||
|
||||
func writeACROutput(outputDirectory, tenantDomain, directorySuffix, prefix string, body [][]string, loot string, verbosity int, wrap bool) {
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: globals.AZ_ACR_MODULE_NAME,
|
||||
PrefixIdentifier: prefix,
|
||||
Table: internal.TableClient{
|
||||
Wrap: wrap,
|
||||
DirectoryName: filepath.Join(outputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantDomain, directorySuffix),
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: []string{"Registry Name", "Subscription Name", "Subscription ID", "Login Server", "Anonymous Pulls"},
|
||||
Body: body,
|
||||
Name: globals.AZ_ACR_MODULE_NAME,
|
||||
})
|
||||
if loot != "" {
|
||||
o.Loot.DirectoryName = filepath.Join(outputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantDomain, directorySuffix, "loot")
|
||||
o.Loot.LootFiles = append(o.Loot.LootFiles, internal.LootFile{Contents: loot, Name: "acr-login-commands"})
|
||||
o.WriteFullOutput(o.Table.TableFiles, o.Loot.LootFiles)
|
||||
} else {
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func getContainerRegistriesOriginal(subscriptionID string) ([]containerregistry.Registry, error) {
|
||||
client := internal.GetContainerRegistriesClient(subscriptionID)
|
||||
page, err := client.List(context.TODO())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var registries []containerregistry.Registry
|
||||
for page.NotDone() {
|
||||
registries = append(registries, page.Values()...)
|
||||
if err := page.NextWithContext(context.TODO()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return registries, nil
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions"
|
||||
containerregistry "github.com/Azure/azure-sdk-for-go/services/preview/containerregistry/mgmt/2022-02-01-preview/containerregistry"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
)
|
||||
|
||||
func TestGetACRRelevantData(t *testing.T) {
|
||||
registries := []containerregistry.Registry{
|
||||
{
|
||||
Name: ptr.String("packnet"),
|
||||
RegistryProperties: &containerregistry.RegistryProperties{
|
||||
LoginServer: ptr.String("packnet.azurecr.io"),
|
||||
AnonymousPullEnabled: ptr.Bool(true),
|
||||
},
|
||||
},
|
||||
}
|
||||
subscription := subscriptions.Subscription{
|
||||
SubscriptionID: ptr.String("8bb4e6f5-be51-4eab-bd68-ecf6fd08338f"),
|
||||
DisplayName: ptr.String("Production"),
|
||||
}
|
||||
|
||||
body, loot, err := getACRRelevantData(subscription, registries)
|
||||
if err != nil {
|
||||
t.Fatalf("getACRRelevantData returned an error: %v", err)
|
||||
}
|
||||
if len(body) != 1 || body[0][0] != "packnet" || body[0][3] != "packnet.azurecr.io" || body[0][4] != "true" {
|
||||
t.Fatalf("unexpected ACR table body: %+v", body)
|
||||
}
|
||||
for _, expected := range []string{
|
||||
"az acr login -n packnet --subscription 8bb4e6f5-be51-4eab-bd68-ecf6fd08338f --expose-token",
|
||||
"docker login packnet.azurecr.io --username " + acrDockerTokenUsername + " --password-stdin",
|
||||
} {
|
||||
if !strings.Contains(loot, expected) {
|
||||
t.Errorf("loot does not contain %q:\n%s", expected, loot)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/automation/mgmt/automation"
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
)
|
||||
|
||||
var (
|
||||
getAutomationAccounts = getAutomationAccountsOriginal
|
||||
getAutomationAssets = getAutomationAssetsOriginal
|
||||
)
|
||||
|
||||
type automationAssets struct {
|
||||
Runbooks []automation.Runbook `json:"runbooks"`
|
||||
RunbookSources map[string]string `json:"-"`
|
||||
Credentials []automation.Credential `json:"credentials"`
|
||||
Webhooks []automation.Webhook `json:"webhooks"`
|
||||
}
|
||||
|
||||
type automationLoot struct {
|
||||
Runbooks string
|
||||
Credentials string
|
||||
Webhooks string
|
||||
}
|
||||
|
||||
func AzAutomationCommand(tenantID, subscription, outputDirectory, version string, verbosity int, wrap, merged bool) error {
|
||||
if tenantID != "" && subscription == "" {
|
||||
tenant := populateTenant(tenantID)
|
||||
if ptr.ToString(tenant.ID) == "" {
|
||||
return fmt.Errorf("could not find Azure tenant %q", tenantID)
|
||||
}
|
||||
azureLogger.InfoM(fmt.Sprintf("Enumerating Automation Accounts for tenant %s (%s)", ptr.ToString(tenant.DefaultDomain), ptr.ToString(tenant.ID)), globals.AZ_AUTOMATION_MODULE_NAME)
|
||||
subs := GetSubscriptionsPerTenantID(ptr.ToString(tenant.ID))
|
||||
if !merged {
|
||||
for _, sub := range subs {
|
||||
if err := runAutomationForSubscription(sub, outputDirectory, verbosity, wrap); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var rows [][]string
|
||||
var loot automationLoot
|
||||
for _, sub := range subs {
|
||||
subRows, subLoot, warnings, err := getAutomationData(sub)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rows = append(rows, subRows...)
|
||||
loot.Runbooks += subLoot.Runbooks
|
||||
loot.Credentials += subLoot.Credentials
|
||||
loot.Webhooks += subLoot.Webhooks
|
||||
for _, warning := range warnings {
|
||||
azureLogger.WarnM(warning.Error(), globals.AZ_AUTOMATION_MODULE_NAME)
|
||||
}
|
||||
}
|
||||
if len(subs) > 0 {
|
||||
writeAutomationOutput(outputDirectory, ptr.ToString(tenant.DefaultDomain), "1-tenant-level", ptr.ToString(tenant.DefaultDomain), rows, loot, verbosity, wrap)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if tenantID == "" && subscription != "" {
|
||||
id := ptr.ToString(GetSubscriptionID(subscription))
|
||||
for _, sub := range GetSubscriptions() {
|
||||
if ptr.ToString(sub.SubscriptionID) == id {
|
||||
return runAutomationForSubscription(sub, outputDirectory, verbosity, wrap)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("could not find Azure subscription %q", subscription)
|
||||
}
|
||||
return fmt.Errorf("please specify either --tenant or --subscription")
|
||||
}
|
||||
|
||||
func runAutomationForSubscription(sub subscriptions.Subscription, outputDirectory string, verbosity int, wrap bool) error {
|
||||
azureLogger.InfoM(fmt.Sprintf("Enumerating Automation Accounts for subscription %s (%s)", ptr.ToString(sub.DisplayName), ptr.ToString(sub.SubscriptionID)), globals.AZ_AUTOMATION_MODULE_NAME)
|
||||
rows, loot, warnings, err := getAutomationData(sub)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, warning := range warnings {
|
||||
azureLogger.WarnM(warning.Error(), globals.AZ_AUTOMATION_MODULE_NAME)
|
||||
}
|
||||
tenant := populateTenant(ptr.ToString(sub.TenantID))
|
||||
writeAutomationOutput(outputDirectory, ptr.ToString(tenant.DefaultDomain), ptr.ToString(sub.DisplayName), ptr.ToString(sub.DisplayName), rows, loot, verbosity, wrap)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getAutomationData(subscription subscriptions.Subscription) ([][]string, automationLoot, []error, error) {
|
||||
subscriptionID := ptr.ToString(subscription.SubscriptionID)
|
||||
accounts, err := getAutomationAccounts(subscriptionID)
|
||||
if err != nil {
|
||||
return nil, automationLoot{}, nil, fmt.Errorf("could not enumerate Automation Accounts for subscription %s: %w", subscriptionID, err)
|
||||
}
|
||||
var rows [][]string
|
||||
var loot automationLoot
|
||||
var warnings []error
|
||||
for _, account := range accounts {
|
||||
name := ptr.ToString(account.Name)
|
||||
rg := resourceGroupFromID(ptr.ToString(account.ID))
|
||||
assets, err := getAutomationAssets(subscriptionID, rg, name)
|
||||
if err != nil {
|
||||
warnings = append(warnings, fmt.Errorf("could not enumerate assets for Automation Account %s: %w", name, err))
|
||||
continue
|
||||
}
|
||||
rows = append(rows, []string{name, ptr.ToString(subscription.DisplayName), subscriptionID, rg, fmt.Sprintf("%d", len(assets.Runbooks)), fmt.Sprintf("%d", len(assets.Credentials)), fmt.Sprintf("%d", len(assets.Webhooks))})
|
||||
if len(assets.Runbooks)+len(assets.Credentials)+len(assets.Webhooks) > 0 {
|
||||
accountLoot := formatAutomationLoot(subscriptionID, name, rg, assets)
|
||||
loot.Runbooks += accountLoot.Runbooks
|
||||
loot.Credentials += accountLoot.Credentials
|
||||
loot.Webhooks += accountLoot.Webhooks
|
||||
}
|
||||
}
|
||||
sort.Slice(rows, func(i, j int) bool { return rows[i][0] < rows[j][0] })
|
||||
return rows, loot, warnings, nil
|
||||
}
|
||||
|
||||
func formatAutomationLoot(subscriptionID, accountName, resourceGroup string, assets automationAssets) automationLoot {
|
||||
var loot automationLoot
|
||||
var runbooks, credentials, webhooks strings.Builder
|
||||
for _, runbook := range assets.Runbooks {
|
||||
name := ptr.ToString(runbook.Name)
|
||||
fmt.Fprintf(&runbooks, "===============================================================\nAutomation Account: %s\nResource Group: %s\nRunbook: %s\n\n%s\n\n", accountName, resourceGroup, name, assets.RunbookSources[name])
|
||||
}
|
||||
for _, credential := range assets.Credentials {
|
||||
name := ptr.ToString(credential.Name)
|
||||
value, _ := json.MarshalIndent(map[string]any{"name": name, "properties": credential.CredentialProperties}, "", " ")
|
||||
fmt.Fprintf(&credentials, "az automation credential show --automation-account-name %q --resource-group %q --name %q --subscription %q\n%s\n\n", accountName, resourceGroup, name, subscriptionID, value)
|
||||
}
|
||||
for _, webhook := range assets.Webhooks {
|
||||
name := ptr.ToString(webhook.Name)
|
||||
value, _ := json.MarshalIndent(map[string]any{"name": name, "properties": webhook.WebhookProperties}, "", " ")
|
||||
fmt.Fprintf(&webhooks, "az automation webhook show --automation-account-name %q --resource-group %q --name %q --subscription %q\n%s\n\n", accountName, resourceGroup, name, subscriptionID, value)
|
||||
}
|
||||
loot.Runbooks = runbooks.String()
|
||||
loot.Credentials = credentials.String()
|
||||
loot.Webhooks = webhooks.String()
|
||||
return loot
|
||||
}
|
||||
|
||||
func writeAutomationOutput(outputDirectory, tenantDomain, directorySuffix, prefix string, rows [][]string, loot automationLoot, verbosity int, wrap bool) {
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: globals.AZ_AUTOMATION_MODULE_NAME,
|
||||
PrefixIdentifier: prefix,
|
||||
Table: internal.TableClient{
|
||||
Wrap: wrap,
|
||||
DirectoryName: filepath.Join(outputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantDomain, directorySuffix),
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: []string{"Automation Account", "Subscription Name", "Subscription ID", "Resource Group", "Runbooks", "Credentials", "Webhooks"},
|
||||
Body: rows,
|
||||
Name: globals.AZ_AUTOMATION_MODULE_NAME,
|
||||
})
|
||||
o.Loot.DirectoryName = filepath.Join(o.Table.DirectoryName, "loot")
|
||||
if loot.Runbooks != "" {
|
||||
o.Loot.LootFiles = append(o.Loot.LootFiles, internal.LootFile{Name: "automation-runbooks", Contents: loot.Runbooks})
|
||||
}
|
||||
if loot.Credentials != "" {
|
||||
o.Loot.LootFiles = append(o.Loot.LootFiles, internal.LootFile{Name: "automation-credentials", Contents: loot.Credentials})
|
||||
}
|
||||
if loot.Webhooks != "" {
|
||||
o.Loot.LootFiles = append(o.Loot.LootFiles, internal.LootFile{Name: "automation-webhooks", Contents: loot.Webhooks})
|
||||
}
|
||||
if len(o.Loot.LootFiles) > 0 {
|
||||
o.WriteFullOutput(o.Table.TableFiles, o.Loot.LootFiles)
|
||||
} else {
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func getAutomationAccountsOriginal(subscriptionID string) ([]automation.Account, error) {
|
||||
accountsClient, _, _, _ := internal.GetAutomationClients(subscriptionID)
|
||||
page, err := accountsClient.List(context.TODO())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var accounts []automation.Account
|
||||
for page.NotDone() {
|
||||
accounts = append(accounts, page.Values()...)
|
||||
if err := page.NextWithContext(context.TODO()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
func getAutomationAssetsOriginal(subscriptionID, resourceGroup, accountName string) (automationAssets, error) {
|
||||
_, runbooksClient, credentialsClient, webhooksClient := internal.GetAutomationClients(subscriptionID)
|
||||
var assets automationAssets
|
||||
runbooks, err := runbooksClient.ListByAutomationAccount(context.TODO(), resourceGroup, accountName)
|
||||
if err != nil {
|
||||
return assets, err
|
||||
}
|
||||
for runbooks.NotDone() {
|
||||
assets.Runbooks = append(assets.Runbooks, runbooks.Values()...)
|
||||
if err := runbooks.NextWithContext(context.TODO()); err != nil {
|
||||
return assets, err
|
||||
}
|
||||
}
|
||||
assets.RunbookSources = make(map[string]string, len(assets.Runbooks))
|
||||
for _, runbook := range assets.Runbooks {
|
||||
name := ptr.ToString(runbook.Name)
|
||||
content, contentErr := runbooksClient.GetContent(context.TODO(), resourceGroup, accountName, name)
|
||||
if contentErr != nil {
|
||||
assets.RunbookSources[name] = fmt.Sprintf("[source unavailable: %s]", contentErr)
|
||||
continue
|
||||
}
|
||||
assets.RunbookSources[name] = ptr.ToString(content.Value)
|
||||
}
|
||||
credentials, err := credentialsClient.ListByAutomationAccount(context.TODO(), resourceGroup, accountName)
|
||||
if err != nil {
|
||||
return assets, err
|
||||
}
|
||||
for credentials.NotDone() {
|
||||
assets.Credentials = append(assets.Credentials, credentials.Values()...)
|
||||
if err := credentials.NextWithContext(context.TODO()); err != nil {
|
||||
return assets, err
|
||||
}
|
||||
}
|
||||
webhooks, err := webhooksClient.ListByAutomationAccount(context.TODO(), resourceGroup, accountName, "")
|
||||
if err != nil {
|
||||
return assets, err
|
||||
}
|
||||
for webhooks.NotDone() {
|
||||
assets.Webhooks = append(assets.Webhooks, webhooks.Values()...)
|
||||
if err := webhooks.NextWithContext(context.TODO()); err != nil {
|
||||
return assets, err
|
||||
}
|
||||
}
|
||||
return assets, nil
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/automation/mgmt/automation"
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
)
|
||||
|
||||
func TestGetAutomationDataCountsAndLoot(t *testing.T) {
|
||||
originalAccounts, originalAssets := getAutomationAccounts, getAutomationAssets
|
||||
t.Cleanup(func() { getAutomationAccounts, getAutomationAssets = originalAccounts, originalAssets })
|
||||
getAutomationAccounts = func(string) ([]automation.Account, error) {
|
||||
return []automation.Account{{Name: ptr.String("operations"), ID: ptr.String("/subscriptions/sub/resourceGroups/ops/providers/Microsoft.Automation/automationAccounts/operations")}}, nil
|
||||
}
|
||||
getAutomationAssets = func(_, resourceGroup, account string) (automationAssets, error) {
|
||||
if resourceGroup != "ops" || account != "operations" {
|
||||
t.Fatalf("unexpected automation target %s/%s", resourceGroup, account)
|
||||
}
|
||||
return automationAssets{Runbooks: []automation.Runbook{{Name: ptr.String("daily")}, {Name: ptr.String("weekly")}}, RunbookSources: map[string]string{"daily": "Write-Output 'daily'", "weekly": "print('weekly')"}, Credentials: []automation.Credential{{Name: ptr.String("service-account")}}, Webhooks: []automation.Webhook{{Name: ptr.String("deploy")}}}, nil
|
||||
}
|
||||
sub := subscriptions.Subscription{SubscriptionID: ptr.String("sub"), DisplayName: ptr.String("Production")}
|
||||
rows, loot, warnings, err := getAutomationData(sub)
|
||||
if err != nil || len(warnings) != 0 {
|
||||
t.Fatalf("unexpected errors: err=%v warnings=%v", err, warnings)
|
||||
}
|
||||
if len(rows) != 1 || rows[0][4] != "2" || rows[0][5] != "1" || rows[0][6] != "1" {
|
||||
t.Fatalf("unexpected automation rows: %+v", rows)
|
||||
}
|
||||
if !strings.Contains(loot.Runbooks, "Runbook: daily") || !strings.Contains(loot.Runbooks, "Write-Output 'daily'") {
|
||||
t.Errorf("runbook loot is incomplete: %s", loot.Runbooks)
|
||||
}
|
||||
for _, value := range []string{"az automation credential show", "service-account", `"name": "service-account"`} {
|
||||
if !strings.Contains(loot.Credentials, value) {
|
||||
t.Errorf("credential loot missing %q: %s", value, loot.Credentials)
|
||||
}
|
||||
}
|
||||
for _, value := range []string{"az automation webhook show", "deploy", `"name": "deploy"`} {
|
||||
if !strings.Contains(loot.Webhooks, value) {
|
||||
t.Errorf("webhook loot missing %q: %s", value, loot.Webhooks)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,526 +0,0 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions"
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/web/mgmt/web"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
)
|
||||
|
||||
var (
|
||||
getFunctionApps = getFunctionAppsOriginal
|
||||
getFunctionConfiguration = getFunctionConfigurationOriginal
|
||||
getFunctions = getFunctionsOriginal
|
||||
)
|
||||
|
||||
type functionConfiguration struct {
|
||||
AppSettings map[string]*string
|
||||
ConnectionStrings map[string]*web.ConnStringValueTypePair
|
||||
AuthSettings web.SiteAuthSettingsV2
|
||||
}
|
||||
|
||||
type functionLoot struct {
|
||||
Configuration string
|
||||
TestData string
|
||||
}
|
||||
|
||||
func AzFuncCommand(tenantID, subscription, outputDirectory, version string, verbosity int, wrap, merged bool) error {
|
||||
if tenantID != "" && subscription == "" {
|
||||
tenant := populateTenant(tenantID)
|
||||
if ptr.ToString(tenant.ID) == "" {
|
||||
return fmt.Errorf("could not find Azure tenant %q", tenantID)
|
||||
}
|
||||
azureLogger.InfoM(fmt.Sprintf("Enumerating Function Apps for tenant %s (%s)", ptr.ToString(tenant.DefaultDomain), ptr.ToString(tenant.ID)), globals.AZ_FUNC_MODULE_NAME)
|
||||
subs := GetSubscriptionsPerTenantID(ptr.ToString(tenant.ID))
|
||||
if !merged {
|
||||
for _, sub := range subs {
|
||||
if err := runFuncForSubscription(sub, outputDirectory, verbosity, wrap); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var rows [][]string
|
||||
var loot functionLoot
|
||||
for _, sub := range subs {
|
||||
subRows, subLoot, warnings, err := getFunctionData(sub)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rows = append(rows, subRows...)
|
||||
loot.Configuration += subLoot.Configuration
|
||||
loot.TestData += subLoot.TestData
|
||||
for _, warning := range warnings {
|
||||
azureLogger.WarnM(warning.Error(), globals.AZ_FUNC_MODULE_NAME)
|
||||
}
|
||||
}
|
||||
if len(subs) > 0 {
|
||||
writeFunctionOutput(outputDirectory, ptr.ToString(tenant.DefaultDomain), "1-tenant-level", ptr.ToString(tenant.DefaultDomain), rows, loot, verbosity, wrap)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if tenantID == "" && subscription != "" {
|
||||
id := ptr.ToString(GetSubscriptionID(subscription))
|
||||
for _, sub := range GetSubscriptions() {
|
||||
if ptr.ToString(sub.SubscriptionID) == id {
|
||||
return runFuncForSubscription(sub, outputDirectory, verbosity, wrap)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("could not find Azure subscription %q", subscription)
|
||||
}
|
||||
return fmt.Errorf("please specify either --tenant or --subscription")
|
||||
}
|
||||
|
||||
func runFuncForSubscription(sub subscriptions.Subscription, outputDirectory string, verbosity int, wrap bool) error {
|
||||
azureLogger.InfoM(fmt.Sprintf("Enumerating Function Apps for subscription %s (%s)", ptr.ToString(sub.DisplayName), ptr.ToString(sub.SubscriptionID)), globals.AZ_FUNC_MODULE_NAME)
|
||||
rows, loot, warnings, err := getFunctionData(sub)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, warning := range warnings {
|
||||
azureLogger.WarnM(warning.Error(), globals.AZ_FUNC_MODULE_NAME)
|
||||
}
|
||||
tenant := populateTenant(ptr.ToString(sub.TenantID))
|
||||
writeFunctionOutput(outputDirectory, ptr.ToString(tenant.DefaultDomain), ptr.ToString(sub.DisplayName), ptr.ToString(sub.DisplayName), rows, loot, verbosity, wrap)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getFunctionData(subscription subscriptions.Subscription) ([][]string, functionLoot, []error, error) {
|
||||
subscriptionID := ptr.ToString(subscription.SubscriptionID)
|
||||
apps, err := getFunctionApps(subscriptionID)
|
||||
if err != nil {
|
||||
return nil, functionLoot{}, nil, fmt.Errorf("could not enumerate Function Apps for subscription %s: %w", subscriptionID, err)
|
||||
}
|
||||
var rows [][]string
|
||||
var loot functionLoot
|
||||
var warnings []error
|
||||
for _, app := range apps {
|
||||
if !strings.Contains(strings.ToLower(ptr.ToString(app.Kind)), "functionapp") {
|
||||
continue
|
||||
}
|
||||
name := ptr.ToString(app.Name)
|
||||
host := ""
|
||||
if app.SiteProperties != nil {
|
||||
host = ptr.ToString(app.DefaultHostName)
|
||||
}
|
||||
appURL := host
|
||||
if host != "" {
|
||||
appURL = "https://" + host
|
||||
}
|
||||
rg := resourceGroupFromID(ptr.ToString(app.ID))
|
||||
config, err := getFunctionConfiguration(subscriptionID, rg, name)
|
||||
if err != nil {
|
||||
warnings = append(warnings, fmt.Errorf("could not read configuration for Function App %s: %w", name, err))
|
||||
}
|
||||
providers := functionAuthProviders(config.AuthSettings)
|
||||
functions, err := getFunctions(subscriptionID, rg, name)
|
||||
if err != nil {
|
||||
warnings = append(warnings, fmt.Errorf("could not enumerate functions for Function App %s: %w", name, err))
|
||||
rows = append(rows, functionAppFallbackRow(name, subscription, appURL, providers))
|
||||
} else if len(functions) == 0 {
|
||||
rows = append(rows, functionAppFallbackRow(name, subscription, appURL, providers))
|
||||
} else {
|
||||
for _, function := range functions {
|
||||
functionName := lastResourceIDSegment(ptr.ToString(function.Name))
|
||||
trigger := "Unknown"
|
||||
state := "Unknown"
|
||||
if function.FunctionEnvelopeProperties != nil {
|
||||
trigger = functionTrigger(function.Config, ptr.ToString(function.InvokeURLTemplate))
|
||||
state = "Enabled"
|
||||
if ptr.ToBool(function.IsDisabled) {
|
||||
state = "Disabled"
|
||||
}
|
||||
loot.TestData += formatFunctionTestDataLoot(subscriptionID, rg, name, functionName, ptr.ToString(function.TestData))
|
||||
}
|
||||
authMethods := append(functionAuthLevels(function.Config), providers...)
|
||||
if len(authMethods) == 0 {
|
||||
authMethods = []string{"Unknown"}
|
||||
}
|
||||
rows = append(rows, []string{name, functionName, trigger, strings.Join(uniqueStrings(authMethods), ", "), state, ptr.ToString(subscription.DisplayName), subscriptionID})
|
||||
}
|
||||
}
|
||||
loot.Configuration += formatFunctionLoot(name, appURL, config)
|
||||
}
|
||||
sort.Slice(rows, func(i, j int) bool {
|
||||
if rows[i][0] == rows[j][0] {
|
||||
return rows[i][1] < rows[j][1]
|
||||
}
|
||||
return rows[i][0] < rows[j][0]
|
||||
})
|
||||
return rows, loot, warnings, nil
|
||||
}
|
||||
|
||||
func functionAppFallbackRow(name string, subscription subscriptions.Subscription, appURL string, providers []string) []string {
|
||||
authentication := strings.Join(providers, ", ")
|
||||
if authentication == "" {
|
||||
authentication = "Unknown"
|
||||
}
|
||||
trigger := "Unknown"
|
||||
if appURL != "" {
|
||||
trigger = "App URL: " + appURL
|
||||
}
|
||||
return []string{name, "", trigger, authentication, "Unknown", ptr.ToString(subscription.DisplayName), ptr.ToString(subscription.SubscriptionID)}
|
||||
}
|
||||
|
||||
func functionTableHeader() []string {
|
||||
return []string{"Function App", "Function Name", "Trigger", "Authentication", "State", "Subscription Name", "Subscription ID"}
|
||||
}
|
||||
|
||||
func functionVisibleColumns() []string {
|
||||
return []string{"Function App", "Function Name", "Trigger", "Authentication", "State", "Subscription Name"}
|
||||
}
|
||||
|
||||
func formatFunctionLoot(name, appURL string, config functionConfiguration) string {
|
||||
if len(config.AppSettings) == 0 && len(config.ConnectionStrings) == 0 {
|
||||
return ""
|
||||
}
|
||||
var loot strings.Builder
|
||||
fmt.Fprintf(&loot, "===============================================================\nFunction App: %s\nURL: %s\n\n", name, appURL)
|
||||
fmt.Fprintf(&loot, "App Service Authentication: %s\n\n", strings.Join(functionAuthProviders(config.AuthSettings), ", "))
|
||||
loot.WriteString("App Settings:\n")
|
||||
settings := sortedKeys(config.AppSettings)
|
||||
for _, key := range settings {
|
||||
fmt.Fprintf(&loot, "%s=%s\n", key, ptr.ToString(config.AppSettings[key]))
|
||||
}
|
||||
loot.WriteString("\nConnection Strings:\n")
|
||||
connections := make([]string, 0, len(config.ConnectionStrings))
|
||||
for key := range config.ConnectionStrings {
|
||||
connections = append(connections, key)
|
||||
}
|
||||
sort.Strings(connections)
|
||||
for _, key := range connections {
|
||||
value := config.ConnectionStrings[key]
|
||||
if value != nil {
|
||||
fmt.Fprintf(&loot, "%s=%s (type=%s)\n", key, ptr.ToString(value.Value), value.Type)
|
||||
}
|
||||
}
|
||||
loot.WriteString("\n")
|
||||
return loot.String()
|
||||
}
|
||||
|
||||
func functionAuthProviders(settings web.SiteAuthSettingsV2) []string {
|
||||
if settings.SiteAuthSettingsV2Properties == nil || settings.Platform == nil || !ptr.ToBool(settings.Platform.Enabled) {
|
||||
return nil
|
||||
}
|
||||
providers := settings.IdentityProviders
|
||||
if providers == nil {
|
||||
return []string{"App Service Authentication enabled"}
|
||||
}
|
||||
var methods []string
|
||||
if providers.AzureActiveDirectory != nil && ptr.ToBool(providers.AzureActiveDirectory.Enabled) {
|
||||
methods = append(methods, "Entra ID")
|
||||
}
|
||||
if providers.Facebook != nil && ptr.ToBool(providers.Facebook.Enabled) {
|
||||
methods = append(methods, "Facebook")
|
||||
}
|
||||
if providers.GitHub != nil && ptr.ToBool(providers.GitHub.Enabled) {
|
||||
methods = append(methods, "GitHub")
|
||||
}
|
||||
if providers.Google != nil && ptr.ToBool(providers.Google.Enabled) {
|
||||
methods = append(methods, "Google")
|
||||
}
|
||||
if providers.LegacyMicrosoftAccount != nil && ptr.ToBool(providers.LegacyMicrosoftAccount.Enabled) {
|
||||
methods = append(methods, "Microsoft Account")
|
||||
}
|
||||
if providers.Twitter != nil && ptr.ToBool(providers.Twitter.Enabled) {
|
||||
methods = append(methods, "Twitter")
|
||||
}
|
||||
if providers.Apple != nil && ptr.ToBool(providers.Apple.Enabled) {
|
||||
methods = append(methods, "Apple")
|
||||
}
|
||||
if providers.AzureStaticWebApps != nil && ptr.ToBool(providers.AzureStaticWebApps.Enabled) {
|
||||
methods = append(methods, "Azure Static Web Apps")
|
||||
}
|
||||
for name, provider := range providers.CustomOpenIDConnectProviders {
|
||||
if provider != nil && ptr.ToBool(provider.Enabled) {
|
||||
methods = append(methods, "OpenID Connect: "+name)
|
||||
}
|
||||
}
|
||||
sort.Strings(methods)
|
||||
return methods
|
||||
}
|
||||
|
||||
func functionAuthLevels(config any) []string {
|
||||
encoded, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var value any
|
||||
if json.Unmarshal(encoded, &value) != nil {
|
||||
return nil
|
||||
}
|
||||
var levels []string
|
||||
var visit func(any)
|
||||
visit = func(current any) {
|
||||
switch typed := current.(type) {
|
||||
case map[string]any:
|
||||
for key, child := range typed {
|
||||
if strings.EqualFold(key, "authLevel") {
|
||||
if level, ok := child.(string); ok {
|
||||
levels = append(levels, formatFunctionAuthLevel(level))
|
||||
}
|
||||
} else {
|
||||
visit(child)
|
||||
}
|
||||
}
|
||||
case []any:
|
||||
for _, child := range typed {
|
||||
visit(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
visit(value)
|
||||
return uniqueStrings(levels)
|
||||
}
|
||||
|
||||
func functionTrigger(config any, invokeURL string) string {
|
||||
encoded, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return "Unknown"
|
||||
}
|
||||
var value any
|
||||
if json.Unmarshal(encoded, &value) != nil {
|
||||
return "Unknown"
|
||||
}
|
||||
result := ""
|
||||
var visit func(any)
|
||||
visit = func(current any) {
|
||||
if result != "" {
|
||||
return
|
||||
}
|
||||
switch typed := current.(type) {
|
||||
case map[string]any:
|
||||
bindingType := mapStringValue(typed, "type")
|
||||
if strings.HasSuffix(strings.ToLower(bindingType), "trigger") {
|
||||
label := functionTriggerLabel(bindingType)
|
||||
detail := functionTriggerDetail(bindingType, typed, invokeURL)
|
||||
result = label
|
||||
if detail != "" {
|
||||
result += ": " + detail
|
||||
}
|
||||
return
|
||||
}
|
||||
for _, child := range typed {
|
||||
visit(child)
|
||||
}
|
||||
case []any:
|
||||
for _, child := range typed {
|
||||
visit(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
visit(value)
|
||||
if result == "" {
|
||||
return "Unknown"
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func functionTriggerLabel(bindingType string) string {
|
||||
switch strings.ToLower(bindingType) {
|
||||
case "httptrigger":
|
||||
return "HTTP"
|
||||
case "timertrigger":
|
||||
return "Time Trigger"
|
||||
case "blobtrigger":
|
||||
return "Blob Trigger"
|
||||
case "queuetrigger":
|
||||
return "Queue Trigger"
|
||||
case "servicebustrigger":
|
||||
return "Service Bus Trigger"
|
||||
case "eventhubtrigger":
|
||||
return "Event Hub Trigger"
|
||||
case "eventgridtrigger":
|
||||
return "Event Grid Trigger"
|
||||
case "cosmosdbtrigger":
|
||||
return "Cosmos DB Trigger"
|
||||
case "kafkatrigger":
|
||||
return "Kafka Trigger"
|
||||
default:
|
||||
name := strings.TrimSuffix(bindingType, "Trigger")
|
||||
return name + " Trigger"
|
||||
}
|
||||
}
|
||||
|
||||
func functionTriggerDetail(bindingType string, binding map[string]any, invokeURL string) string {
|
||||
switch strings.ToLower(bindingType) {
|
||||
case "httptrigger":
|
||||
return invokeURL
|
||||
case "timertrigger":
|
||||
return mapStringValue(binding, "schedule")
|
||||
case "blobtrigger":
|
||||
return mapStringValue(binding, "path")
|
||||
case "queuetrigger":
|
||||
return mapStringValue(binding, "queueName")
|
||||
case "servicebustrigger":
|
||||
return firstNonEmpty(mapStringValue(binding, "queueName"), mapStringValue(binding, "topicName"))
|
||||
case "eventhubtrigger":
|
||||
return mapStringValue(binding, "eventHubName")
|
||||
case "cosmosdbtrigger":
|
||||
database := mapStringValue(binding, "databaseName")
|
||||
container := firstNonEmpty(mapStringValue(binding, "containerName"), mapStringValue(binding, "collectionName"))
|
||||
return strings.Trim(strings.Join([]string{database, container}, "/"), "/")
|
||||
case "kafkatrigger":
|
||||
return firstNonEmpty(mapStringValue(binding, "topic"), mapStringValue(binding, "brokerList"))
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func mapStringValue(values map[string]any, name string) string {
|
||||
for key, value := range values {
|
||||
if strings.EqualFold(key, name) {
|
||||
if text, ok := value.(string); ok {
|
||||
return text
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, value := range values {
|
||||
if value != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func formatFunctionTestDataLoot(subscriptionID, resourceGroup, appName, functionName, testData string) string {
|
||||
if testData == "" {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("az functionapp function show --name %q --resource-group %q --function-name %q --subscription %q --query testData --output tsv\n%s\n\n", appName, resourceGroup, functionName, subscriptionID, testData)
|
||||
}
|
||||
|
||||
func formatFunctionAuthLevel(level string) string {
|
||||
switch strings.ToLower(level) {
|
||||
case "anonymous":
|
||||
return "Anonymous"
|
||||
case "function":
|
||||
return "Function key"
|
||||
case "admin":
|
||||
return "Admin/master key"
|
||||
default:
|
||||
return level
|
||||
}
|
||||
}
|
||||
|
||||
func uniqueStrings(values []string) []string {
|
||||
seen := map[string]bool{}
|
||||
var result []string
|
||||
for _, value := range values {
|
||||
if value != "" && !seen[value] {
|
||||
seen[value] = true
|
||||
result = append(result, value)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func sortedKeys(values map[string]*string) []string {
|
||||
keys := make([]string, 0, len(values))
|
||||
for key := range values {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
func getFunctionAppsOriginal(subscriptionID string) ([]web.Site, error) {
|
||||
client := internal.GetWebAppsClient(subscriptionID)
|
||||
page, err := client.List(context.TODO())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var apps []web.Site
|
||||
for page.NotDone() {
|
||||
apps = append(apps, page.Values()...)
|
||||
if err := page.NextWithContext(context.TODO()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return apps, nil
|
||||
}
|
||||
|
||||
func getFunctionConfigurationOriginal(subscriptionID, resourceGroup, name string) (functionConfiguration, error) {
|
||||
client := internal.GetWebAppsClient(subscriptionID)
|
||||
var config functionConfiguration
|
||||
settings, err := client.ListApplicationSettings(context.TODO(), resourceGroup, name)
|
||||
if err != nil {
|
||||
return config, err
|
||||
}
|
||||
config.AppSettings = settings.Properties
|
||||
connections, err := client.ListConnectionStrings(context.TODO(), resourceGroup, name)
|
||||
if err != nil {
|
||||
return config, err
|
||||
}
|
||||
config.ConnectionStrings = connections.Properties
|
||||
authSettings, err := client.GetAuthSettingsV2WithoutSecrets(context.TODO(), resourceGroup, name)
|
||||
if err != nil {
|
||||
return config, err
|
||||
}
|
||||
config.AuthSettings = authSettings
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func getFunctionsOriginal(subscriptionID, resourceGroup, appName string) ([]web.FunctionEnvelope, error) {
|
||||
client := internal.GetWebAppsClient(subscriptionID)
|
||||
page, err := client.ListFunctions(context.TODO(), resourceGroup, appName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var functions []web.FunctionEnvelope
|
||||
for page.NotDone() {
|
||||
functions = append(functions, page.Values()...)
|
||||
if err := page.NextWithContext(context.TODO()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return functions, nil
|
||||
}
|
||||
|
||||
func resourceGroupFromID(id string) string {
|
||||
parts := strings.Split(strings.Trim(id, "/"), "/")
|
||||
for i := 0; i+1 < len(parts); i++ {
|
||||
if strings.EqualFold(parts[i], "resourceGroups") {
|
||||
return parts[i+1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func writeFunctionOutput(outputDirectory, tenantDomain, directorySuffix, prefix string, rows [][]string, loot functionLoot, verbosity int, wrap bool) {
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: globals.AZ_FUNC_MODULE_NAME,
|
||||
PrefixIdentifier: prefix,
|
||||
Table: internal.TableClient{
|
||||
Wrap: wrap,
|
||||
DirectoryName: filepath.Join(outputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, tenantDomain, directorySuffix),
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{Header: functionTableHeader(), Body: rows, Name: globals.AZ_FUNC_MODULE_NAME, TableCols: functionVisibleColumns()})
|
||||
o.Loot.DirectoryName = filepath.Join(o.Table.DirectoryName, "loot")
|
||||
if loot.Configuration != "" {
|
||||
o.Loot.LootFiles = append(o.Loot.LootFiles, internal.LootFile{Name: "function-app-configuration", Contents: loot.Configuration})
|
||||
}
|
||||
if loot.TestData != "" {
|
||||
o.Loot.LootFiles = append(o.Loot.LootFiles, internal.LootFile{Name: "function-test-data", Contents: loot.TestData})
|
||||
}
|
||||
if len(o.Loot.LootFiles) > 0 {
|
||||
o.WriteFullOutput(o.Table.TableFiles, o.Loot.LootFiles)
|
||||
} else {
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions"
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/web/mgmt/web"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
)
|
||||
|
||||
func TestGetFunctionDataAndConfigurationLoot(t *testing.T) {
|
||||
originalApps, originalConfig, originalFunctions := getFunctionApps, getFunctionConfiguration, getFunctions
|
||||
t.Cleanup(func() {
|
||||
getFunctionApps, getFunctionConfiguration, getFunctions = originalApps, originalConfig, originalFunctions
|
||||
})
|
||||
getFunctionApps = func(string) ([]web.Site, error) {
|
||||
return []web.Site{
|
||||
{Name: ptr.String("process-orders"), Kind: ptr.String("functionapp,linux"), ID: ptr.String("/subscriptions/sub/resourceGroups/apps/providers/Microsoft.Web/sites/process-orders"), SiteProperties: &web.SiteProperties{DefaultHostName: ptr.String("process-orders.azurewebsites.net")}},
|
||||
{Name: ptr.String("ordinary-web-app"), Kind: ptr.String("app")},
|
||||
}, nil
|
||||
}
|
||||
getFunctionConfiguration = func(_, resourceGroup, name string) (functionConfiguration, error) {
|
||||
if resourceGroup != "apps" || name != "process-orders" {
|
||||
t.Fatalf("unexpected configuration target %s/%s", resourceGroup, name)
|
||||
}
|
||||
return functionConfiguration{AppSettings: map[string]*string{"API_KEY": ptr.String("secret")}, ConnectionStrings: map[string]*web.ConnStringValueTypePair{"Database": {Value: ptr.String("Server=db")}}, AuthSettings: web.SiteAuthSettingsV2{SiteAuthSettingsV2Properties: &web.SiteAuthSettingsV2Properties{Platform: &web.AuthPlatform{Enabled: ptr.Bool(true)}, IdentityProviders: &web.IdentityProviders{AzureActiveDirectory: &web.AzureActiveDirectory{Enabled: ptr.Bool(true)}}}}}, nil
|
||||
}
|
||||
getFunctions = func(_, _, _ string) ([]web.FunctionEnvelope, error) {
|
||||
return []web.FunctionEnvelope{{Name: ptr.String("process-orders/HttpTrigger"), FunctionEnvelopeProperties: &web.FunctionEnvelopeProperties{InvokeURLTemplate: ptr.String("https://process-orders.azurewebsites.net/api/orders"), Config: map[string]any{"bindings": []any{map[string]any{"type": "httpTrigger", "authLevel": "function"}}}, IsDisabled: ptr.Bool(false), TestData: ptr.String(`{"orderId":42}`)}}}, nil
|
||||
}
|
||||
sub := subscriptions.Subscription{SubscriptionID: ptr.String("sub"), DisplayName: ptr.String("Production")}
|
||||
rows, loot, warnings, err := getFunctionData(sub)
|
||||
if err != nil || len(warnings) != 0 {
|
||||
t.Fatalf("unexpected errors: err=%v warnings=%v", err, warnings)
|
||||
}
|
||||
if len(rows) != 1 || rows[0][0] != "process-orders" || rows[0][1] != "HttpTrigger" || rows[0][2] != "HTTP: https://process-orders.azurewebsites.net/api/orders" || !strings.Contains(rows[0][3], "Function key") || !strings.Contains(rows[0][3], "Entra ID") || rows[0][4] != "Enabled" || rows[0][5] != "Production" || rows[0][6] != "sub" {
|
||||
t.Fatalf("unexpected function rows: %+v", rows)
|
||||
}
|
||||
for _, value := range []string{"API_KEY=secret", "Database=Server=db", "Entra ID"} {
|
||||
if !strings.Contains(loot.Configuration, value) {
|
||||
t.Errorf("loot missing %q: %s", value, loot.Configuration)
|
||||
}
|
||||
}
|
||||
for _, value := range []string{"az functionapp function show", `--function-name "HttpTrigger"`, `{"orderId":42}`} {
|
||||
if !strings.Contains(loot.TestData, value) {
|
||||
t.Errorf("test-data loot missing %q: %s", value, loot.TestData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFunctionDataFallsBackWhenEnrichmentFails(t *testing.T) {
|
||||
originalApps, originalConfig, originalFunctions := getFunctionApps, getFunctionConfiguration, getFunctions
|
||||
t.Cleanup(func() {
|
||||
getFunctionApps, getFunctionConfiguration, getFunctions = originalApps, originalConfig, originalFunctions
|
||||
})
|
||||
getFunctionApps = func(string) ([]web.Site, error) {
|
||||
return []web.Site{{Name: ptr.String("visible-app"), Kind: ptr.String("functionapp"), ID: ptr.String("/subscriptions/sub/resourceGroups/apps/providers/Microsoft.Web/sites/visible-app"), SiteProperties: &web.SiteProperties{DefaultHostName: ptr.String("visible.azurewebsites.net")}}}, nil
|
||||
}
|
||||
getFunctionConfiguration = func(_, _, _ string) (functionConfiguration, error) {
|
||||
return functionConfiguration{}, errors.New("forbidden")
|
||||
}
|
||||
getFunctions = func(_, _, _ string) ([]web.FunctionEnvelope, error) { return nil, nil }
|
||||
|
||||
rows, _, warnings, err := getFunctionData(subscriptions.Subscription{SubscriptionID: ptr.String("sub"), DisplayName: ptr.String("Production")})
|
||||
if err != nil || len(rows) != 1 || rows[0][0] != "visible-app" || rows[0][2] != "App URL: https://visible.azurewebsites.net" || rows[0][4] != "Unknown" || len(warnings) != 1 {
|
||||
t.Fatalf("expected visible fallback row and warning, got rows=%+v warnings=%v err=%v", rows, warnings, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetFunctionDataUsesCronScheduleAsTrigger(t *testing.T) {
|
||||
originalApps, originalConfig, originalFunctions := getFunctionApps, getFunctionConfiguration, getFunctions
|
||||
t.Cleanup(func() {
|
||||
getFunctionApps, getFunctionConfiguration, getFunctions = originalApps, originalConfig, originalFunctions
|
||||
})
|
||||
getFunctionApps = func(string) ([]web.Site, error) {
|
||||
return []web.Site{{Name: ptr.String("scheduled-app"), Kind: ptr.String("functionapp"), ID: ptr.String("/subscriptions/sub/resourceGroups/apps/providers/Microsoft.Web/sites/scheduled-app")}}, nil
|
||||
}
|
||||
getFunctionConfiguration = func(_, _, _ string) (functionConfiguration, error) { return functionConfiguration{}, nil }
|
||||
getFunctions = func(_, _, _ string) ([]web.FunctionEnvelope, error) {
|
||||
return []web.FunctionEnvelope{{
|
||||
Name: ptr.String("scheduled-app/Cleanup"),
|
||||
FunctionEnvelopeProperties: &web.FunctionEnvelopeProperties{
|
||||
Config: map[string]any{"bindings": []any{map[string]any{"type": "timerTrigger", "schedule": "0 */5 * * * *"}}},
|
||||
IsDisabled: ptr.Bool(true),
|
||||
},
|
||||
}}, nil
|
||||
}
|
||||
rows, _, _, err := getFunctionData(subscriptions.Subscription{SubscriptionID: ptr.String("sub"), DisplayName: ptr.String("Production")})
|
||||
if err != nil || len(rows) != 1 || rows[0][2] != "Time Trigger: 0 */5 * * * *" || rows[0][4] != "Disabled" {
|
||||
t.Fatalf("expected CRON schedule trigger, got rows=%+v err=%v", rows, err)
|
||||
}
|
||||
}
|
||||
+12
-4
@@ -10,6 +10,8 @@ import (
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"github.com/fatih/color"
|
||||
"github.com/kyokomi/emoji"
|
||||
)
|
||||
|
||||
func AzInventoryCommand(AzTenantID, AzSubscriptionID, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error {
|
||||
@@ -38,7 +40,10 @@ func AzInventoryCommand(AzTenantID, AzSubscriptionID, AzOutputDirectory, Version
|
||||
},
|
||||
}
|
||||
|
||||
azureLogger.InfoM(fmt.Sprintf("Gathering inventory for tenant %s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID)), o.CallingModule)
|
||||
fmt.Printf(
|
||||
"[%s][%s] Gathering inventory for subscription %s\n",
|
||||
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(o.CallingModule),
|
||||
fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID)))
|
||||
|
||||
o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain)
|
||||
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level")
|
||||
@@ -52,7 +57,7 @@ func AzInventoryCommand(AzTenantID, AzSubscriptionID, AzOutputDirectory, Version
|
||||
internal.TableFile{
|
||||
Header: header,
|
||||
Body: body,
|
||||
Name: o.CallingModule})
|
||||
Name: fmt.Sprintf(o.CallingModule)})
|
||||
|
||||
if body != nil {
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
@@ -71,7 +76,7 @@ func AzInventoryCommand(AzTenantID, AzSubscriptionID, AzOutputDirectory, Version
|
||||
|
||||
} else {
|
||||
// Error: please make a valid flag selection
|
||||
azureLogger.ErrorM("Please enter a valid input with a valid flag. Use --help for info.", globals.AZ_INVENTORY_MODULE_NAME)
|
||||
fmt.Println("Please enter a valid input with a valid flag. Use --help for info.")
|
||||
}
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
return nil
|
||||
@@ -96,7 +101,10 @@ func runInventoryCommandForSingleSubscription(AzSubscription string, AzOutputDir
|
||||
o.PrefixIdentifier = AzSubscriptionInfo.Name
|
||||
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name)
|
||||
|
||||
azureLogger.InfoM(fmt.Sprintf("Gathering inventory for subscription %s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID), o.CallingModule)
|
||||
fmt.Printf(
|
||||
"[%s][%s] Gathering inventory for subscription %s\n",
|
||||
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(o.CallingModule),
|
||||
fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID))
|
||||
|
||||
// populate the table data
|
||||
header, body, err = getInventoryInfoPerSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID)
|
||||
|
||||
+31
-22
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"github.com/fatih/color"
|
||||
"github.com/kyokomi/emoji"
|
||||
)
|
||||
|
||||
func AzRBACCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error {
|
||||
@@ -37,13 +39,19 @@ func AzRBACCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory
|
||||
if AzTenantID != "" && AzSubscription == "" {
|
||||
// cloudfox azure rbac --tenant [TENANT_ID | PRIMARY_DOMAIN]
|
||||
|
||||
var err error
|
||||
tenantInfo := populateTenant(AzTenantID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain)
|
||||
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level")
|
||||
|
||||
azureLogger.InfoM(fmt.Sprintf("Enumerating RBAC permissions for tenant %s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID)), globals.AZ_RBAC_MODULE_NAME)
|
||||
fmt.Printf("[%s][%s] Enumerating RBAC permissions for tenant %s\n",
|
||||
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME),
|
||||
fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID)))
|
||||
|
||||
header, body, err := getRBACperTenant(ptr.ToString(tenantInfo.ID), c)
|
||||
header, body, err = getRBACperTenant(ptr.ToString(tenantInfo.ID), c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -61,7 +69,8 @@ func AzRBACCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory
|
||||
o.PrefixIdentifier = AzSubscriptionInfo.Name
|
||||
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name)
|
||||
|
||||
azureLogger.InfoM(fmt.Sprintf("Enumerating RBAC permissions for subscription %s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID), globals.AZ_RBAC_MODULE_NAME)
|
||||
fmt.Printf("[%s][%s] Enumerating RBAC permissions for subscription %s\n", color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME),
|
||||
fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID))
|
||||
header, body = getRBACperSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID, c)
|
||||
o.Table.TableFiles = append(o.Table.TableFiles,
|
||||
internal.TableFile{
|
||||
@@ -71,7 +80,7 @@ func AzRBACCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory
|
||||
|
||||
} else {
|
||||
// Error: please make a valid flag selection
|
||||
azureLogger.ErrorM("Please enter a valid input with a valid flag. Use --help for info.", globals.AZ_RBAC_MODULE_NAME)
|
||||
fmt.Println("Please enter a valid input with a valid flag. Use --help for info.")
|
||||
}
|
||||
|
||||
if body != nil {
|
||||
@@ -133,13 +142,13 @@ func (c *CloudFoxRBACclient) initialize(tenantID string, subscriptionIDs []strin
|
||||
for _, subID := range subscriptionIDs {
|
||||
rd, err := getRoleDefinitions(subID)
|
||||
if err != nil {
|
||||
azureLogger.WarnM(fmt.Sprintf("Failed to get role definitions for subscription %s: %s. Skipping it.", subID, err), globals.AZ_RBAC_MODULE_NAME)
|
||||
fmt.Printf("[%s] failed to get role definitions for subscription %s: %s. Skipping it.\n", color.New(color.FgCyan).Sprint(globals.AZ_RBAC_MODULE_NAME), subID, err)
|
||||
}
|
||||
c.roleDefinitions = append(c.roleDefinitions, rd...)
|
||||
|
||||
ra, err := getRoleAssignments(subID)
|
||||
if err != nil {
|
||||
azureLogger.WarnM(fmt.Sprintf("Failed to get role assignments for subscription %s: %s. Skipping it.", subID, err), globals.AZ_RBAC_MODULE_NAME)
|
||||
fmt.Printf("[%s] failed to get role assignments for subscription %s: %s. Skipping it.\n", color.New(color.FgCyan).Sprint(globals.AZ_RBAC_MODULE_NAME), subID, err)
|
||||
}
|
||||
c.roleAssignments = append(c.roleAssignments, ra...)
|
||||
}
|
||||
@@ -230,11 +239,11 @@ func mockedGetAzureADUsers(tenantID string) ([]graphrbac.User, error) {
|
||||
|
||||
file, err := os.ReadFile(globals.AAD_USERS_TEST_FILE)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(fmt.Sprintf("Could not read file %s", globals.AAD_USERS_TEST_FILE), globals.AZ_RBAC_MODULE_NAME)
|
||||
log.Fatalf("could not read file %s", globals.AAD_USERS_TEST_FILE)
|
||||
}
|
||||
err = json.Unmarshal(file, &users)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(fmt.Sprintf("Could not unmarshal file %s", globals.AAD_USERS_TEST_FILE), globals.AZ_RBAC_MODULE_NAME)
|
||||
log.Fatalf("could not unmarshall file %s", globals.AAD_USERS_TEST_FILE)
|
||||
}
|
||||
return users.AzureADUsers, nil
|
||||
}
|
||||
@@ -244,15 +253,15 @@ func generateAzureADUsersTestFIle(tenantID string) {
|
||||
// ObjectID *string `json:"objectId,omitempty"`
|
||||
users, err := getAzureADUsers(tenantID)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(fmt.Sprintf("Could not enumerate users for tenant %s", tenantID), globals.AZ_RBAC_MODULE_NAME)
|
||||
log.Fatalf("could not enumerate users for tenant %s", tenantID)
|
||||
}
|
||||
usersJSON, err := json.Marshal(AzureADUsersTestFile{AzureADUsers: users})
|
||||
if err != nil {
|
||||
azureLogger.FatalM(fmt.Sprintf("Could not marshal JSON for Azure AD users in tenant %s", tenantID), globals.AZ_RBAC_MODULE_NAME)
|
||||
log.Fatalf("could not marshall json for azure ad users in tenant %s", tenantID)
|
||||
}
|
||||
err = os.WriteFile(globals.AAD_USERS_TEST_FILE, usersJSON, os.ModeAppend)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(fmt.Sprintf("Could not write to Azure AD users test file %s", globals.AAD_USERS_TEST_FILE), globals.AZ_RBAC_MODULE_NAME)
|
||||
log.Fatalf("could not write to azure ad users test file %s", globals.AAD_USERS_TEST_FILE)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,11 +291,11 @@ func mockedGetRoleDefinitions(subscriptionID string) ([]authorization.RoleDefini
|
||||
var roleDefinitions RoleDefinitionTestFile
|
||||
file, err := os.ReadFile(globals.ROLE_DEFINITIONS_TEST_FILE)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(fmt.Sprintf("Could not read file %s", globals.ROLE_DEFINITIONS_TEST_FILE), globals.AZ_RBAC_MODULE_NAME)
|
||||
log.Fatalf("could not read file %s", globals.ROLE_DEFINITIONS_TEST_FILE)
|
||||
}
|
||||
err = json.Unmarshal(file, &roleDefinitions)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(fmt.Sprintf("Could not unmarshal file %s", globals.ROLE_DEFINITIONS_TEST_FILE), globals.AZ_RBAC_MODULE_NAME)
|
||||
log.Fatalf("could not unmarshall file %s", globals.ROLE_DEFINITIONS_TEST_FILE)
|
||||
}
|
||||
return roleDefinitions.RoleDefinitions, nil
|
||||
}
|
||||
@@ -298,11 +307,11 @@ func generateRoleDefinitionsTestFile(subscriptionID string) {
|
||||
|
||||
roleDefinitions, err := getRoleDefinitions(subscriptionID)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(err.Error(), globals.AZ_RBAC_MODULE_NAME)
|
||||
log.Fatal(err)
|
||||
}
|
||||
roleAssignments, err := getRoleAssignments(subscriptionID)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(err.Error(), globals.AZ_RBAC_MODULE_NAME)
|
||||
log.Fatal(err)
|
||||
}
|
||||
var roleDefinitionsResults []authorization.RoleDefinition
|
||||
|
||||
@@ -324,12 +333,12 @@ func generateRoleDefinitionsTestFile(subscriptionID string) {
|
||||
|
||||
rolesjson, err := json.Marshal(tf)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(fmt.Sprintf("Could not marshal JSON for role definitions in subscription %s", subscriptionID), globals.AZ_RBAC_MODULE_NAME)
|
||||
log.Fatalf("could not marshall json for role definitions in subscription %s", subscriptionID)
|
||||
}
|
||||
|
||||
err = os.WriteFile(globals.ROLE_DEFINITIONS_TEST_FILE, rolesjson, os.ModeAppend)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(fmt.Sprintf("Could not write to role definitions test file %s", globals.ROLE_DEFINITIONS_TEST_FILE), globals.AZ_RBAC_MODULE_NAME)
|
||||
log.Fatalf("could not write to role definitions test file %s", globals.ROLE_DEFINITIONS_TEST_FILE)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,11 +367,11 @@ func mockedGetRoleAssignments(subscriptionID string) ([]authorization.RoleAssign
|
||||
var allRoleAssignments, roleAssignmentsResults []authorization.RoleAssignment
|
||||
file, err := os.ReadFile(globals.ROLE_ASSIGNMENTS_TEST_FILE)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(fmt.Sprintf("Could not read file %s", globals.ROLE_ASSIGNMENTS_TEST_FILE), globals.AZ_RBAC_MODULE_NAME)
|
||||
log.Fatalf("could not read file %s", globals.ROLE_ASSIGNMENTS_TEST_FILE)
|
||||
}
|
||||
err = json.Unmarshal(file, &allRoleAssignments)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(fmt.Sprintf("Could not unmarshal file %s", globals.ROLE_ASSIGNMENTS_TEST_FILE), globals.AZ_RBAC_MODULE_NAME)
|
||||
log.Fatalf("could not unmarshall file %s", globals.ROLE_ASSIGNMENTS_TEST_FILE)
|
||||
}
|
||||
for _, ra := range allRoleAssignments {
|
||||
roleAssignmentSubscriptionID := strings.Split(ptr.ToString(ra.Properties.RoleDefinitionID), "/")[2]
|
||||
@@ -376,15 +385,15 @@ func mockedGetRoleAssignments(subscriptionID string) ([]authorization.RoleAssign
|
||||
func generateRoleAssignmentsTestFile(subscriptionID string) {
|
||||
ra, err := getRoleAssignments(subscriptionID)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(fmt.Sprintf("Could not generate role assignments for subscription %s", subscriptionID), globals.AZ_RBAC_MODULE_NAME)
|
||||
log.Fatalf("could not generate role assignments for subscription %s", subscriptionID)
|
||||
}
|
||||
roleAssginments, err := json.Marshal(RoleAssignmentsTestFile{RoleAssignments: ra})
|
||||
if err != nil {
|
||||
azureLogger.FatalM(fmt.Sprintf("Could not marshal JSON for role assignments in subscription %s", subscriptionID), globals.AZ_RBAC_MODULE_NAME)
|
||||
log.Fatalf("could not marshall json for role assignments in subscription %s", subscriptionID)
|
||||
}
|
||||
err = os.WriteFile(globals.ROLE_ASSIGNMENTS_TEST_FILE, roleAssginments, os.ModeAppend)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(fmt.Sprintf("Could not write to role assignments test file %s", globals.ROLE_ASSIGNMENTS_TEST_FILE), globals.AZ_RBAC_MODULE_NAME)
|
||||
log.Fatalf("could not write to azure ad users test file %s", globals.ROLE_ASSIGNMENTS_TEST_FILE)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ import (
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
)
|
||||
|
||||
var azureLogger = internal.NewLogger()
|
||||
|
||||
type TenantInfo struct {
|
||||
ID *string
|
||||
DefaultDomain *string
|
||||
|
||||
+11
-4
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"github.com/fatih/color"
|
||||
"github.com/kyokomi/emoji"
|
||||
)
|
||||
|
||||
// Color functions
|
||||
@@ -45,7 +46,9 @@ func AzStorageCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirect
|
||||
|
||||
var err error
|
||||
|
||||
azureLogger.InfoM(fmt.Sprintf("Enumerating storage accounts for tenant %s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID)), globals.AZ_STORAGE_MODULE_NAME)
|
||||
fmt.Printf("[%s][%s] Enumerating storage accounts for tenant %s\n",
|
||||
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_RBAC_MODULE_NAME),
|
||||
fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID)))
|
||||
|
||||
o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain)
|
||||
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level")
|
||||
@@ -84,7 +87,7 @@ func AzStorageCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirect
|
||||
|
||||
} else {
|
||||
// Error: please make a valid flag selection
|
||||
azureLogger.ErrorM("Please enter a valid input with a valid flag. Use --help for info.", globals.AZ_STORAGE_MODULE_NAME)
|
||||
fmt.Println("Please enter a valid input with a valid flag. Use --help for info.")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -112,7 +115,11 @@ func runStorageCommandForSingleSubcription(AzSubscription string, AzOutputDirect
|
||||
o.PrefixIdentifier = AzSubscriptionInfo.Name
|
||||
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name)
|
||||
|
||||
azureLogger.InfoM(fmt.Sprintf("Enumerating storage accounts for subscription %s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID), globals.AZ_STORAGE_MODULE_NAME)
|
||||
fmt.Printf(
|
||||
"[%s][%s] Enumerating storage accounts for subscription %s\n",
|
||||
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)),
|
||||
color.CyanString(globals.AZ_STORAGE_MODULE_NAME),
|
||||
fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID))
|
||||
//AzTenantID := ptr.ToString(GetTenantIDPerSubscription(AzSubscription))
|
||||
header, body, publicBlobURLs, err = getStorageInfoPerSubscription(ptr.ToString(tenantInfo.ID), AzSubscriptionInfo.ID)
|
||||
if err != nil {
|
||||
@@ -160,7 +167,7 @@ func writeBlobURLslootFile(callingModule, controlMessagePrefix, outputDirectory
|
||||
}
|
||||
}
|
||||
|
||||
azureLogger.SuccessM(fmt.Sprintf("Loot file written to [%s]", file.Name()), callingModule)
|
||||
fmt.Printf("[%s][%s] Loot file written to [%s]\n", cyan(callingModule), cyan(controlMessagePrefix), file.Name())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
)
|
||||
|
||||
func AzTokenCacheCommand(outputDirectory, version string, wrap bool, verbosity int, listResourceGroups bool) error {
|
||||
azureLogger.InfoM("Enumerating Azure CLI sessions...", globals.AZ_TOKENCACHE_MODULE_NAME)
|
||||
header, body := getTokenCacheRelevantDataSubsOnly()
|
||||
fileName := globals.AZ_TOKENCACHE_MODULE_NAME + "-subscriptions"
|
||||
if listResourceGroups {
|
||||
header, body = getTokenCacheRelevantDataPerRG()
|
||||
fileName = globals.AZ_TOKENCACHE_MODULE_NAME + "-resource-groups"
|
||||
}
|
||||
|
||||
o := internal.OutputClient{
|
||||
Verbosity: verbosity,
|
||||
CallingModule: globals.AZ_TOKENCACHE_MODULE_NAME,
|
||||
PrefixIdentifier: "N/A",
|
||||
Table: internal.TableClient{
|
||||
Wrap: wrap,
|
||||
DirectoryName: filepath.Join(outputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, "tokencache-data"),
|
||||
},
|
||||
}
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: header,
|
||||
Body: body,
|
||||
Name: fmt.Sprintf("%s-%s", fileName, strconv.FormatInt(time.Now().Unix(), 10)),
|
||||
})
|
||||
o.WriteFullOutput(o.Table.TableFiles, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTokenCacheRelevantDataPerRG() ([]string, [][]string) {
|
||||
header := []string{"Tenant ID", "Tenant Primary Domain", "Subscription ID", "Subscription Name", "RG Name", "Region"}
|
||||
var body [][]string
|
||||
for _, tenant := range getTenants() {
|
||||
for _, subscription := range GetSubscriptions() {
|
||||
if ptr.ToString(tenant.TenantID) != ptr.ToString(subscription.TenantID) {
|
||||
continue
|
||||
}
|
||||
for _, resourceGroup := range getResourceGroups(ptr.ToString(subscription.SubscriptionID)) {
|
||||
body = append(body, []string{ptr.ToString(subscription.TenantID), ptr.ToString(tenant.DefaultDomain), ptr.ToString(subscription.SubscriptionID), ptr.ToString(subscription.DisplayName), ptr.ToString(resourceGroup.Name), ptr.ToString(resourceGroup.Location)})
|
||||
}
|
||||
}
|
||||
}
|
||||
return header, body
|
||||
}
|
||||
|
||||
func getTokenCacheRelevantDataSubsOnly() ([]string, [][]string) {
|
||||
header := []string{"Tenant ID", "Tenant Primary Domain", "Subscription ID", "Subscription Name"}
|
||||
var body [][]string
|
||||
for _, tenant := range getTenants() {
|
||||
for _, subscription := range GetSubscriptions() {
|
||||
if ptr.ToString(tenant.TenantID) == ptr.ToString(subscription.TenantID) {
|
||||
body = append(body, []string{ptr.ToString(subscription.TenantID), ptr.ToString(tenant.DefaultDomain), ptr.ToString(subscription.SubscriptionID), ptr.ToString(subscription.DisplayName)})
|
||||
}
|
||||
}
|
||||
}
|
||||
return header, body
|
||||
}
|
||||
+13
-7
@@ -16,6 +16,8 @@ import (
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"github.com/fatih/color"
|
||||
"github.com/kyokomi/emoji"
|
||||
)
|
||||
|
||||
func AzVMsCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, Version string, AzVerbosity int, AzWrapTable bool, AzMergedTable bool) error {
|
||||
@@ -37,7 +39,9 @@ func AzVMsCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory,
|
||||
Wrap: AzWrapTable,
|
||||
},
|
||||
}
|
||||
azureLogger.InfoM(fmt.Sprintf("Enumerating VMs for tenant %s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID)), globals.AZ_VMS_MODULE_NAME)
|
||||
fmt.Printf("[%s][%s] Enumerating VMs for tenant %s\n",
|
||||
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_VMS_MODULE_NAME),
|
||||
fmt.Sprintf("%s (%s)", ptr.ToString(tenantInfo.DefaultDomain), ptr.ToString(tenantInfo.ID)))
|
||||
|
||||
o.PrefixIdentifier = ptr.ToString(tenantInfo.DefaultDomain)
|
||||
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), "1-tenant-level")
|
||||
@@ -80,7 +84,7 @@ func AzVMsCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory,
|
||||
|
||||
} else {
|
||||
// Error: please make a valid flag selection
|
||||
azureLogger.ErrorM("Please enter a valid input with a valid flag. Use --help for info.", globals.AZ_VMS_MODULE_NAME)
|
||||
fmt.Println("Please enter a valid input with a valid flag. Use --help for info.")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -106,7 +110,9 @@ func runVMsCommandForSingleSubscription(AzSubscription string, AzOutputDirectory
|
||||
o.PrefixIdentifier = AzSubscriptionInfo.Name
|
||||
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, ptr.ToString(tenantInfo.DefaultDomain), AzSubscriptionInfo.Name)
|
||||
|
||||
azureLogger.InfoM(fmt.Sprintf("Enumerating VMs for subscription %s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID), globals.AZ_VMS_MODULE_NAME)
|
||||
fmt.Printf("[%s][%s] Enumerating VMs for subscription %s\n",
|
||||
color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", Version)), color.CyanString(globals.AZ_VMS_MODULE_NAME),
|
||||
fmt.Sprintf("%s (%s)", AzSubscriptionInfo.Name, AzSubscriptionInfo.ID))
|
||||
|
||||
// populate the table data
|
||||
header, body, userData = getVMsPerSubscriptionID(AzSubscriptionInfo.ID)
|
||||
@@ -147,7 +153,7 @@ func getVMsPerTenantID(AzTenantID string) ([]string, [][]string, string) {
|
||||
for _, rg := range getResourceGroups(ptr.ToString(s.SubscriptionID)) {
|
||||
resultsHeader, b, userData, err = getComputeRelevantData(s, rg)
|
||||
if err != nil {
|
||||
azureLogger.WarnM(fmt.Sprintf("Could not enumerate VMs for resource group %s in subscription %s: %s", ptr.ToString(rg.Name), ptr.ToString(s.SubscriptionID), err), globals.AZ_VMS_MODULE_NAME)
|
||||
fmt.Printf("[%s] Could not enumerate VMs for resource group %s in subscription %s\n", color.CyanString(globals.AZ_VMS_MODULE_NAME), ptr.ToString(rg.Name), ptr.ToString(s.SubscriptionID))
|
||||
} else {
|
||||
resultsBody = append(resultsBody, b...)
|
||||
userDataCombined += userData
|
||||
@@ -169,7 +175,7 @@ func getVMsPerSubscriptionID(AzSubscriptionID string) ([]string, [][]string, str
|
||||
for _, rg := range getResourceGroups(ptr.ToString(s.SubscriptionID)) {
|
||||
resultsHeader, b, userData, err = getComputeRelevantData(s, rg)
|
||||
if err != nil {
|
||||
azureLogger.WarnM(fmt.Sprintf("Could not enumerate VMs for resource group %s in subscription %s: %s", ptr.ToString(rg.Name), ptr.ToString(s.SubscriptionID), err), globals.AZ_VMS_MODULE_NAME)
|
||||
fmt.Printf("[%s] Could not enumerate VMs for resource group %s in subscription %s\n", color.CyanString(globals.AZ_VMS_MODULE_NAME), ptr.ToString(rg.Name), ptr.ToString(s.SubscriptionID))
|
||||
} else {
|
||||
resultsBody = append(resultsBody, b...)
|
||||
userDataCombined += userData
|
||||
@@ -203,13 +209,13 @@ func getComputeRelevantData(sub subscriptions.Subscription, rg resources.Group)
|
||||
// get userdata
|
||||
vmDetails, err := getComputeVmInfo(subscriptionID, resourceGroupName, ptr.ToString(vm.Name))
|
||||
if err != nil {
|
||||
azureLogger.WarnM(fmt.Sprintf("Error fetching VM details for %s: %s", ptr.ToString(vm.Name), err), globals.AZ_VMS_MODULE_NAME)
|
||||
fmt.Println("error fetching vm details for vm: ", ptr.ToString(vm.Name))
|
||||
}
|
||||
|
||||
if vmDetails.VirtualMachineProperties != nil && vmDetails.VirtualMachineProperties.UserData != nil {
|
||||
userData, err := base64.StdEncoding.DecodeString(ptr.ToString(vmDetails.VirtualMachineProperties.UserData))
|
||||
if err != nil {
|
||||
azureLogger.WarnM(fmt.Sprintf("Error decoding user data for VM %s: %s", ptr.ToString(vm.Name), err), globals.AZ_VMS_MODULE_NAME)
|
||||
fmt.Println("error decoding userdata for vm: ", ptr.ToString(vm.Name))
|
||||
}
|
||||
//append userdata from this vm to the string with headers and newlines for VM name, location, and resource group name
|
||||
userDataString += fmt.Sprintf(
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ func TestAzVMsCommand(t *testing.T) {
|
||||
|
||||
err := AzVMsCommand(s.azTenantID, s.azSubscriptionID, s.azOutputFormat, s.azOutputDirectory, s.version, 2, s.wrapTableOutput, s.azMergedTable)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
+70
-335
@@ -2,66 +2,56 @@ package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/authorization/mgmt/authorization"
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/resources"
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
"github.com/fatih/color"
|
||||
"github.com/kyokomi/emoji"
|
||||
)
|
||||
|
||||
func AzWhoamiCommand(AzOutputDirectory, version string, AzWrapTable bool, AzVerbosity int) error {
|
||||
func AzWhoamiCommand(AzOutputDirectory, version string, AzWrapTable bool, AzVerbosity int, AzWhoamiListRGsAlso bool) error {
|
||||
o := internal.OutputClient{
|
||||
Verbosity: AzVerbosity,
|
||||
CallingModule: globals.AZ_WHOAMI_MODULE_NAME,
|
||||
Table: internal.TableClient{
|
||||
Wrap: AzWrapTable,
|
||||
DirectoryName: filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, "whoami-data"),
|
||||
Wrap: AzWrapTable,
|
||||
},
|
||||
}
|
||||
|
||||
fileTimestamp := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
fmt.Printf("[%s][%s] Enumerating Azure CLI sessions...\n", color.CyanString(emoji.Sprintf(":fox:cloudfox %s :fox:", version)), color.CyanString(globals.AZ_WHOAMI_MODULE_NAME))
|
||||
var header []string
|
||||
var body [][]string
|
||||
o.PrefixIdentifier = "N/A"
|
||||
azureLogger.InfoM("Enumerating caller's Entra ID roles", globals.AZ_WHOAMI_MODULE_NAME)
|
||||
roleHeader, roleBody, roleErrors := getWhoamiEntraRoles()
|
||||
for _, err := range roleErrors {
|
||||
azureLogger.WarnM(fmt.Sprintf("Could not enumerate Entra ID roles: %s", err), globals.AZ_WHOAMI_MODULE_NAME)
|
||||
}
|
||||
if len(roleBody) > 0 {
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: roleHeader,
|
||||
Body: roleBody,
|
||||
Name: globals.AZ_WHOAMI_MODULE_NAME + "-entra-memberships-" + fileTimestamp,
|
||||
})
|
||||
}
|
||||
if !AzWhoamiListRGsAlso {
|
||||
// cloudfox azure whoami
|
||||
header, body = getWhoamiRelevantDataSubsOnly()
|
||||
o.Table.DirectoryName = filepath.Join(AzOutputDirectory, globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, "whoami-data")
|
||||
// append timestamp to filename (time from epoch)
|
||||
o.Table.TableFiles = append(o.Table.TableFiles,
|
||||
internal.TableFile{
|
||||
Header: header,
|
||||
Body: body,
|
||||
Name: fmt.Sprintf(globals.AZ_WHOAMI_MODULE_NAME+"-subs-only") + "-" + strconv.FormatInt((time.Now().Unix()), 10)})
|
||||
|
||||
azureLogger.InfoM("Enumerating caller's Azure RBAC roles", globals.AZ_WHOAMI_MODULE_NAME)
|
||||
rbacHeader, rbacBody, rbacErrors := getWhoamiAzureRBAC()
|
||||
for _, err := range rbacErrors {
|
||||
azureLogger.WarnM(fmt.Sprintf("Could not enumerate Azure RBAC roles: %s", err), globals.AZ_WHOAMI_MODULE_NAME)
|
||||
}
|
||||
if len(rbacBody) > 0 {
|
||||
o.Table.TableFiles = append(o.Table.TableFiles, internal.TableFile{
|
||||
Header: rbacHeader,
|
||||
Body: rbacBody,
|
||||
Name: globals.AZ_WHOAMI_MODULE_NAME + "-azure-rbac-" + fileTimestamp,
|
||||
})
|
||||
} else {
|
||||
// cloudfox azure whoami --list-rgs
|
||||
header, body = getWhoamiRelevantDataPerRG()
|
||||
o.Table.DirectoryName = filepath.Join(ptr.ToString(internal.GetLogDirPath()), globals.AZ_DIR_BASE, "whoami-data")
|
||||
o.Table.TableFiles = append(o.Table.TableFiles,
|
||||
internal.TableFile{
|
||||
Header: header,
|
||||
Body: body,
|
||||
Name: globals.AZ_WHOAMI_MODULE_NAME + "-" + strconv.FormatInt((time.Now().Unix()), 10)})
|
||||
}
|
||||
//internal.PrintTableToScreen(header, body, AzWrapTable)
|
||||
|
||||
@@ -70,307 +60,52 @@ func AzWhoamiCommand(AzOutputDirectory, version string, AzWrapTable bool, AzVerb
|
||||
return nil
|
||||
}
|
||||
|
||||
const microsoftGraphScope = "https://graph.microsoft.com/.default"
|
||||
func getWhoamiRelevantDataPerRG() ([]string, [][]string) {
|
||||
tableHead := []string{"Tenant ID", "Tentant Primary Domain", "Subscription ID", "Subscription Name", "RG Name", "Region"}
|
||||
var tableBody [][]string
|
||||
|
||||
var (
|
||||
graphBaseURL = "https://graph.microsoft.com/v1.0"
|
||||
graphHTTPClient = http.DefaultClient
|
||||
getWhoamiEntraRoles = getWhoamiEntraRolesOriginal
|
||||
getWhoamiAzureRBAC = getWhoamiAzureRBACOriginal
|
||||
)
|
||||
|
||||
type entraRole struct {
|
||||
ODataType string `json:"@odata.type"`
|
||||
ID string `json:"id"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type entraRolePage struct {
|
||||
Value []entraRole `json:"value"`
|
||||
NextLink string `json:"@odata.nextLink"`
|
||||
}
|
||||
|
||||
type graphErrorResponse struct {
|
||||
Error struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
type tokenClaims struct {
|
||||
ObjectID string `json:"oid"`
|
||||
Name string `json:"name"`
|
||||
PreferredUsername string `json:"preferred_username"`
|
||||
AppID string `json:"appid"`
|
||||
IdentityType string `json:"idtyp"`
|
||||
}
|
||||
|
||||
func getWhoamiEntraRolesOriginal() ([]string, [][]string, []error) {
|
||||
header := []string{"Tenant ID", "Tenant Primary Domain", "Principal ID", "Principal Name", "Principal Type", "Membership Type", "Membership Name", "Description"}
|
||||
var body [][]string
|
||||
var errs []error
|
||||
|
||||
for _, tenant := range getTenants() {
|
||||
tenantID := ptr.ToString(tenant.TenantID)
|
||||
cred, err := azidentity.NewAzureCLICredential(&azidentity.AzureCLICredentialOptions{TenantID: tenantID})
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("tenant %s: create Azure CLI credential: %w", tenantID, err))
|
||||
continue
|
||||
}
|
||||
|
||||
claims, memberships, err := getCallerDirectoryMemberships(context.Background(), cred)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("tenant %s: %w", tenantID, err))
|
||||
continue
|
||||
}
|
||||
|
||||
principalName := claims.PreferredUsername
|
||||
if principalName == "" {
|
||||
principalName = claims.Name
|
||||
}
|
||||
principalType := "User"
|
||||
if claims.IdentityType == "app" || (claims.PreferredUsername == "" && claims.AppID != "") {
|
||||
principalType = "Service Principal"
|
||||
}
|
||||
foundRole := false
|
||||
for _, membership := range memberships {
|
||||
membershipType := directoryMembershipType(membership.ODataType)
|
||||
if membershipType == "" {
|
||||
continue
|
||||
}
|
||||
membershipName := membership.DisplayName
|
||||
if membershipName == "" {
|
||||
membershipName = membership.ID
|
||||
}
|
||||
body = append(body, []string{tenantID, ptr.ToString(tenant.DefaultDomain), claims.ObjectID, principalName, principalType, membershipType, membershipName, membership.Description})
|
||||
foundRole = foundRole || membershipType == "Directory Role"
|
||||
}
|
||||
if !foundRole {
|
||||
body = append(body, []string{tenantID, ptr.ToString(tenant.DefaultDomain), claims.ObjectID, principalName, principalType, "Directory Role", "(none)", "No active Entra ID directory roles found"})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(body, func(i, j int) bool {
|
||||
if body[i][0] == body[j][0] {
|
||||
if body[i][5] == body[j][5] {
|
||||
return body[i][6] < body[j][6]
|
||||
}
|
||||
return body[i][5] < body[j][5]
|
||||
}
|
||||
return body[i][0] < body[j][0]
|
||||
})
|
||||
return header, body, errs
|
||||
}
|
||||
|
||||
func getCallerEntraRoles(ctx context.Context, cred azcore.TokenCredential) (tokenClaims, []entraRole, error) {
|
||||
claims, memberships, err := getCallerDirectoryMemberships(ctx, cred)
|
||||
if err != nil {
|
||||
return tokenClaims{}, nil, err
|
||||
}
|
||||
var roles []entraRole
|
||||
for _, object := range memberships {
|
||||
if directoryMembershipType(object.ODataType) == "Directory Role" {
|
||||
roles = append(roles, object)
|
||||
}
|
||||
}
|
||||
return claims, roles, nil
|
||||
}
|
||||
|
||||
func directoryMembershipType(odataType string) string {
|
||||
switch {
|
||||
case strings.EqualFold(odataType, "#microsoft.graph.directoryRole"), strings.EqualFold(odataType, "#Microsoft.DirectoryServices.DirectoryRole"):
|
||||
return "Directory Role"
|
||||
case strings.EqualFold(odataType, "#microsoft.graph.group"), strings.EqualFold(odataType, "#Microsoft.DirectoryServices.Group"):
|
||||
return "Group"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func getCallerDirectoryMemberships(ctx context.Context, cred azcore.TokenCredential) (tokenClaims, []entraRole, error) {
|
||||
token, err := cred.GetToken(ctx, policy.TokenRequestOptions{Scopes: []string{microsoftGraphScope}})
|
||||
if err != nil {
|
||||
return tokenClaims{}, nil, fmt.Errorf("get Microsoft Graph access token: %w", err)
|
||||
}
|
||||
|
||||
claims, err := parseTokenClaims(token.Token)
|
||||
if err != nil {
|
||||
return tokenClaims{}, nil, err
|
||||
}
|
||||
|
||||
nextLink := fmt.Sprintf("%s/directoryObjects/%s/transitiveMemberOf", strings.TrimRight(graphBaseURL, "/"), url.PathEscape(claims.ObjectID))
|
||||
var memberships []entraRole
|
||||
for nextLink != "" {
|
||||
page, err := getEntraRolePage(ctx, token.Token, nextLink)
|
||||
if err != nil {
|
||||
return tokenClaims{}, nil, err
|
||||
}
|
||||
memberships = append(memberships, page.Value...)
|
||||
nextLink = page.NextLink
|
||||
}
|
||||
return claims, memberships, nil
|
||||
}
|
||||
|
||||
type rbacPrincipal struct {
|
||||
ID string
|
||||
Name string
|
||||
AssignmentType string
|
||||
}
|
||||
|
||||
func getWhoamiAzureRBACOriginal() ([]string, [][]string, []error) {
|
||||
header := []string{"Tenant ID", "Subscription ID", "Subscription Name", "Principal ID", "Principal Name", "Assignment Via", "Azure RBAC Role", "Scope"}
|
||||
var body [][]string
|
||||
var errs []error
|
||||
|
||||
for _, tenant := range getTenants() {
|
||||
tenantID := ptr.ToString(tenant.TenantID)
|
||||
cred, err := azidentity.NewAzureCLICredential(&azidentity.AzureCLICredentialOptions{TenantID: tenantID})
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("tenant %s: create Azure CLI credential: %w", tenantID, err))
|
||||
continue
|
||||
}
|
||||
claims, memberships, err := getCallerDirectoryMemberships(context.Background(), cred)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("tenant %s: get caller's groups: %w", tenantID, err))
|
||||
continue
|
||||
}
|
||||
|
||||
callerName := claims.PreferredUsername
|
||||
if callerName == "" {
|
||||
callerName = claims.Name
|
||||
}
|
||||
principals := map[string]rbacPrincipal{
|
||||
strings.ToLower(claims.ObjectID): {ID: claims.ObjectID, Name: callerName, AssignmentType: "Direct"},
|
||||
}
|
||||
for _, object := range memberships {
|
||||
if directoryMembershipType(object.ODataType) != "Group" {
|
||||
continue
|
||||
}
|
||||
groupName := object.DisplayName
|
||||
if groupName == "" {
|
||||
groupName = object.ID
|
||||
}
|
||||
principals[strings.ToLower(object.ID)] = rbacPrincipal{ID: object.ID, Name: groupName, AssignmentType: "Group"}
|
||||
}
|
||||
|
||||
foundAssignment := false
|
||||
for _, subscription := range GetSubscriptionsPerTenantID(tenantID) {
|
||||
subscriptionID := ptr.ToString(subscription.SubscriptionID)
|
||||
assignments, err := getRoleAssignments(subscriptionID)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("subscription %s: %w", subscriptionID, err))
|
||||
continue
|
||||
}
|
||||
definitions, err := getRoleDefinitions(subscriptionID)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("subscription %s: %w", subscriptionID, err))
|
||||
}
|
||||
roleNames := roleDefinitionNames(definitions)
|
||||
rows := azureRBACRowsForSubscription(tenantID, subscriptionID, ptr.ToString(subscription.DisplayName), principals, roleNames, assignments)
|
||||
body = append(body, rows...)
|
||||
foundAssignment = foundAssignment || len(rows) > 0
|
||||
}
|
||||
if !foundAssignment {
|
||||
body = append(body, []string{tenantID, "", "", claims.ObjectID, callerName, "Direct or Group", "(none)", "No Azure RBAC assignments found in accessible subscriptions"})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(body, func(i, j int) bool {
|
||||
for _, column := range []int{0, 1, 6, 5, 4} {
|
||||
if body[i][column] != body[j][column] {
|
||||
return body[i][column] < body[j][column]
|
||||
for _, t := range getTenants() {
|
||||
for _, s := range GetSubscriptions() {
|
||||
if ptr.ToString(t.TenantID) == ptr.ToString(s.TenantID) {
|
||||
for _, rg := range getResourceGroups(ptr.ToString(s.SubscriptionID)) {
|
||||
tableBody = append(
|
||||
tableBody,
|
||||
[]string{
|
||||
ptr.ToString(s.TenantID),
|
||||
ptr.ToString(t.DefaultDomain),
|
||||
ptr.ToString(s.SubscriptionID),
|
||||
ptr.ToString(s.DisplayName),
|
||||
ptr.ToString(rg.Name),
|
||||
ptr.ToString(rg.Location),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return body[i][7] < body[j][7]
|
||||
})
|
||||
return header, body, errs
|
||||
}
|
||||
|
||||
return tableHead, tableBody
|
||||
}
|
||||
|
||||
func azureRBACRowsForSubscription(tenantID, subscriptionID, subscriptionName string, principals map[string]rbacPrincipal, roleNames map[string]string, assignments []authorization.RoleAssignment) [][]string {
|
||||
var rows [][]string
|
||||
for _, assignment := range assignments {
|
||||
principal, ok := principals[strings.ToLower(ptr.ToString(assignment.Properties.PrincipalID))]
|
||||
if !ok {
|
||||
continue
|
||||
func getWhoamiRelevantDataSubsOnly() ([]string, [][]string) {
|
||||
tableHead := []string{"Tenant ID", "Tenant Primary Domain", "Subscription ID", "Subscription Name"}
|
||||
var tableBody [][]string
|
||||
|
||||
for _, t := range getTenants() {
|
||||
for _, s := range GetSubscriptions() {
|
||||
if ptr.ToString(t.TenantID) == ptr.ToString(s.TenantID) {
|
||||
tableBody = append(
|
||||
tableBody,
|
||||
[]string{
|
||||
ptr.ToString(s.TenantID),
|
||||
ptr.ToString(t.DefaultDomain),
|
||||
ptr.ToString(s.SubscriptionID),
|
||||
ptr.ToString(s.DisplayName),
|
||||
})
|
||||
}
|
||||
}
|
||||
roleDefinitionID := lastResourceIDSegment(ptr.ToString(assignment.Properties.RoleDefinitionID))
|
||||
roleName := roleNames[strings.ToLower(roleDefinitionID)]
|
||||
if roleName == "" {
|
||||
roleName = roleDefinitionID
|
||||
}
|
||||
rows = append(rows, []string{tenantID, subscriptionID, subscriptionName, principal.ID, principal.Name, principal.AssignmentType, roleName, ptr.ToString(assignment.Properties.Scope)})
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func roleDefinitionNames(definitions []authorization.RoleDefinition) map[string]string {
|
||||
names := make(map[string]string, len(definitions))
|
||||
for _, definition := range definitions {
|
||||
names[strings.ToLower(lastResourceIDSegment(ptr.ToString(definition.ID)))] = ptr.ToString(definition.RoleName)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func lastResourceIDSegment(id string) string {
|
||||
id = strings.TrimRight(id, "/")
|
||||
if index := strings.LastIndex(id, "/"); index >= 0 {
|
||||
return id[index+1:]
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func parseTokenClaims(token string) (tokenClaims, error) {
|
||||
parts := strings.Split(token, ".")
|
||||
if len(parts) != 3 {
|
||||
return tokenClaims{}, fmt.Errorf("Microsoft Graph returned a malformed access token")
|
||||
}
|
||||
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
|
||||
if err != nil {
|
||||
return tokenClaims{}, fmt.Errorf("decode Microsoft Graph access token claims: %w", err)
|
||||
}
|
||||
var claims tokenClaims
|
||||
if err := json.Unmarshal(payload, &claims); err != nil {
|
||||
return tokenClaims{}, fmt.Errorf("decode Microsoft Graph access token claims: %w", err)
|
||||
}
|
||||
if claims.ObjectID == "" {
|
||||
return tokenClaims{}, fmt.Errorf("Microsoft Graph access token has no oid claim")
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func getEntraRolePage(ctx context.Context, accessToken, endpoint string) (entraRolePage, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||
if err != nil {
|
||||
return entraRolePage{}, fmt.Errorf("create Microsoft Graph request: %w", err)
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("ConsistencyLevel", "eventual")
|
||||
req.Header.Set("User-Agent", globals.CLOUDFOX_USER_AGENT)
|
||||
|
||||
resp, err := graphHTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return entraRolePage{}, fmt.Errorf("query Microsoft Graph: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
responseBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return entraRolePage{}, fmt.Errorf("read Microsoft Graph response: %w", err)
|
||||
}
|
||||
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||
var graphErr graphErrorResponse
|
||||
if json.Unmarshal(responseBody, &graphErr) == nil && graphErr.Error.Message != "" {
|
||||
return entraRolePage{}, fmt.Errorf("Microsoft Graph returned %s (%s): %s", resp.Status, graphErr.Error.Code, graphErr.Error.Message)
|
||||
}
|
||||
return entraRolePage{}, fmt.Errorf("Microsoft Graph returned %s", resp.Status)
|
||||
}
|
||||
|
||||
var page entraRolePage
|
||||
if err := json.Unmarshal(responseBody, &page); err != nil {
|
||||
return entraRolePage{}, fmt.Errorf("decode Microsoft Graph response: %w", err)
|
||||
}
|
||||
return page, nil
|
||||
return tableHead, tableBody
|
||||
}
|
||||
|
||||
var getTenants = getTenantsOriginal
|
||||
@@ -380,7 +115,7 @@ func getTenantsOriginal() []subscriptions.TenantIDDescription {
|
||||
var results []subscriptions.TenantIDDescription
|
||||
for page, err := tenantsClient.List(context.TODO()); page.NotDone(); err = page.Next() {
|
||||
if err != nil {
|
||||
azureLogger.FatalM("Could not get tenants for active session", globals.AZ_WHOAMI_MODULE_NAME)
|
||||
log.Fatal("could not get tenants for active session")
|
||||
}
|
||||
results = append(results, page.Values()...)
|
||||
}
|
||||
@@ -406,7 +141,7 @@ func getSubscriptionsOriginal() []subscriptions.Subscription {
|
||||
subsClient := internal.GetSubscriptionsClient()
|
||||
for page, err := subsClient.List(context.TODO()); page.NotDone(); err = page.Next() {
|
||||
if err != nil {
|
||||
azureLogger.FatalM("Could not get subscriptions for active session", globals.AZ_WHOAMI_MODULE_NAME)
|
||||
log.Fatal("could not get subscriptions for active session")
|
||||
}
|
||||
results = append(results, page.Values()...)
|
||||
}
|
||||
@@ -436,7 +171,7 @@ func getResourceGroupsOriginal(subscriptionID string) []resources.Group {
|
||||
|
||||
for page, err := rgClient.List(context.TODO(), "", nil); page.NotDone(); err = page.Next() {
|
||||
if err != nil {
|
||||
azureLogger.FatalM(fmt.Sprintf("Error reading resource groups for subscription %s", subscriptionID), globals.AZ_WHOAMI_MODULE_NAME)
|
||||
log.Fatalf("error reading resource groups for subscription %s", subscriptionID)
|
||||
}
|
||||
results = append(results, page.Values()...)
|
||||
}
|
||||
@@ -464,12 +199,12 @@ func mockedGetResourceGroups(subscriptionID string) []resources.Group {
|
||||
func loadTestFile(fileName string) ResourcesTestFile {
|
||||
file, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(fmt.Sprintf("Could not read file %s", globals.RESOURCES_TEST_FILE), globals.AZ_WHOAMI_MODULE_NAME)
|
||||
log.Fatalf("could not read file %s", globals.RESOURCES_TEST_FILE)
|
||||
}
|
||||
var testFile ResourcesTestFile
|
||||
err = json.Unmarshal(file, &testFile)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(fmt.Sprintf("Could not unmarshal file %s", globals.RESOURCES_TEST_FILE), globals.AZ_WHOAMI_MODULE_NAME)
|
||||
log.Fatalf("could not unmarshall file %s", globals.RESOURCES_TEST_FILE)
|
||||
}
|
||||
return testFile
|
||||
}
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/authorization/mgmt/authorization"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
|
||||
)
|
||||
|
||||
type staticTokenCredential struct {
|
||||
token string
|
||||
err error
|
||||
scope string
|
||||
}
|
||||
|
||||
func TestAzureRBACRowsIncludeDirectAndGroupAssignments(t *testing.T) {
|
||||
var assignments []authorization.RoleAssignment
|
||||
err := json.Unmarshal([]byte(`[
|
||||
{"properties":{"principalId":"caller-id","roleDefinitionId":"/providers/Microsoft.Authorization/roleDefinitions/reader-id","scope":"/subscriptions/sub-id"}},
|
||||
{"properties":{"principalId":"group-id","roleDefinitionId":"/providers/Microsoft.Authorization/roleDefinitions/owner-id","scope":"/subscriptions/sub-id/resourceGroups/example"}},
|
||||
{"properties":{"principalId":"other-id","roleDefinitionId":"/providers/Microsoft.Authorization/roleDefinitions/ignored-id","scope":"/subscriptions/sub-id"}}
|
||||
]`), &assignments)
|
||||
if err != nil {
|
||||
t.Fatalf("unmarshal role assignments: %v", err)
|
||||
}
|
||||
|
||||
principals := map[string]rbacPrincipal{
|
||||
"caller-id": {ID: "caller-id", Name: "[email protected]", AssignmentType: "Direct"},
|
||||
"group-id": {ID: "group-id", Name: "Cloud Operators", AssignmentType: "Group"},
|
||||
}
|
||||
roleNames := map[string]string{"reader-id": "Reader", "owner-id": "Owner"}
|
||||
rows := azureRBACRowsForSubscription("tenant-id", "sub-id", "Production", principals, roleNames, assignments)
|
||||
|
||||
if len(rows) != 2 {
|
||||
t.Fatalf("got %d relevant role assignments, want 2: %+v", len(rows), rows)
|
||||
}
|
||||
if rows[0][5] != "Direct" || rows[0][6] != "Reader" {
|
||||
t.Fatalf("unexpected direct assignment row: %+v", rows[0])
|
||||
}
|
||||
if rows[1][4] != "Cloud Operators" || rows[1][5] != "Group" || rows[1][6] != "Owner" {
|
||||
t.Fatalf("unexpected group assignment row: %+v", rows[1])
|
||||
}
|
||||
}
|
||||
|
||||
func (c *staticTokenCredential) GetToken(_ context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error) {
|
||||
if len(options.Scopes) > 0 {
|
||||
c.scope = options.Scopes[0]
|
||||
}
|
||||
return azcore.AccessToken{Token: c.token, ExpiresOn: time.Now().Add(time.Hour)}, c.err
|
||||
}
|
||||
|
||||
func unsignedTestToken(payload string) string {
|
||||
return "header." + base64.RawURLEncoding.EncodeToString([]byte(payload)) + ".signature"
|
||||
}
|
||||
|
||||
func TestGetCallerEntraRoles(t *testing.T) {
|
||||
var authorization string
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
authorization = r.Header.Get("Authorization")
|
||||
if r.URL.Path != "/directoryObjects/caller-object-id/transitiveMemberOf" {
|
||||
t.Fatalf("unexpected Graph path: %s", r.URL.Path)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"value":[{"@odata.type":"#Microsoft.DirectoryServices.Group","id":"group-id","displayName":"Readers"},{"@odata.type":"#Microsoft.DirectoryServices.DirectoryRole","id":"role-id","displayName":"Global Reader","description":"Read everything"}]}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
originalBaseURL, originalHTTPClient := graphBaseURL, graphHTTPClient
|
||||
graphBaseURL, graphHTTPClient = server.URL, server.Client()
|
||||
t.Cleanup(func() {
|
||||
graphBaseURL, graphHTTPClient = originalBaseURL, originalHTTPClient
|
||||
})
|
||||
|
||||
cred := &staticTokenCredential{token: unsignedTestToken(`{"oid":"caller-object-id","preferred_username":"[email protected]"}`)}
|
||||
claims, roles, err := getCallerEntraRoles(context.Background(), cred)
|
||||
if err != nil {
|
||||
t.Fatalf("getCallerEntraRoles returned an error: %v", err)
|
||||
}
|
||||
if cred.scope != microsoftGraphScope {
|
||||
t.Fatalf("requested scope %q, want %q", cred.scope, microsoftGraphScope)
|
||||
}
|
||||
if authorization != "Bearer "+cred.token {
|
||||
t.Fatalf("authorization header %q does not contain the Graph token", authorization)
|
||||
}
|
||||
if claims.ObjectID != "caller-object-id" || len(roles) != 1 || roles[0].DisplayName != "Global Reader" {
|
||||
t.Fatalf("unexpected claims or roles: claims=%+v roles=%+v", claims, roles)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirectoryMembershipTypeSupportsGraphAndLegacyNames(t *testing.T) {
|
||||
tests := map[string]string{
|
||||
"#microsoft.graph.directoryRole": "Directory Role",
|
||||
"#Microsoft.DirectoryServices.DirectoryRole": "Directory Role",
|
||||
"#microsoft.graph.group": "Group",
|
||||
"#Microsoft.DirectoryServices.Group": "Group",
|
||||
"#Microsoft.DirectoryServices.AdministrativeUnit": "",
|
||||
}
|
||||
for odataType, want := range tests {
|
||||
if got := directoryMembershipType(odataType); got != want {
|
||||
t.Errorf("directoryMembershipType(%q) = %q, want %q", odataType, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCallerEntraRolesGraphError(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
_, _ = w.Write([]byte(`{"error":{"code":"Authorization_RequestDenied","message":"Insufficient privileges"}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
originalBaseURL, originalHTTPClient := graphBaseURL, graphHTTPClient
|
||||
graphBaseURL, graphHTTPClient = server.URL, server.Client()
|
||||
t.Cleanup(func() {
|
||||
graphBaseURL, graphHTTPClient = originalBaseURL, originalHTTPClient
|
||||
})
|
||||
|
||||
cred := &staticTokenCredential{token: unsignedTestToken(`{"oid":"caller-object-id"}`)}
|
||||
_, _, err := getCallerEntraRoles(context.Background(), cred)
|
||||
if err == nil || !strings.Contains(err.Error(), "Insufficient privileges") {
|
||||
t.Fatalf("expected a descriptive Graph permission error, got %v", err)
|
||||
}
|
||||
}
|
||||
+5
-29
@@ -9,19 +9,14 @@ import (
|
||||
"github.com/aws/smithy-go/ptr"
|
||||
)
|
||||
|
||||
func TestAzTokenCacheCommand(t *testing.T) {
|
||||
func TestAzWhoamiCommand(t *testing.T) {
|
||||
fmt.Println()
|
||||
fmt.Println("[test case] Azure Token Cache Command")
|
||||
fmt.Println("[test case] Azure Whoami Command")
|
||||
|
||||
// Mocked functions to simulate Azure calls and responses
|
||||
getTenants = mockedGetTenants
|
||||
GetSubscriptions = mockedGetSubscriptions
|
||||
getResourceGroups = mockedGetResourceGroups
|
||||
t.Cleanup(func() {
|
||||
getTenants = getTenantsOriginal
|
||||
GetSubscriptions = getSubscriptionsOriginal
|
||||
getResourceGroups = getResourceGroupsOriginal
|
||||
})
|
||||
|
||||
// Test case parameters
|
||||
internal.MockFileSystem(true)
|
||||
@@ -34,7 +29,7 @@ func TestAzTokenCacheCommand(t *testing.T) {
|
||||
azOutputDirectory string
|
||||
}{
|
||||
{
|
||||
name: "./cloudfox azure tokencache",
|
||||
name: "./cloudfox azure whoami",
|
||||
resourcesTestFile: "./test-data/resources.json",
|
||||
azExtendedFilter: false,
|
||||
version: "DEV",
|
||||
@@ -42,7 +37,7 @@ func TestAzTokenCacheCommand(t *testing.T) {
|
||||
azOutputDirectory: "~/.cloudfox",
|
||||
},
|
||||
{
|
||||
name: "./cloudfox azure tokencache --list-rgs",
|
||||
name: "./cloudfox azure whoami --extended",
|
||||
resourcesTestFile: "./test-data/resources.json",
|
||||
azExtendedFilter: true,
|
||||
version: "DEV",
|
||||
@@ -54,26 +49,7 @@ func TestAzTokenCacheCommand(t *testing.T) {
|
||||
globals.RESOURCES_TEST_FILE = s.resourcesTestFile
|
||||
fmt.Println()
|
||||
fmt.Printf("[subtest] %s\n", s.name)
|
||||
if err := AzTokenCacheCommand(s.azOutputDirectory, s.version, s.wrapTableOutput, 1, s.azExtendedFilter); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAzWhoamiCommand(t *testing.T) {
|
||||
originalEntra, originalRBAC := getWhoamiEntraRoles, getWhoamiAzureRBAC
|
||||
getWhoamiEntraRoles = func() ([]string, [][]string, []error) {
|
||||
return []string{"Tenant ID", "Membership"}, [][]string{{"tenant", "Global Reader"}}, nil
|
||||
}
|
||||
getWhoamiAzureRBAC = func() ([]string, [][]string, []error) {
|
||||
return []string{"Tenant ID", "Azure RBAC Role"}, [][]string{{"tenant", "Reader"}}, nil
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
getWhoamiEntraRoles = originalEntra
|
||||
getWhoamiAzureRBAC = originalRBAC
|
||||
})
|
||||
if err := AzWhoamiCommand("~/.cloudfox", "DEV", false, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
AzWhoamiCommand(s.azOutputDirectory, s.version, s.wrapTableOutput, 1, false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-15
@@ -92,9 +92,8 @@ var (
|
||||
AWSUseCache bool
|
||||
AWSMFAToken string
|
||||
|
||||
Goroutines int
|
||||
Verbosity int
|
||||
AWSMaxRetries int
|
||||
Goroutines int
|
||||
Verbosity int
|
||||
|
||||
AWSCommands = &cobra.Command{
|
||||
Use: "aws",
|
||||
@@ -545,10 +544,10 @@ type OrgAccounts struct {
|
||||
}
|
||||
|
||||
func awsPreRun(cmd *cobra.Command, args []string) {
|
||||
gob.Register(&types.Organization{})
|
||||
// Set up Ctrl+C handler (double-press to confirm shutdown)
|
||||
internal.SetupSignalHandler()
|
||||
|
||||
// Set the max retries before any AWS config loading happens
|
||||
internal.MaxRetries = AWSMaxRetries
|
||||
gob.Register(&types.Organization{})
|
||||
|
||||
// if multiple profiles were used, ensure the management account is first
|
||||
// if AWSProfilesList != "" || AWSAllProfiles {
|
||||
@@ -2256,7 +2255,6 @@ func runAllChecksCommand(cmd *cobra.Command, args []string) {
|
||||
WrapTable: AWSWrapTable,
|
||||
AWSOutputType: AWSOutputType,
|
||||
AWSTableCols: AWSTableCols,
|
||||
PmapperDataBasePath: PmapperDataBasePath,
|
||||
}
|
||||
ec2UserData.Instances(InstancesFilter, AWSOutputDirectory, Verbosity)
|
||||
envsMod := aws.EnvsModule{
|
||||
@@ -2439,13 +2437,12 @@ func runAllChecksCommand(cmd *cobra.Command, args []string) {
|
||||
roleTrusts.PrintRoleTrusts(AWSOutputDirectory, Verbosity)
|
||||
|
||||
pmapperCommand := aws.PmapperModule{
|
||||
Caller: *caller,
|
||||
AWSProfile: profile,
|
||||
Goroutines: Goroutines,
|
||||
WrapTable: AWSWrapTable,
|
||||
AWSOutputType: AWSOutputType,
|
||||
AWSTableCols: AWSTableCols,
|
||||
PmapperDataBasePath: PmapperDataBasePath,
|
||||
Caller: *caller,
|
||||
AWSProfile: profile,
|
||||
Goroutines: Goroutines,
|
||||
WrapTable: AWSWrapTable,
|
||||
AWSOutputType: AWSOutputType,
|
||||
AWSTableCols: AWSTableCols,
|
||||
}
|
||||
pmapperCommand.PrintPmapperData(AWSOutputDirectory, Verbosity)
|
||||
|
||||
@@ -2557,7 +2554,6 @@ func init() {
|
||||
AWSCommands.PersistentFlags().BoolVarP(&AWSUseCache, "cached", "c", false, "Load cached data from disk. Faster, but if changes have been recently made you'll miss them")
|
||||
AWSCommands.PersistentFlags().StringVarP(&AWSTableCols, "cols", "t", "", "Comma separated list of columns to display in table output")
|
||||
AWSCommands.PersistentFlags().StringVar(&AWSMFAToken, "mfa-token", "", "MFA Token")
|
||||
AWSCommands.PersistentFlags().IntVar(&AWSMaxRetries, "max-retries", 3, "Maximum number of AWS API retry attempts. Set to 0 for no retries (faster for black-box/limited-permission enumeration)")
|
||||
AWSCommands.PersistentFlags().StringVar(&PmapperDataBasePath, "pmapper-data-basepath", "", "Supply the base path for the pmapper data files (useful if you have copied them from another machine)\nPoint to the parent directory that contains all of the pmapper data by account numbers. \n\tExample: /path/to/com.nccgroup.principalmapper/\n\tExample: ./pmapperdata/")
|
||||
|
||||
AWSCommands.AddCommand(
|
||||
|
||||
+13
-72
@@ -1,13 +1,12 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/BishopFox/cloudfox/azure"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var azureLogger = internal.NewLogger()
|
||||
|
||||
var (
|
||||
AzTenantID string
|
||||
AzSubscription string
|
||||
@@ -27,34 +26,18 @@ var (
|
||||
cmd.Help()
|
||||
},
|
||||
}
|
||||
AzTokenCacheListRGs bool
|
||||
AzWhoamiListRGsAlso bool
|
||||
AzWhoamiCommand = &cobra.Command{
|
||||
Use: "whoami",
|
||||
Aliases: []string{},
|
||||
Short: "Display the caller's Entra ID memberships and Azure RBAC assignments",
|
||||
Long: `
|
||||
Display the caller's Entra ID memberships and Azure RBAC assignments:
|
||||
./cloudfox az whoami`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := azure.AzWhoamiCommand(AzOutputDirectory, cmd.Root().Version, AzWrapTable, AzVerbosity)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(err.Error(), "whoami")
|
||||
}
|
||||
},
|
||||
}
|
||||
AzTokenCacheCommand = &cobra.Command{
|
||||
Use: "tokencache",
|
||||
Aliases: []string{"token-cache"},
|
||||
Short: "Display available Azure CLI sessions",
|
||||
Long: `
|
||||
Display available Azure CLI tenants and subscriptions:
|
||||
./cloudfox az tokencache
|
||||
|
||||
Include resource groups:
|
||||
./cloudfox az tokencache --list-rgs`,
|
||||
Display Available Azure CLI Sessions:
|
||||
./cloudfox az whoami`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := azure.AzTokenCacheCommand(AzOutputDirectory, cmd.Root().Version, AzWrapTable, AzVerbosity, AzTokenCacheListRGs); err != nil {
|
||||
azureLogger.FatalM(err.Error(), "tokencache")
|
||||
err := azure.AzWhoamiCommand(AzOutputDirectory, cmd.Root().Version, AzWrapTable, AzVerbosity, AzWhoamiListRGsAlso)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -72,7 +55,7 @@ Enumerate inventory for a specific subscription:
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := azure.AzInventoryCommand(AzTenantID, AzSubscription, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(err.Error(), "inventory")
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -91,7 +74,7 @@ Enumerate role assignments for a specific subscription:
|
||||
|
||||
err := azure.AzRBACCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(err.Error(), "rbac")
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -108,7 +91,7 @@ Enumerate VMs for a specific subscription:
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := azure.AzVMsCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(err.Error(), "vms")
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -126,45 +109,7 @@ Enumerate storage accounts for a specific subscription:
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := azure.AzStorageCommand(AzTenantID, AzSubscription, AzOutputFormat, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(err.Error(), "storage")
|
||||
}
|
||||
},
|
||||
}
|
||||
AzACRCommand = &cobra.Command{
|
||||
Use: "acr",
|
||||
Aliases: []string{"container-registries"},
|
||||
Short: "Enumerates Azure Container Registries",
|
||||
Long: `
|
||||
Enumerate container registries for a specific tenant:
|
||||
./cloudfox az acr --tenant TENANT_ID
|
||||
|
||||
Enumerate container registries for a specific subscription:
|
||||
./cloudfox az acr --subscription SUBSCRIPTION_ID
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := azure.AzACRCommand(AzTenantID, AzSubscription, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable)
|
||||
if err != nil {
|
||||
azureLogger.FatalM(err.Error(), "acr")
|
||||
}
|
||||
},
|
||||
}
|
||||
AzFuncCommand = &cobra.Command{
|
||||
Use: "func",
|
||||
Aliases: []string{"functions"},
|
||||
Short: "Enumerates Azure Function Apps and their configuration",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := azure.AzFuncCommand(AzTenantID, AzSubscription, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable); err != nil {
|
||||
azureLogger.FatalM(err.Error(), "func")
|
||||
}
|
||||
},
|
||||
}
|
||||
AzAutomationCommand = &cobra.Command{
|
||||
Use: "automation",
|
||||
Aliases: []string{"automations"},
|
||||
Short: "Enumerates Azure Automation Accounts and their assets",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := azure.AzAutomationCommand(AzTenantID, AzSubscription, AzOutputDirectory, cmd.Root().Version, AzVerbosity, AzWrapTable, AzMergedTable); err != nil {
|
||||
azureLogger.FatalM(err.Error(), "automation")
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -172,7 +117,7 @@ Enumerate container registries for a specific subscription:
|
||||
|
||||
func init() {
|
||||
|
||||
AzTokenCacheCommand.Flags().BoolVarP(&AzTokenCacheListRGs, "list-rgs", "l", false, "Drill down to the resource group level")
|
||||
AzWhoamiCommand.Flags().BoolVarP(&AzWhoamiListRGsAlso, "list-rgs", "l", false, "Drill down to the resource group level")
|
||||
|
||||
// Global flags
|
||||
AzCommands.PersistentFlags().StringVarP(&AzOutputFormat, "output", "o", "all", "[\"table\" | \"csv\" | \"all\" ]")
|
||||
@@ -186,13 +131,9 @@ func init() {
|
||||
|
||||
AzCommands.AddCommand(
|
||||
AzWhoamiCommand,
|
||||
AzTokenCacheCommand,
|
||||
AzRBACCommand,
|
||||
AzVMsCommand,
|
||||
AzStorageCommand,
|
||||
AzACRCommand,
|
||||
AzFuncCommand,
|
||||
AzAutomationCommand,
|
||||
AzInventoryCommand)
|
||||
|
||||
}
|
||||
+253
-25
@@ -2,7 +2,9 @@ package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -12,6 +14,8 @@ import (
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
gcpinternal "github.com/BishopFox/cloudfox/internal/gcp"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -22,6 +26,15 @@ var (
|
||||
GCPProjectIDs []string
|
||||
GCPAllProjects bool
|
||||
|
||||
// Service account impersonation
|
||||
GCPImpersonateSA string
|
||||
|
||||
// Service account key file authentication
|
||||
GCPKeyFile string
|
||||
|
||||
// Raw access token authentication
|
||||
GCPAccessToken string
|
||||
|
||||
// Project name mapping (ProjectID -> DisplayName)
|
||||
GCPProjectNames map[string]string
|
||||
|
||||
@@ -31,6 +44,8 @@ var (
|
||||
GCPVerbosity int
|
||||
GCPWrapTable bool
|
||||
GCPFlatOutput bool
|
||||
GCPQuery string
|
||||
GCPSQLQuery string
|
||||
|
||||
// Refresh cache flag - force re-enumeration even if cache exists
|
||||
GCPRefreshCache bool
|
||||
@@ -52,22 +67,111 @@ var (
|
||||
GCPProjectIDs = nil
|
||||
GCPProjectNames = make(map[string]string)
|
||||
|
||||
// Create session based on auth flags.
|
||||
// Allowed combinations:
|
||||
// --access-token (raw token)
|
||||
// --key-file (SA key file)
|
||||
// --impersonate-sa (impersonate over ADC)
|
||||
// --access-token + --impersonate-sa (impersonate using token as base)
|
||||
// --key-file + --impersonate-sa (impersonate using key as base)
|
||||
// Disallowed: --access-token + --key-file (both are base credentials)
|
||||
var gcpSession *gcpinternal.SafeSession
|
||||
|
||||
if GCPAccessToken != "" && GCPKeyFile != "" {
|
||||
GCPLogger.FatalM("--access-token and --key-file cannot be used together (both are base credentials)", "gcp")
|
||||
}
|
||||
|
||||
// Step 1: Create base session from access-token or key-file (if provided)
|
||||
var baseSession *gcpinternal.SafeSession
|
||||
if GCPAccessToken != "" {
|
||||
os.Unsetenv("GOOGLE_CLOUD_QUOTA_PROJECT")
|
||||
GCPLogger.InfoM("Authenticating with provided access token...", "gcp")
|
||||
var err error
|
||||
baseSession, err = gcpinternal.NewSafeSessionFromAccessToken(context.Background(), GCPAccessToken)
|
||||
if err != nil {
|
||||
GCPLogger.FatalM(fmt.Sprintf("Failed to authenticate with access token: %v", err), "gcp")
|
||||
}
|
||||
// If tokeninfo/userinfo endpoints didn't return an email (common with
|
||||
// SA tokens that only have the cloud-platform scope), try a project-scoped
|
||||
// probe using the CRM API to extract the caller from the error response.
|
||||
if baseSession.GetEmail() == "" && GCPProjectID != "" {
|
||||
gcpinternal.ProbeIdentityWithProject(context.Background(), baseSession, GCPProjectID)
|
||||
}
|
||||
email := baseSession.GetEmail()
|
||||
if email != "" {
|
||||
GCPLogger.SuccessM(fmt.Sprintf("Authenticated as %s (from access token)", email), "gcp")
|
||||
} else {
|
||||
GCPLogger.WarnM("Authenticated with access token (could not resolve identity)", "gcp")
|
||||
}
|
||||
} else if GCPKeyFile != "" {
|
||||
os.Unsetenv("GOOGLE_CLOUD_QUOTA_PROJECT")
|
||||
GCPLogger.InfoM(fmt.Sprintf("Authenticating with key file: %s", GCPKeyFile), "gcp")
|
||||
var err error
|
||||
baseSession, err = gcpinternal.NewSafeSessionFromKeyFile(context.Background(), GCPKeyFile)
|
||||
if err != nil {
|
||||
GCPLogger.FatalM(fmt.Sprintf("Failed to authenticate with key file %s: %v", GCPKeyFile, err), "gcp")
|
||||
}
|
||||
GCPLogger.SuccessM(fmt.Sprintf("Authenticated as %s (from key file)", baseSession.GetEmail()), "gcp")
|
||||
}
|
||||
|
||||
// Step 2: Layer impersonation on top (if requested)
|
||||
if GCPImpersonateSA != "" {
|
||||
os.Unsetenv("GOOGLE_CLOUD_QUOTA_PROJECT")
|
||||
var baseTS oauth2.TokenSource
|
||||
if baseSession != nil {
|
||||
baseTS = baseSession.GetTokenSource()
|
||||
GCPLogger.InfoM(fmt.Sprintf("Impersonating %s using %s as base credential...", GCPImpersonateSA, baseSession.GetEmail()), "gcp")
|
||||
} else {
|
||||
GCPLogger.InfoM(fmt.Sprintf("Impersonating service account: %s", GCPImpersonateSA), "gcp")
|
||||
}
|
||||
var err error
|
||||
gcpSession, err = gcpinternal.NewSafeSessionWithImpersonation(context.Background(), GCPImpersonateSA, baseTS)
|
||||
if err != nil {
|
||||
GCPLogger.FatalM(fmt.Sprintf("Failed to impersonate %s: %v", GCPImpersonateSA, err), "gcp")
|
||||
}
|
||||
GCPLogger.SuccessM(fmt.Sprintf("Successfully impersonating %s", GCPImpersonateSA), "gcp")
|
||||
} else if baseSession != nil {
|
||||
// No impersonation, use the base session directly
|
||||
gcpSession = baseSession
|
||||
} else {
|
||||
// Set quota project for GCP API calls. ADC may reference a deleted
|
||||
// or inaccessible quota project, causing all API calls to fail with
|
||||
// 403/404. Override it with the target project when known.
|
||||
if GCPProjectID != "" {
|
||||
os.Setenv("GOOGLE_CLOUD_QUOTA_PROJECT", GCPProjectID)
|
||||
} else if GCPProjectIDsFilePath != "" {
|
||||
lines := internal.LoadFileLinesIntoArray(GCPProjectIDsFilePath)
|
||||
if len(lines) > 0 {
|
||||
os.Setenv("GOOGLE_CLOUD_QUOTA_PROJECT", lines[0])
|
||||
}
|
||||
} else if GCPAllProjects {
|
||||
// No target project yet. Validate the ADC quota project
|
||||
// and clear it if the project is inaccessible.
|
||||
validateADCQuotaProject()
|
||||
}
|
||||
}
|
||||
|
||||
// Handle project discovery based on flags
|
||||
// Priority: -p (single project) > -l (project list) > -A (all projects)
|
||||
if GCPProjectID != "" {
|
||||
// Single project specified with -p/--project
|
||||
GCPProjectIDs = append(GCPProjectIDs, GCPProjectID)
|
||||
resolveProjectNames(GCPProjectIDs)
|
||||
resolveProjectNames(GCPProjectIDs, gcpSession)
|
||||
} else if GCPProjectIDsFilePath != "" {
|
||||
// Project list specified with -l/--project-list
|
||||
rawProjectIDs := internal.LoadFileLinesIntoArray(GCPProjectIDsFilePath)
|
||||
GCPProjectIDs = deduplicateProjectIDs(rawProjectIDs)
|
||||
resolveProjectNames(GCPProjectIDs)
|
||||
resolveProjectNames(GCPProjectIDs, gcpSession)
|
||||
} else if GCPAllProjects {
|
||||
// Discover all accessible projects with -A/--all-projects
|
||||
GCPLogger.InfoM("Discovering all accessible projects...", "gcp")
|
||||
orgsSvc := orgsservice.New()
|
||||
projects, err := orgsSvc.SearchProjects("")
|
||||
orgsSvc := newOrgsService(gcpSession)
|
||||
var projects []orgsservice.ProjectInfo
|
||||
err := gcpinternal.RetryWithBackoff(GCPLogger, "gcp", 3, func() error {
|
||||
var searchErr error
|
||||
projects, searchErr = orgsSvc.SearchProjects("")
|
||||
return searchErr
|
||||
})
|
||||
if err != nil {
|
||||
GCPLogger.FatalM(fmt.Sprintf("Failed to discover projects: %v. Try using -p or -l flags instead.", err), "gcp")
|
||||
}
|
||||
@@ -85,22 +189,53 @@ var (
|
||||
GCPLogger.InfoM("No project scope specified. Use -p, -l, or -A flag.", "gcp")
|
||||
}
|
||||
|
||||
// Create a context with project IDs and names
|
||||
ctx := context.WithValue(context.Background(), "projectIDs", GCPProjectIDs)
|
||||
// Create a context with project IDs and names, using signal-aware base context
|
||||
sigCtx := internal.SetupSignalHandler()
|
||||
ctx := context.WithValue(sigCtx, "projectIDs", GCPProjectIDs)
|
||||
ctx = context.WithValue(ctx, "projectNames", GCPProjectNames)
|
||||
|
||||
// Authenticate and get account info
|
||||
os := oauthservice.NewOAuthService()
|
||||
principal, err := os.WhoAmI()
|
||||
if err != nil {
|
||||
GCPLogger.FatalM(fmt.Sprintf("could not determine default user credential with error %s.\n\nPlease use default application default credentials: https://cloud.google.com/docs/authentication/application-default-credentials\n\nTry: gcloud auth application-default login", err.Error()), "gcp")
|
||||
// Set default session so all services pick up impersonation
|
||||
if gcpSession != nil {
|
||||
gcpinternal.SetDefaultSession(gcpSession)
|
||||
}
|
||||
|
||||
// Set global query filter if --query or --sqlquery flag is provided
|
||||
if GCPQuery != "" && GCPSQLQuery != "" {
|
||||
GCPLogger.FatalM("--query and --sqlquery cannot be used together", "gcp")
|
||||
}
|
||||
if GCPQuery != "" {
|
||||
internal.SetQueryFilter(GCPQuery)
|
||||
}
|
||||
if GCPSQLQuery != "" {
|
||||
internal.SetSQLQueryFilter(GCPSQLQuery)
|
||||
}
|
||||
|
||||
// Authenticate and get account info.
|
||||
// Check impersonation first since it overrides the effective identity
|
||||
// even when combined with --access-token or --key-file.
|
||||
if GCPImpersonateSA != "" {
|
||||
ctx = context.WithValue(ctx, "account", GCPImpersonateSA)
|
||||
} else if GCPAccessToken != "" {
|
||||
email := gcpSession.GetEmail()
|
||||
if email == "" {
|
||||
email = "access-token-user"
|
||||
}
|
||||
ctx = context.WithValue(ctx, "account", email)
|
||||
} else if GCPKeyFile != "" {
|
||||
ctx = context.WithValue(ctx, "account", gcpSession.GetEmail())
|
||||
} else {
|
||||
os := oauthservice.NewOAuthService()
|
||||
principal, err := os.WhoAmI()
|
||||
if err != nil {
|
||||
GCPLogger.FatalM(fmt.Sprintf("could not determine default user credential with error %s.\n\nPlease use default application default credentials: https://cloud.google.com/docs/authentication/application-default-credentials\n\nTry: gcloud auth application-default login", err.Error()), "gcp")
|
||||
}
|
||||
ctx = context.WithValue(ctx, "account", principal.Email)
|
||||
}
|
||||
ctx = context.WithValue(ctx, "account", principal.Email)
|
||||
|
||||
// Build scope hierarchy for hierarchical output (unless --flat-output is set)
|
||||
if !GCPFlatOutput && len(GCPProjectIDs) > 0 {
|
||||
GCPLogger.InfoM("Building scope hierarchy for hierarchical output...", "gcp")
|
||||
orgsSvc := orgsservice.New()
|
||||
orgsSvc := newOrgsService(gcpSession)
|
||||
provider := orgsservice.NewHierarchyProvider(orgsSvc)
|
||||
hierarchy, err := gcpinternal.BuildScopeHierarchy(GCPProjectIDs, provider)
|
||||
if err != nil {
|
||||
@@ -153,7 +288,7 @@ var (
|
||||
// Cache auto-refreshes after 24 hours
|
||||
// Force refresh when running the organizations command to ensure fresh data
|
||||
refreshCache := GCPRefreshCache || cmd.Name() == "organizations"
|
||||
orgCache := loadOrPopulateOrgCache(account, refreshCache)
|
||||
orgCache := loadOrPopulateOrgCache(account, refreshCache, gcpSession)
|
||||
if orgCache != nil && orgCache.IsPopulated() {
|
||||
ctx = gcpinternal.SetOrgCacheInContext(ctx, orgCache)
|
||||
}
|
||||
@@ -163,6 +298,14 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
// newOrgsService creates an organizations service, using the impersonated session if provided
|
||||
func newOrgsService(session *gcpinternal.SafeSession) *orgsservice.OrganizationsService {
|
||||
if session != nil {
|
||||
return orgsservice.NewWithSession(session)
|
||||
}
|
||||
return orgsservice.New()
|
||||
}
|
||||
|
||||
// deduplicateProjectIDs removes duplicates, trims whitespace, and filters empty entries
|
||||
func deduplicateProjectIDs(projectIDs []string) []string {
|
||||
seen := make(map[string]bool)
|
||||
@@ -195,15 +338,72 @@ func deduplicateProjectIDs(projectIDs []string) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
// validateADCQuotaProject reads the ADC file and checks whether the configured
|
||||
// quota project is accessible. The Go SDK sends x-goog-user-project on every
|
||||
// request based on the ADC's quota_project_id. If that project is deleted or
|
||||
// inaccessible, ALL API calls will fail with 403/404. This function detects
|
||||
// that condition and clears the quota project so the SDK doesn't send the
|
||||
// broken header. The caller is responsible for setting a valid quota project
|
||||
// once a working project is discovered.
|
||||
func validateADCQuotaProject() {
|
||||
creds, err := google.FindDefaultCredentials(context.Background())
|
||||
if err != nil || creds.JSON == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var adcFile struct {
|
||||
QuotaProject string `json:"quota_project_id"`
|
||||
}
|
||||
if err := json.Unmarshal(creds.JSON, &adcFile); err != nil || adcFile.QuotaProject == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Proactively clear the quota project via env var override. If the ADC
|
||||
// quota project is valid, the SearchProjects call below will succeed and
|
||||
// we'll restore it. If it's broken, we've already fixed the problem.
|
||||
os.Setenv("GOOGLE_CLOUD_QUOTA_PROJECT", "")
|
||||
|
||||
orgsSvc := orgsservice.New()
|
||||
projects, err := orgsSvc.SearchProjects("")
|
||||
if err != nil {
|
||||
// Can't validate. Leave it cleared (better than a known-broken value).
|
||||
GCPLogger.WarnM(fmt.Sprintf("Could not validate ADC quota project '%s': %v", adcFile.QuotaProject, err), "gcp")
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the original quota project is among accessible active projects
|
||||
for _, p := range projects {
|
||||
if p.ProjectID == adcFile.QuotaProject && p.State == "ACTIVE" {
|
||||
// Original quota project is valid, restore it
|
||||
os.Setenv("GOOGLE_CLOUD_QUOTA_PROJECT", adcFile.QuotaProject)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Original quota project is not accessible. Pick the first active project.
|
||||
GCPLogger.WarnM(fmt.Sprintf("ADC quota project '%s' is not accessible or has been deleted. Overriding with first available project.", adcFile.QuotaProject), "gcp")
|
||||
for _, p := range projects {
|
||||
if p.State == "ACTIVE" {
|
||||
os.Setenv("GOOGLE_CLOUD_QUOTA_PROJECT", p.ProjectID)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// resolveProjectNames fetches display names for given project IDs
|
||||
func resolveProjectNames(projectIDs []string) {
|
||||
func resolveProjectNames(projectIDs []string, session *gcpinternal.SafeSession) {
|
||||
if len(projectIDs) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
orgsSvc := orgsservice.New()
|
||||
orgsSvc := newOrgsService(session)
|
||||
// Fetch all accessible projects and build lookup map
|
||||
projects, err := orgsSvc.SearchProjects("")
|
||||
var projects []orgsservice.ProjectInfo
|
||||
err := gcpinternal.RetryWithBackoff(GCPLogger, "gcp", 3, func() error {
|
||||
var searchErr error
|
||||
projects, searchErr = orgsSvc.SearchProjects("")
|
||||
return searchErr
|
||||
})
|
||||
if err != nil {
|
||||
// Non-fatal: we can continue without display names
|
||||
GCPLogger.InfoM("Could not resolve project names, using project IDs only", "gcp")
|
||||
@@ -245,11 +445,17 @@ var GCPAllChecksCommand = &cobra.Command{
|
||||
ctx = gcpinternal.SetAllChecksMode(ctx, true)
|
||||
cmd.SetContext(ctx)
|
||||
|
||||
// Get session from context for impersonation support
|
||||
var allChecksSession *gcpinternal.SafeSession
|
||||
if s, ok := ctx.Value("session").(*gcpinternal.SafeSession); ok {
|
||||
allChecksSession = s
|
||||
}
|
||||
|
||||
// Load or populate org cache for cross-project modules
|
||||
existingOrgCache := gcpinternal.GetOrgCacheFromContext(ctx)
|
||||
if existingOrgCache == nil || !existingOrgCache.IsPopulated() {
|
||||
GCPLogger.InfoM("Loading/enumerating organization data for cross-project analysis...", "all-checks")
|
||||
orgCache := loadOrPopulateOrgCache(account, GCPRefreshCache)
|
||||
orgCache := loadOrPopulateOrgCache(account, GCPRefreshCache, allChecksSession)
|
||||
if orgCache != nil && orgCache.IsPopulated() {
|
||||
ctx = gcpinternal.SetOrgCacheInContext(ctx, orgCache)
|
||||
cmd.SetContext(ctx)
|
||||
@@ -310,6 +516,7 @@ var GCPAllChecksCommand = &cobra.Command{
|
||||
"bigquery-enum": true,
|
||||
"bigtable-enum": true,
|
||||
"spanner-enum": true,
|
||||
"access-tokens": true, // Local-only, no API calls
|
||||
}
|
||||
|
||||
// Count total modules to execute (excluding self, hidden, and excluded modules)
|
||||
@@ -344,7 +551,7 @@ var GCPAllChecksCommand = &cobra.Command{
|
||||
}
|
||||
|
||||
// loadOrPopulateOrgCache loads org cache from disk if available, or enumerates and saves it
|
||||
func loadOrPopulateOrgCache(account string, forceRefresh bool) *gcpinternal.OrgCache {
|
||||
func loadOrPopulateOrgCache(account string, forceRefresh bool, session *gcpinternal.SafeSession) *gcpinternal.OrgCache {
|
||||
// Check if cache exists and we're not forcing refresh
|
||||
if !forceRefresh && gcpinternal.OrgCacheExists(GCPOutputDirectory, account) {
|
||||
// Check if cache is stale (older than 24 hours)
|
||||
@@ -368,18 +575,23 @@ func loadOrPopulateOrgCache(account string, forceRefresh bool) *gcpinternal.OrgC
|
||||
}
|
||||
|
||||
// Enumerate and create cache
|
||||
cache := enumerateAndCacheOrgs(account)
|
||||
cache := enumerateAndCacheOrgs(account, session)
|
||||
return cache
|
||||
}
|
||||
|
||||
// enumerateAndCacheOrgs enumerates all orgs/folders/projects and saves to disk
|
||||
func enumerateAndCacheOrgs(account string) *gcpinternal.OrgCache {
|
||||
func enumerateAndCacheOrgs(account string, session *gcpinternal.SafeSession) *gcpinternal.OrgCache {
|
||||
cache := gcpinternal.NewOrgCache()
|
||||
|
||||
orgsSvc := orgsservice.New()
|
||||
orgsSvc := newOrgsService(session)
|
||||
|
||||
// Get all organizations
|
||||
orgs, err := orgsSvc.SearchOrganizations()
|
||||
var orgs []orgsservice.OrganizationInfo
|
||||
err := gcpinternal.RetryWithBackoff(GCPLogger, "gcp", 3, func() error {
|
||||
var searchErr error
|
||||
orgs, searchErr = orgsSvc.SearchOrganizations()
|
||||
return searchErr
|
||||
})
|
||||
if err == nil {
|
||||
for _, org := range orgs {
|
||||
cache.AddOrganization(gcpinternal.CachedOrganization{
|
||||
@@ -393,7 +605,12 @@ func enumerateAndCacheOrgs(account string) *gcpinternal.OrgCache {
|
||||
}
|
||||
|
||||
// Get all folders
|
||||
folders, err := orgsSvc.SearchAllFolders()
|
||||
var folders []orgsservice.FolderInfo
|
||||
err = gcpinternal.RetryWithBackoff(GCPLogger, "gcp", 3, func() error {
|
||||
var searchErr error
|
||||
folders, searchErr = orgsSvc.SearchAllFolders()
|
||||
return searchErr
|
||||
})
|
||||
if err == nil {
|
||||
for _, folder := range folders {
|
||||
cache.AddFolder(gcpinternal.CachedFolder{
|
||||
@@ -407,7 +624,12 @@ func enumerateAndCacheOrgs(account string) *gcpinternal.OrgCache {
|
||||
}
|
||||
|
||||
// Get all projects
|
||||
projects, err := orgsSvc.SearchProjects("")
|
||||
var projects []orgsservice.ProjectInfo
|
||||
err = gcpinternal.RetryWithBackoff(GCPLogger, "gcp", 3, func() error {
|
||||
var searchErr error
|
||||
projects, searchErr = orgsSvc.SearchProjects("")
|
||||
return searchErr
|
||||
})
|
||||
if err == nil {
|
||||
for _, project := range projects {
|
||||
// Extract project number from Name (format: "projects/123456789")
|
||||
@@ -504,6 +726,11 @@ func init() {
|
||||
GCPCommands.PersistentFlags().BoolVarP(&GCPWrapTable, "wrap", "w", false, "Wrap table to fit in terminal (complicates grepping)")
|
||||
GCPCommands.PersistentFlags().BoolVar(&GCPFlatOutput, "flat-output", false, "Use legacy flat output structure instead of hierarchical per-project directories")
|
||||
GCPCommands.PersistentFlags().BoolVar(&GCPRefreshCache, "refresh-cache", false, "Force re-enumeration of cached data (cache auto-expires after 24 hours)")
|
||||
GCPCommands.PersistentFlags().StringVarP(&GCPImpersonateSA, "impersonate-sa", "i", "", "Service account email to impersonate (requires roles/iam.serviceAccountTokenCreator)")
|
||||
GCPCommands.PersistentFlags().StringVarP(&GCPKeyFile, "key-file", "k", "", "Path to a service account JSON key file for authentication")
|
||||
GCPCommands.PersistentFlags().StringVar(&GCPAccessToken, "access-token", "", "Raw GCP access token for authentication (non-refreshable)")
|
||||
GCPCommands.PersistentFlags().StringVarP(&GCPQuery, "query", "q", "", "Filter output rows by substring match (e.g. \"Role:owner,Principal:privesc02\" or plain \"privesc02\")")
|
||||
GCPCommands.PersistentFlags().StringVar(&GCPSQLQuery, "sqlquery", "", "Filter output with SQL-like syntax (e.g. \"Role CONTAINS owner AND Principal CONTAINS privesc02\")")
|
||||
|
||||
// Available commands
|
||||
GCPCommands.AddCommand(
|
||||
@@ -572,6 +799,7 @@ func init() {
|
||||
commands.GCPAccessLevelsCommand,
|
||||
|
||||
// Pentest/Exploitation commands
|
||||
commands.GCPAccessTokensCommand,
|
||||
commands.GCPPrivescCommand,
|
||||
commands.GCPOrgPoliciesCommand,
|
||||
commands.GCPStorageEnumCommand,
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
accesstokensservice "github.com/BishopFox/cloudfox/gcp/services/accessTokensService"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// GCPAccessTokensCommand reads locally cached gcloud credentials.
|
||||
// It overrides PersistentPreRun to bypass the parent's auth/project requirements
|
||||
// since it only reads local files — no GCP API calls needed.
|
||||
var GCPAccessTokensCommand = &cobra.Command{
|
||||
Use: globals.GCP_ACCESS_TOKENS_MODULE_NAME,
|
||||
Aliases: []string{"tokens", "creds"},
|
||||
Short: "Enumerate locally cached gcloud access tokens, refresh tokens, and ADC",
|
||||
Long: `Reads locally cached gcloud credentials for post-exploitation credential discovery.
|
||||
|
||||
Sources checked:
|
||||
- access_tokens.db Cached OAuth2 access tokens
|
||||
- credentials.db Cached refresh tokens
|
||||
- application_default_credentials.json (ADC)
|
||||
- configurations/config_* All gcloud profiles
|
||||
- active_config Currently active profile
|
||||
- GOOGLE_APPLICATION_CREDENTIALS env var (SA key files)
|
||||
- CLOUDSDK_CONFIG_DIR env var (custom config paths)
|
||||
|
||||
This command reads LOCAL FILES ONLY — no GCP API calls are made.
|
||||
No project flag (-p) or authentication is required.`,
|
||||
|
||||
// Override PersistentPreRun to bypass the parent's auth/project resolution.
|
||||
// Cobra behavior: a child's PersistentPreRun fully replaces the parent's.
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
// Intentionally empty — no project flags or OAuth needed
|
||||
},
|
||||
Run: runGCPAccessTokensCommand,
|
||||
}
|
||||
|
||||
// AccessTokensModule holds the credential discovery results
|
||||
type AccessTokensModule struct {
|
||||
Credentials []accesstokensservice.CredentialSummary
|
||||
Verbosity int
|
||||
WrapTable bool
|
||||
OutputDir string
|
||||
Format string
|
||||
}
|
||||
|
||||
// AccessTokensOutput implements internal.CloudfoxOutput
|
||||
type AccessTokensOutput struct {
|
||||
Table []internal.TableFile
|
||||
Loot []internal.LootFile
|
||||
}
|
||||
|
||||
func (o AccessTokensOutput) TableFiles() []internal.TableFile { return o.Table }
|
||||
func (o AccessTokensOutput) LootFiles() []internal.LootFile { return o.Loot }
|
||||
|
||||
func runGCPAccessTokensCommand(cmd *cobra.Command, args []string) {
|
||||
logger := internal.NewLogger()
|
||||
|
||||
// Read output flags from the parent command (gcp)
|
||||
parentCmd := cmd.Parent()
|
||||
verbosity, _ := parentCmd.PersistentFlags().GetInt("verbosity")
|
||||
wrap, _ := parentCmd.PersistentFlags().GetBool("wrap")
|
||||
outputDir, _ := parentCmd.PersistentFlags().GetString("outdir")
|
||||
format, _ := parentCmd.PersistentFlags().GetString("output")
|
||||
if format == "" {
|
||||
format = "all"
|
||||
}
|
||||
|
||||
module := &AccessTokensModule{
|
||||
Verbosity: verbosity,
|
||||
WrapTable: wrap,
|
||||
OutputDir: outputDir,
|
||||
Format: format,
|
||||
}
|
||||
|
||||
module.Execute(logger)
|
||||
}
|
||||
|
||||
func (m *AccessTokensModule) Execute(logger internal.Logger) {
|
||||
logger.InfoM("Searching for locally cached gcloud credentials...", globals.GCP_ACCESS_TOKENS_MODULE_NAME)
|
||||
|
||||
svc := accesstokensservice.New()
|
||||
creds, err := svc.GetAllCredentials()
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error reading credentials: %v", err), globals.GCP_ACCESS_TOKENS_MODULE_NAME)
|
||||
return
|
||||
}
|
||||
|
||||
if len(creds) == 0 {
|
||||
logger.InfoM("No cached credentials found", globals.GCP_ACCESS_TOKENS_MODULE_NAME)
|
||||
return
|
||||
}
|
||||
|
||||
m.Credentials = creds
|
||||
|
||||
// Sort: active tokens with refresh tokens first, then active-only, then expired with refresh, then rest
|
||||
sort.Slice(m.Credentials, func(i, j int) bool {
|
||||
return credSortScore(m.Credentials[i]) > credSortScore(m.Credentials[j])
|
||||
})
|
||||
|
||||
logger.SuccessM(fmt.Sprintf("Found %d credential(s)", len(m.Credentials)), globals.GCP_ACCESS_TOKENS_MODULE_NAME)
|
||||
|
||||
m.writeOutput(logger)
|
||||
}
|
||||
|
||||
// credSortScore returns a numeric score for sorting (higher = more interesting to a pentester)
|
||||
func credSortScore(c accesstokensservice.CredentialSummary) int {
|
||||
score := 0
|
||||
if c.Validity == "ACTIVE" {
|
||||
score += 4
|
||||
}
|
||||
if c.HasRefresh {
|
||||
score += 2
|
||||
}
|
||||
if c.IsActive {
|
||||
score += 1
|
||||
}
|
||||
return score
|
||||
}
|
||||
|
||||
func (m *AccessTokensModule) getTableHeader() []string {
|
||||
return []string{
|
||||
"Account",
|
||||
"Type",
|
||||
"Validity",
|
||||
"Has Refresh Token",
|
||||
"RAPT Status",
|
||||
"Profile",
|
||||
"Active Profile",
|
||||
"Project",
|
||||
"Access Token",
|
||||
}
|
||||
}
|
||||
|
||||
func (m *AccessTokensModule) credentialsToTableBody() [][]string {
|
||||
var body [][]string
|
||||
for _, c := range m.Credentials {
|
||||
hasRefresh := "No"
|
||||
if c.HasRefresh {
|
||||
hasRefresh = "Yes"
|
||||
}
|
||||
active := "No"
|
||||
if c.IsActive {
|
||||
active = "Yes"
|
||||
}
|
||||
profile := c.Profile
|
||||
if profile == "" {
|
||||
profile = "-"
|
||||
}
|
||||
project := c.Project
|
||||
if project == "" {
|
||||
project = "-"
|
||||
}
|
||||
accessToken := c.AccessToken
|
||||
if accessToken == "" {
|
||||
accessToken = "-"
|
||||
}
|
||||
|
||||
body = append(body, []string{
|
||||
c.Account,
|
||||
c.Type,
|
||||
c.Validity,
|
||||
hasRefresh,
|
||||
c.RAPTStatus,
|
||||
profile,
|
||||
active,
|
||||
project,
|
||||
accessToken,
|
||||
})
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
func (m *AccessTokensModule) writeOutput(logger internal.Logger) {
|
||||
body := m.credentialsToTableBody()
|
||||
|
||||
tables := []internal.TableFile{{
|
||||
Name: "access-tokens",
|
||||
Header: m.getTableHeader(),
|
||||
Body: body,
|
||||
}}
|
||||
|
||||
// Build loot files
|
||||
var lootFiles []internal.LootFile
|
||||
|
||||
// Loot 1: Exploitation commands (curl with active access tokens)
|
||||
var exploitCmds strings.Builder
|
||||
exploitCmds.WriteString("# Active Access Token Exploitation Commands\n")
|
||||
exploitCmds.WriteString("# Generated by CloudFox\n\n")
|
||||
hasExploitCmds := false
|
||||
for _, c := range m.Credentials {
|
||||
if c.Type == "access_token" && c.Validity == "ACTIVE" && c.AccessToken != "" {
|
||||
exploitCmds.WriteString(fmt.Sprintf("# Account: %s (Profile: %s, Project: %s)\n", c.Account, c.Profile, c.Project))
|
||||
exploitCmds.WriteString(fmt.Sprintf("curl -s -H 'Authorization: Bearer %s' https://www.googleapis.com/oauth2/v1/tokeninfo\n", c.AccessToken))
|
||||
exploitCmds.WriteString(fmt.Sprintf("curl -s -H 'Authorization: Bearer %s' https://cloudresourcemanager.googleapis.com/v1/projects\n\n", c.AccessToken))
|
||||
hasExploitCmds = true
|
||||
}
|
||||
if c.Type == "sa_key" {
|
||||
exploitCmds.WriteString(fmt.Sprintf("# Service Account Key: %s\n", c.Account))
|
||||
exploitCmds.WriteString(fmt.Sprintf("# Source: %s\n", c.Source))
|
||||
exploitCmds.WriteString(fmt.Sprintf("gcloud auth activate-service-account --key-file=%s\n\n", c.Source))
|
||||
hasExploitCmds = true
|
||||
}
|
||||
}
|
||||
if hasExploitCmds {
|
||||
lootFiles = append(lootFiles, internal.LootFile{
|
||||
Name: "access-tokens-exploitation-commands",
|
||||
Contents: exploitCmds.String(),
|
||||
})
|
||||
}
|
||||
|
||||
// Loot 2: Refresh token commands
|
||||
var refreshCmds strings.Builder
|
||||
refreshCmds.WriteString("# Refresh Token Commands\n")
|
||||
refreshCmds.WriteString("# Generated by CloudFox\n")
|
||||
refreshCmds.WriteString("# Refresh tokens can be used to obtain new access tokens\n\n")
|
||||
hasRefreshCmds := false
|
||||
for _, c := range m.Credentials {
|
||||
if c.HasRefresh && (c.Type == "access_token" || c.Type == "refresh_token") {
|
||||
refreshCmds.WriteString(fmt.Sprintf("# Account: %s\n", c.Account))
|
||||
refreshCmds.WriteString("# To extract the refresh token:\n")
|
||||
refreshCmds.WriteString(fmt.Sprintf("# sqlite3 ~/.config/gcloud/credentials.db \"SELECT value FROM credentials WHERE account_id='%s'\"\n", c.Account))
|
||||
refreshCmds.WriteString("# Then use it to get a new access token:\n")
|
||||
refreshCmds.WriteString("# curl -s -X POST https://oauth2.googleapis.com/token \\\n")
|
||||
refreshCmds.WriteString("# -d 'client_id=<CLIENT_ID>' \\\n")
|
||||
refreshCmds.WriteString("# -d 'client_secret=<CLIENT_SECRET>' \\\n")
|
||||
refreshCmds.WriteString("# -d 'refresh_token=<REFRESH_TOKEN>' \\\n")
|
||||
refreshCmds.WriteString("# -d 'grant_type=refresh_token'\n\n")
|
||||
hasRefreshCmds = true
|
||||
}
|
||||
}
|
||||
if hasRefreshCmds {
|
||||
lootFiles = append(lootFiles, internal.LootFile{
|
||||
Name: "access-tokens-refresh-commands",
|
||||
Contents: refreshCmds.String(),
|
||||
})
|
||||
}
|
||||
|
||||
// Loot 3: ADC commands
|
||||
var adcCmds strings.Builder
|
||||
adcCmds.WriteString("# Application Default Credentials (ADC) Commands\n")
|
||||
adcCmds.WriteString("# Generated by CloudFox\n\n")
|
||||
hasADCCmds := false
|
||||
for _, c := range m.Credentials {
|
||||
if c.Type == "adc" {
|
||||
adcCmds.WriteString(fmt.Sprintf("# ADC Account: %s\n", c.Account))
|
||||
adcCmds.WriteString(fmt.Sprintf("# Source: %s\n", c.Source))
|
||||
if c.Project != "" {
|
||||
adcCmds.WriteString(fmt.Sprintf("# Quota Project: %s\n", c.Project))
|
||||
}
|
||||
adcCmds.WriteString("# ADC is used by client libraries and tools like terraform\n")
|
||||
adcCmds.WriteString("# To use these credentials:\n")
|
||||
adcCmds.WriteString("# gcloud auth application-default print-access-token\n")
|
||||
adcCmds.WriteString("# Or copy ~/.config/gcloud/application_default_credentials.json to your attack machine\n\n")
|
||||
hasADCCmds = true
|
||||
}
|
||||
}
|
||||
if hasADCCmds {
|
||||
lootFiles = append(lootFiles, internal.LootFile{
|
||||
Name: "access-tokens-adc-commands",
|
||||
Contents: adcCmds.String(),
|
||||
})
|
||||
}
|
||||
|
||||
output := AccessTokensOutput{Table: tables, Loot: lootFiles}
|
||||
|
||||
// Use HandleOutputSmart with a local-only scope
|
||||
err := internal.HandleOutputSmart(
|
||||
"gcp",
|
||||
m.Format,
|
||||
m.OutputDir,
|
||||
m.Verbosity,
|
||||
m.WrapTable,
|
||||
"local",
|
||||
[]string{"localhost"},
|
||||
[]string{"localhost"},
|
||||
"local",
|
||||
output,
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), globals.GCP_ACCESS_TOKENS_MODULE_NAME)
|
||||
}
|
||||
}
|
||||
@@ -200,7 +200,7 @@ func (m *AppEngineModule) processProject(ctx context.Context, projectID string,
|
||||
app, err := aeService.Apps.Get(projectID).Do()
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), "404") {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, GCP_APPENGINE_MODULE_NAME,
|
||||
fmt.Sprintf("Could not get App Engine app in project %s", projectID))
|
||||
}
|
||||
@@ -251,7 +251,7 @@ func (m *AppEngineModule) processProject(ctx context.Context, projectID string,
|
||||
func (m *AppEngineModule) enumerateServices(ctx context.Context, projectID string, aeService *appengine.APIService, logger internal.Logger) {
|
||||
services, err := aeService.Apps.Services.List(projectID).Do()
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, GCP_APPENGINE_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate App Engine services in project %s", projectID))
|
||||
return
|
||||
@@ -288,7 +288,7 @@ func (m *AppEngineModule) enumerateServices(ctx context.Context, projectID strin
|
||||
func (m *AppEngineModule) enumerateVersions(ctx context.Context, projectID, serviceID, ingressSettings string, aeService *appengine.APIService, logger internal.Logger) {
|
||||
versions, err := aeService.Apps.Services.Versions.List(projectID, serviceID).Do()
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, GCP_APPENGINE_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate App Engine versions for service %s", serviceID))
|
||||
return
|
||||
@@ -362,7 +362,7 @@ func (m *AppEngineModule) enumerateVersions(ctx context.Context, projectID, serv
|
||||
func (m *AppEngineModule) enumerateFirewallRules(ctx context.Context, projectID string, aeService *appengine.APIService, logger internal.Logger) {
|
||||
rules, err := aeService.Apps.Firewall.IngressRules.List(projectID).Do()
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, GCP_APPENGINE_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate App Engine firewall rules in project %s", projectID))
|
||||
return
|
||||
@@ -776,7 +776,7 @@ func (m *AppEngineModule) writeHierarchicalOutput(ctx context.Context, logger in
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing hierarchical output: %v", err), GCP_APPENGINE_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -823,6 +823,6 @@ func (m *AppEngineModule) writeFlatOutput(ctx context.Context, logger internal.L
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), GCP_APPENGINE_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
artifactregistry "cloud.google.com/go/artifactregistry/apiv1"
|
||||
ArtifactRegistryService "github.com/BishopFox/cloudfox/gcp/services/artifactRegistryService"
|
||||
gcpinternal "github.com/BishopFox/cloudfox/internal/gcp"
|
||||
"google.golang.org/api/option"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -73,8 +74,12 @@ func runGCPArtifactRegistryCommand(cmd *cobra.Command, args []string) {
|
||||
return // Error already logged
|
||||
}
|
||||
|
||||
// Create Artifact Registry client
|
||||
client, err := artifactregistry.NewClient(cmdCtx.Ctx)
|
||||
// Create Artifact Registry client (uses impersonated credentials if --impersonate-sa is set)
|
||||
var arOpts []option.ClientOption
|
||||
if s := gcpinternal.GetDefaultSession(); s != nil {
|
||||
arOpts = append(arOpts, s.GetClientOption())
|
||||
}
|
||||
client, err := artifactregistry.NewClient(cmdCtx.Ctx, arOpts...)
|
||||
if err != nil {
|
||||
cmdCtx.Logger.ErrorM(fmt.Sprintf("Failed to create Artifact Registry client: %v", err), globals.GCP_ARTIFACT_RESGISTRY_MODULE_NAME)
|
||||
return
|
||||
@@ -146,7 +151,7 @@ func (m *ArtifactRegistryModule) processProject(ctx context.Context, projectID s
|
||||
ars := ArtifactRegistryService.New(m.client)
|
||||
result, err := ars.RepositoriesAndArtifacts(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_ARTIFACT_RESGISTRY_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate artifact registries in project %s", projectID))
|
||||
return
|
||||
@@ -525,7 +530,7 @@ func (m *ArtifactRegistryModule) writeHierarchicalOutput(ctx context.Context, lo
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing hierarchical output: %v", err), globals.GCP_ARTIFACT_RESGISTRY_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,6 +588,6 @@ func (m *ArtifactRegistryModule) writeFlatOutput(ctx context.Context, logger int
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), globals.GCP_ARTIFACT_RESGISTRY_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
@@ -257,7 +257,7 @@ func (m *AssetInventoryModule) processProject(ctx context.Context, projectID str
|
||||
svc := assetservice.New()
|
||||
assets, err := svc.ListAssets(projectID, assetTypes)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_ASSET_INVENTORY_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate assets in project %s", projectID))
|
||||
return
|
||||
@@ -283,7 +283,7 @@ func (m *AssetInventoryModule) processProjectIAM(ctx context.Context, projectID
|
||||
svc := assetservice.New()
|
||||
assets, err := svc.ListAssetsWithIAM(projectID, assetTypes)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_ASSET_INVENTORY_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate assets with IAM in project %s", projectID))
|
||||
return
|
||||
@@ -309,7 +309,7 @@ func (m *AssetInventoryModule) processProjectCounts(ctx context.Context, project
|
||||
svc := assetservice.New()
|
||||
counts, err := svc.GetAssetTypeCounts(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_ASSET_INVENTORY_MODULE_NAME,
|
||||
fmt.Sprintf("Could not count assets in project %s", projectID))
|
||||
return
|
||||
@@ -374,7 +374,7 @@ func (m *AssetInventoryModule) processProjectWithDependencies(ctx context.Contex
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
parsedErr := gcpinternal.ParseGCPError(err, "cloudasset.googleapis.com")
|
||||
gcpinternal.HandleGCPError(parsedErr, logger, globals.GCP_ASSET_INVENTORY_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate assets in project %s", projectID))
|
||||
|
||||
@@ -283,7 +283,7 @@ func (m *BackupInventoryModule) enumerateDisks(ctx context.Context, projectID st
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, GCP_BACKUPINVENTORY_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate disks in project %s", projectID))
|
||||
}
|
||||
@@ -376,7 +376,7 @@ func (m *BackupInventoryModule) enumerateSnapshots(ctx context.Context, projectI
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, GCP_BACKUPINVENTORY_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate snapshots in project %s", projectID))
|
||||
}
|
||||
@@ -445,7 +445,7 @@ func (m *BackupInventoryModule) trackSnapshotProtection(projectID string) {
|
||||
func (m *BackupInventoryModule) enumerateSQLBackups(ctx context.Context, projectID string, sqlService *sqladmin.Service, logger internal.Logger) {
|
||||
instances, err := sqlService.Instances.List(projectID).Do()
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, GCP_BACKUPINVENTORY_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate SQL instances in project %s", projectID))
|
||||
return
|
||||
|
||||
@@ -132,7 +132,7 @@ func (m *BeyondCorpModule) processProject(ctx context.Context, projectID string,
|
||||
// Get app connectors
|
||||
connectors, err := svc.ListAppConnectors(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_BEYONDCORP_MODULE_NAME,
|
||||
fmt.Sprintf("Could not list app connectors in project %s", projectID))
|
||||
}
|
||||
@@ -146,7 +146,7 @@ func (m *BeyondCorpModule) processProject(ctx context.Context, projectID string,
|
||||
// Get app connections
|
||||
connections, err := svc.ListAppConnections(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_BEYONDCORP_MODULE_NAME,
|
||||
fmt.Sprintf("Could not list app connections in project %s", projectID))
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"sync"
|
||||
|
||||
BigQueryService "github.com/BishopFox/cloudfox/gcp/services/bigqueryService"
|
||||
IAMService "github.com/BishopFox/cloudfox/gcp/services/iamService"
|
||||
"github.com/BishopFox/cloudfox/gcp/shared"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
gcpinternal "github.com/BishopFox/cloudfox/internal/gcp"
|
||||
@@ -129,12 +131,46 @@ func (m *BigQueryModule) processProject(ctx context.Context, projectID string, l
|
||||
bqService := BigQueryService.New()
|
||||
result, err := bqService.BigqueryDatasetsAndTables(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_BIGQUERY_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate BigQuery in project %s", projectID))
|
||||
return
|
||||
}
|
||||
|
||||
// Inject inherited project-level IAM bindings
|
||||
iamSvc := IAMService.New()
|
||||
projectPolicies, projErr := iamSvc.Policies(projectID, "project")
|
||||
if projErr == nil {
|
||||
// Inject into datasets (AccessEntries)
|
||||
for i := range result.Datasets {
|
||||
for _, pb := range projectPolicies {
|
||||
if !shared.IsInheritedRole("dataset", pb.Role) {
|
||||
continue
|
||||
}
|
||||
for _, member := range pb.Members {
|
||||
result.Datasets[i].AccessEntries = append(result.Datasets[i].AccessEntries, BigQueryService.AccessEntry{
|
||||
Role: pb.Role,
|
||||
Entity: member,
|
||||
Source: "Project",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
// Inject into tables
|
||||
for i := range result.Tables {
|
||||
for _, pb := range projectPolicies {
|
||||
if !shared.IsInheritedRole("table", pb.Role) {
|
||||
continue
|
||||
}
|
||||
result.Tables[i].IAMBindings = append(result.Tables[i].IAMBindings, BigQueryService.TableIAMBinding{
|
||||
Role: pb.Role,
|
||||
Members: pb.Members,
|
||||
Source: "Project",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Thread-safe store per-project
|
||||
m.mu.Lock()
|
||||
m.ProjectDatasets[projectID] = result.Datasets
|
||||
@@ -261,6 +297,7 @@ func (m *BigQueryModule) getDatasetHeader() []string {
|
||||
"IAM Binding Role",
|
||||
"Principal Type",
|
||||
"IAM Binding Principal",
|
||||
"Binding Source",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,6 +313,7 @@ func (m *BigQueryModule) getTableHeader() []string {
|
||||
"Public",
|
||||
"IAM Binding Role",
|
||||
"IAM Binding Principal",
|
||||
"Binding Source",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,6 +344,7 @@ func (m *BigQueryModule) datasetsToTableBody(datasets []BigQueryService.Bigquery
|
||||
role,
|
||||
memberType,
|
||||
entry.Entity,
|
||||
shared.BindingSource(entry.Source),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
@@ -318,6 +357,7 @@ func (m *BigQueryModule) datasetsToTableBody(datasets []BigQueryService.Bigquery
|
||||
"-",
|
||||
"-",
|
||||
"-",
|
||||
"-",
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -344,6 +384,7 @@ func (m *BigQueryModule) tablesToTableBody(tables []BigQueryService.BigqueryTabl
|
||||
publicStatus,
|
||||
"-",
|
||||
"-",
|
||||
"-",
|
||||
})
|
||||
} else {
|
||||
for _, binding := range table.IAMBindings {
|
||||
@@ -358,6 +399,7 @@ func (m *BigQueryModule) tablesToTableBody(tables []BigQueryService.BigqueryTabl
|
||||
publicStatus,
|
||||
binding.Role,
|
||||
member,
|
||||
shared.BindingSource(binding.Source),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -425,7 +467,7 @@ func (m *BigQueryModule) writeHierarchicalOutput(ctx context.Context, logger int
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing hierarchical output: %v", err), globals.GCP_BIGQUERY_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,6 +521,6 @@ func (m *BigQueryModule) writeFlatOutput(ctx context.Context, logger internal.Lo
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), globals.GCP_BIGQUERY_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
@@ -128,7 +128,7 @@ func (m *BigQueryEnumModule) processProject(ctx context.Context, projectID strin
|
||||
|
||||
resources, err := svc.EnumerateSensitiveResources(projectID, m.MaxTables, m.SampleData, m.MaxRows)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_BIGQUERYENUM_MODULE_NAME,
|
||||
fmt.Sprintf("Could not scan BigQuery in project %s", projectID))
|
||||
return
|
||||
|
||||
@@ -118,7 +118,7 @@ func (m *BigtableModule) processProject(ctx context.Context, projectID string, l
|
||||
svc := bigtableservice.New()
|
||||
result, err := svc.ListInstances(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_BIGTABLE_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate Bigtable instances in project %s", projectID))
|
||||
return
|
||||
|
||||
@@ -103,7 +103,7 @@ func (m *BigtableEnumModule) processProject(ctx context.Context, projectID strin
|
||||
|
||||
resources, err := svc.EnumerateSensitiveResources(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_BIGTABLEENUM_MODULE_NAME,
|
||||
fmt.Sprintf("Could not scan Bigtable in project %s", projectID))
|
||||
return
|
||||
|
||||
@@ -214,7 +214,7 @@ func (m *BucketEnumModule) processProject(ctx context.Context, projectID string,
|
||||
// Get list of buckets
|
||||
buckets, err := svc.GetBucketsList(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_STORAGEENUM_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate buckets in project %s", projectID))
|
||||
return
|
||||
@@ -230,7 +230,7 @@ func (m *BucketEnumModule) processProject(ctx context.Context, projectID string,
|
||||
for _, bucketName := range buckets {
|
||||
objects, err := svc.EnumerateAllBucketObjects(bucketName, projectID, m.MaxObjects)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_STORAGEENUM_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate bucket %s in project %s", bucketName, projectID))
|
||||
continue
|
||||
@@ -263,7 +263,7 @@ func (m *BucketEnumModule) processProject(ctx context.Context, projectID string,
|
||||
for _, bucketName := range buckets {
|
||||
files, err := svc.EnumerateBucketSensitiveFiles(bucketName, projectID, m.MaxObjects)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_STORAGEENUM_MODULE_NAME,
|
||||
fmt.Sprintf("Could not scan bucket %s in project %s", bucketName, projectID))
|
||||
continue
|
||||
|
||||
+25
-3
@@ -7,6 +7,7 @@ import (
|
||||
"sync"
|
||||
|
||||
CloudStorageService "github.com/BishopFox/cloudfox/gcp/services/cloudStorageService"
|
||||
IAMService "github.com/BishopFox/cloudfox/gcp/services/iamService"
|
||||
"github.com/BishopFox/cloudfox/gcp/shared"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
@@ -155,12 +156,30 @@ func (m *BucketsModule) processProject(ctx context.Context, projectID string, lo
|
||||
cs := CloudStorageService.New()
|
||||
buckets, err := cs.Buckets(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_STORAGE_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate buckets in project %s", projectID))
|
||||
return
|
||||
}
|
||||
|
||||
// Inject inherited project-level IAM bindings into each bucket
|
||||
iamSvc := IAMService.New()
|
||||
projectPolicies, projErr := iamSvc.Policies(projectID, "project")
|
||||
if projErr == nil {
|
||||
for i := range buckets {
|
||||
for _, pb := range projectPolicies {
|
||||
if !shared.IsInheritedRole("bucket", pb.Role) {
|
||||
continue
|
||||
}
|
||||
buckets[i].IAMBindings = append(buckets[i].IAMBindings, CloudStorageService.IAMBinding{
|
||||
Role: pb.Role,
|
||||
Members: pb.Members,
|
||||
Source: "Project",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Thread-safe store per-project
|
||||
m.mu.Lock()
|
||||
m.ProjectBuckets[projectID] = buckets
|
||||
@@ -305,7 +324,7 @@ func (m *BucketsModule) writeHierarchicalOutput(ctx context.Context, logger inte
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing hierarchical output: %v", err), globals.GCP_STORAGE_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,7 +376,7 @@ func (m *BucketsModule) writeFlatOutput(ctx context.Context, logger internal.Log
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), globals.GCP_STORAGE_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,6 +396,7 @@ func (m *BucketsModule) getTableHeader() []string {
|
||||
"IAM Binding Role",
|
||||
"Principal Type",
|
||||
"IAM Binding Principal",
|
||||
"Binding Source",
|
||||
"Principal Attack Paths",
|
||||
}
|
||||
}
|
||||
@@ -454,6 +474,7 @@ func (m *BucketsModule) bucketsToTableBody(buckets []CloudStorageService.BucketI
|
||||
binding.Role,
|
||||
memberType,
|
||||
member,
|
||||
shared.BindingSource(binding.Source),
|
||||
attackPaths,
|
||||
})
|
||||
}
|
||||
@@ -475,6 +496,7 @@ func (m *BucketsModule) bucketsToTableBody(buckets []CloudStorageService.BucketI
|
||||
"-",
|
||||
"-",
|
||||
"-",
|
||||
"-",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ func (m *CertManagerModule) processProject(ctx context.Context, projectID string
|
||||
// Get Certificate Manager certs
|
||||
certs, err := svc.GetCertificates(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_CERTMANAGER_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate certificates in project %s", projectID))
|
||||
}
|
||||
@@ -175,7 +175,7 @@ func (m *CertManagerModule) processProject(ctx context.Context, projectID string
|
||||
// Get classic SSL certs
|
||||
sslCerts, err := svc.GetSSLCertificates(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_CERTMANAGER_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate SSL certificates in project %s", projectID))
|
||||
}
|
||||
@@ -183,7 +183,7 @@ func (m *CertManagerModule) processProject(ctx context.Context, projectID string
|
||||
// Get certificate maps
|
||||
certMaps, err := svc.GetCertificateMaps(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_CERTMANAGER_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate certificate maps in project %s", projectID))
|
||||
}
|
||||
@@ -377,6 +377,6 @@ func (m *CertManagerModule) writeFlatOutput(ctx context.Context, logger internal
|
||||
"project", m.ProjectIDs, scopeNames, m.Account, output)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), globals.GCP_CERTMANAGER_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
@@ -133,7 +133,7 @@ func (m *CloudArmorModule) processProject(ctx context.Context, projectID string,
|
||||
// Get security policies
|
||||
policies, err := svc.GetSecurityPolicies(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_CLOUDARMOR_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate Cloud Armor security policies in project %s", projectID))
|
||||
}
|
||||
@@ -141,7 +141,7 @@ func (m *CloudArmorModule) processProject(ctx context.Context, projectID string,
|
||||
// Get unprotected LBs
|
||||
unprotectedLBs, err := svc.GetUnprotectedLoadBalancers(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_CLOUDARMOR_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate unprotected load balancers in project %s", projectID))
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ func (m *CloudBuildModule) processProject(ctx context.Context, projectID string,
|
||||
// Get triggers
|
||||
triggers, err := cbSvc.ListTriggers(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_CLOUDBUILD_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate Cloud Build triggers in project %s", projectID))
|
||||
}
|
||||
@@ -134,7 +134,7 @@ func (m *CloudBuildModule) processProject(ctx context.Context, projectID string,
|
||||
// Get recent builds
|
||||
builds, err := cbSvc.ListBuilds(projectID, 20)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_CLOUDBUILD_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate Cloud Build builds in project %s", projectID))
|
||||
}
|
||||
@@ -518,6 +518,6 @@ func (m *CloudBuildModule) writeFlatOutput(ctx context.Context, logger internal.
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), globals.GCP_CLOUDBUILD_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"sync"
|
||||
|
||||
CloudRunService "github.com/BishopFox/cloudfox/gcp/services/cloudrunService"
|
||||
IAMService "github.com/BishopFox/cloudfox/gcp/services/iamService"
|
||||
"github.com/BishopFox/cloudfox/gcp/shared"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
gcpinternal "github.com/BishopFox/cloudfox/internal/gcp"
|
||||
@@ -173,13 +175,34 @@ func (m *CloudRunModule) processProject(ctx context.Context, projectID string, l
|
||||
}
|
||||
m.mu.Unlock()
|
||||
|
||||
// Fetch project-level IAM for inherited binding injection
|
||||
iamSvc := IAMService.New()
|
||||
projectPolicies, _ := iamSvc.Policies(projectID, "project")
|
||||
|
||||
// Get services
|
||||
services, err := cs.Services(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_CLOUDRUN_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate Cloud Run services in project %s", projectID))
|
||||
} else {
|
||||
// Inject inherited project-level IAM bindings into services
|
||||
if projectPolicies != nil {
|
||||
for i := range services {
|
||||
for _, pb := range projectPolicies {
|
||||
if !shared.IsInheritedRole("cloudrun", pb.Role) {
|
||||
continue
|
||||
}
|
||||
for _, member := range pb.Members {
|
||||
services[i].IAMBindings = append(services[i].IAMBindings, CloudRunService.IAMBinding{
|
||||
Role: pb.Role,
|
||||
Member: member,
|
||||
Source: "Project",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m.mu.Lock()
|
||||
m.ProjectServices[projectID] = services
|
||||
for _, svc := range services {
|
||||
@@ -191,10 +214,27 @@ func (m *CloudRunModule) processProject(ctx context.Context, projectID string, l
|
||||
// Get jobs
|
||||
jobs, err := cs.Jobs(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_CLOUDRUN_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate Cloud Run jobs in project %s", projectID))
|
||||
} else {
|
||||
// Inject inherited project-level IAM bindings into jobs
|
||||
if projectPolicies != nil {
|
||||
for i := range jobs {
|
||||
for _, pb := range projectPolicies {
|
||||
if !shared.IsInheritedRole("cloudrun", pb.Role) {
|
||||
continue
|
||||
}
|
||||
for _, member := range pb.Members {
|
||||
jobs[i].IAMBindings = append(jobs[i].IAMBindings, CloudRunService.IAMBinding{
|
||||
Role: pb.Role,
|
||||
Member: member,
|
||||
Source: "Project",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m.mu.Lock()
|
||||
m.ProjectJobs[projectID] = jobs
|
||||
for _, job := range jobs {
|
||||
@@ -391,7 +431,7 @@ func (m *CloudRunModule) writeHierarchicalOutput(ctx context.Context, logger int
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing hierarchical output: %v", err), globals.GCP_CLOUDRUN_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,7 +476,7 @@ func (m *CloudRunModule) writeFlatOutput(ctx context.Context, logger internal.Lo
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), globals.GCP_CLOUDRUN_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,7 +494,7 @@ func (m *CloudRunModule) buildTablesForProject(projectID string, services []Clou
|
||||
servicesHeader := []string{
|
||||
"Project", "Type", "Name", "Region", "Status", "URL", "Ingress", "Public",
|
||||
"Service Account", "SA Attack Paths", "Default SA", "Image", "VPC Access",
|
||||
"Min/Max", "IAM Binding Role", "IAM Binding Principal",
|
||||
"Min/Max", "IAM Binding Role", "IAM Binding Principal", "Binding Source",
|
||||
}
|
||||
|
||||
var servicesBody [][]string
|
||||
@@ -497,6 +537,7 @@ func (m *CloudRunModule) buildTablesForProject(projectID string, services []Clou
|
||||
formatIngress(svc.IngressSettings), publicStatus, svc.ServiceAccount,
|
||||
attackPaths, defaultSA, svc.ContainerImage, vpcAccess, scaling,
|
||||
binding.Role, binding.Member,
|
||||
shared.BindingSource(binding.Source),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
@@ -505,7 +546,7 @@ func (m *CloudRunModule) buildTablesForProject(projectID string, services []Clou
|
||||
m.GetProjectName(svc.ProjectID), "Service", svc.Name, svc.Region, status, svc.URL,
|
||||
formatIngress(svc.IngressSettings), publicStatus, svc.ServiceAccount,
|
||||
attackPaths, defaultSA, svc.ContainerImage, vpcAccess, scaling,
|
||||
"-", "-",
|
||||
"-", "-", "-",
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -522,7 +563,7 @@ func (m *CloudRunModule) buildTablesForProject(projectID string, services []Clou
|
||||
jobsHeader := []string{
|
||||
"Project", "Type", "Name", "Region", "Status", "Service Account", "SA Attack Paths", "Default SA",
|
||||
"Image", "VPC Access", "Tasks", "Parallelism", "Last Execution",
|
||||
"IAM Binding Role", "IAM Binding Principal",
|
||||
"IAM Binding Role", "IAM Binding Principal", "Binding Source",
|
||||
}
|
||||
|
||||
var jobsBody [][]string
|
||||
@@ -565,6 +606,7 @@ func (m *CloudRunModule) buildTablesForProject(projectID string, services []Clou
|
||||
job.ServiceAccount, jobAttackPaths, defaultSA, job.ContainerImage, vpcAccess,
|
||||
fmt.Sprintf("%d", job.TaskCount), fmt.Sprintf("%d", job.Parallelism),
|
||||
lastExec, binding.Role, binding.Member,
|
||||
shared.BindingSource(binding.Source),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
@@ -573,7 +615,7 @@ func (m *CloudRunModule) buildTablesForProject(projectID string, services []Clou
|
||||
m.GetProjectName(job.ProjectID), "Job", job.Name, job.Region, status,
|
||||
job.ServiceAccount, jobAttackPaths, defaultSA, job.ContainerImage, vpcAccess,
|
||||
fmt.Sprintf("%d", job.TaskCount), fmt.Sprintf("%d", job.Parallelism),
|
||||
lastExec, "-", "-",
|
||||
lastExec, "-", "-", "-",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ func (m *CloudSQLModule) processProject(ctx context.Context, projectID string, l
|
||||
cs := CloudSQLService.New()
|
||||
instances, err := cs.Instances(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_CLOUDSQL_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate Cloud SQL in project %s", projectID))
|
||||
return
|
||||
@@ -403,7 +403,7 @@ func (m *CloudSQLModule) writeHierarchicalOutput(ctx context.Context, logger int
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing hierarchical output: %v", err), globals.GCP_CLOUDSQL_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,7 +453,7 @@ func (m *CloudSQLModule) writeFlatOutput(ctx context.Context, logger internal.Lo
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), globals.GCP_CLOUDSQL_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1852,7 +1852,7 @@ func (m *ComplianceDashboardModule) writeHierarchicalOutput(ctx context.Context,
|
||||
err := internal.HandleHierarchicalOutputSmart("gcp", m.Format, m.Verbosity, m.WrapTable, pathBuilder, outputData)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing hierarchical output: %v", err), GCP_COMPLIANCEDASHBOARD_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1979,7 +1979,7 @@ func (m *ComplianceDashboardModule) writeFlatOutput(ctx context.Context, logger
|
||||
output,
|
||||
)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, GCP_COMPLIANCEDASHBOARD_MODULE_NAME,
|
||||
"Could not write output")
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ func (m *ComposerModule) processProject(ctx context.Context, projectID string, l
|
||||
svc := composerservice.New()
|
||||
environments, err := svc.ListEnvironments(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_COMPOSER_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate Composer environments in project %s", projectID))
|
||||
return
|
||||
|
||||
@@ -259,7 +259,7 @@ func (m *CostSecurityModule) analyzeComputeInstances(ctx context.Context, projec
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, GCP_COSTSECURITY_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate compute instances in project %s", projectID))
|
||||
}
|
||||
@@ -525,7 +525,7 @@ func (m *CostSecurityModule) findOrphanedDisks(ctx context.Context, projectID st
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, GCP_COSTSECURITY_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate disks in project %s", projectID))
|
||||
}
|
||||
@@ -583,7 +583,7 @@ func (m *CostSecurityModule) findOrphanedIPs(ctx context.Context, projectID stri
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, GCP_COSTSECURITY_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate addresses in project %s", projectID))
|
||||
}
|
||||
@@ -592,7 +592,7 @@ func (m *CostSecurityModule) findOrphanedIPs(ctx context.Context, projectID stri
|
||||
func (m *CostSecurityModule) analyzeSQLInstances(ctx context.Context, projectID string, sqlService *sqladmin.Service, logger internal.Logger) {
|
||||
instances, err := sqlService.Instances.List(projectID).Do()
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, GCP_COSTSECURITY_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate SQL instances in project %s", projectID))
|
||||
return
|
||||
@@ -642,7 +642,7 @@ func (m *CostSecurityModule) analyzeSQLInstances(ctx context.Context, projectID
|
||||
func (m *CostSecurityModule) analyzeStorageBuckets(ctx context.Context, projectID string, storageService *storage.Service, logger internal.Logger) {
|
||||
buckets, err := storageService.Buckets.List(projectID).Do()
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, GCP_COSTSECURITY_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate storage buckets in project %s", projectID))
|
||||
return
|
||||
@@ -1011,7 +1011,7 @@ func (m *CostSecurityModule) writeHierarchicalOutput(ctx context.Context, logger
|
||||
err := internal.HandleHierarchicalOutputSmart("gcp", m.Format, m.Verbosity, m.WrapTable, pathBuilder, outputData)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing hierarchical output: %v", err), GCP_COSTSECURITY_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1166,6 +1166,6 @@ func (m *CostSecurityModule) writeFlatOutput(ctx context.Context, logger interna
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), GCP_COSTSECURITY_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
@@ -142,7 +142,7 @@ func (m *CrossProjectModule) Execute(ctx context.Context, logger internal.Logger
|
||||
// Analyze cross-project bindings
|
||||
bindings, err := svc.AnalyzeCrossProjectAccess(m.ProjectIDs, m.OrgCache)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_CROSSPROJECT_MODULE_NAME,
|
||||
"Could not analyze cross-project access")
|
||||
} else {
|
||||
@@ -152,7 +152,7 @@ func (m *CrossProjectModule) Execute(ctx context.Context, logger internal.Logger
|
||||
// Get cross-project service accounts
|
||||
sas, err := svc.GetCrossProjectServiceAccounts(m.ProjectIDs)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_CROSSPROJECT_MODULE_NAME,
|
||||
"Could not get cross-project service accounts")
|
||||
} else {
|
||||
@@ -162,7 +162,7 @@ func (m *CrossProjectModule) Execute(ctx context.Context, logger internal.Logger
|
||||
// Find lateral movement paths
|
||||
paths, err := svc.FindLateralMovementPaths(m.ProjectIDs, m.OrgCache)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_CROSSPROJECT_MODULE_NAME,
|
||||
"Could not find lateral movement paths")
|
||||
} else {
|
||||
@@ -172,7 +172,7 @@ func (m *CrossProjectModule) Execute(ctx context.Context, logger internal.Logger
|
||||
// Find cross-project logging sinks
|
||||
sinks, err := svc.FindCrossProjectLoggingSinks(m.ProjectIDs)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_CROSSPROJECT_MODULE_NAME,
|
||||
"Could not find cross-project logging sinks")
|
||||
} else {
|
||||
@@ -182,7 +182,7 @@ func (m *CrossProjectModule) Execute(ctx context.Context, logger internal.Logger
|
||||
// Find cross-project Pub/Sub exports
|
||||
pubsubExports, err := svc.FindCrossProjectPubSubExports(m.ProjectIDs)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_CROSSPROJECT_MODULE_NAME,
|
||||
"Could not find cross-project Pub/Sub exports")
|
||||
} else {
|
||||
@@ -606,7 +606,7 @@ func (m *CrossProjectModule) writeHierarchicalOutput(ctx context.Context, logger
|
||||
err := internal.HandleHierarchicalOutputSmart("gcp", m.Format, m.Verbosity, m.WrapTable, pathBuilder, outputData)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing hierarchical output: %v", err), globals.GCP_CROSSPROJECT_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -656,7 +656,7 @@ func (m *CrossProjectModule) writeFlatOutput(ctx context.Context, logger interna
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output for project %s: %v", targetProject, err), globals.GCP_CROSSPROJECT_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -703,7 +703,7 @@ func (m *DataExfiltrationModule) processProject(ctx context.Context, projectID s
|
||||
func (m *DataExfiltrationModule) findPublicSnapshots(ctx context.Context, projectID string, logger internal.Logger) {
|
||||
computeService, err := compute.NewService(ctx)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, GCP_DATAEXFILTRATION_MODULE_NAME,
|
||||
fmt.Sprintf("Could not create Compute service in project %s", projectID))
|
||||
return
|
||||
@@ -848,7 +848,7 @@ func (m *DataExfiltrationModule) findPublicImages(ctx context.Context, projectID
|
||||
func (m *DataExfiltrationModule) findPublicBuckets(ctx context.Context, projectID string, logger internal.Logger) {
|
||||
storageService, err := storage.NewService(ctx)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, GCP_DATAEXFILTRATION_MODULE_NAME,
|
||||
fmt.Sprintf("Could not create Storage service in project %s", projectID))
|
||||
return
|
||||
@@ -1705,6 +1705,6 @@ func (m *DataExfiltrationModule) writeFlatOutput(ctx context.Context, logger int
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), GCP_DATAEXFILTRATION_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
@@ -103,7 +103,7 @@ func (m *DataflowModule) processProject(ctx context.Context, projectID string, l
|
||||
svc := dataflowservice.New()
|
||||
jobs, err := svc.ListJobs(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_DATAFLOW_MODULE_NAME,
|
||||
fmt.Sprintf("Could not list Dataflow jobs in project %s", projectID))
|
||||
return
|
||||
|
||||
@@ -104,7 +104,7 @@ func (m *DataprocModule) processProject(ctx context.Context, projectID string, l
|
||||
|
||||
clusters, err := svc.ListClusters(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_DATAPROC_MODULE_NAME,
|
||||
fmt.Sprintf("Could not list Dataproc clusters in project %s", projectID))
|
||||
return
|
||||
|
||||
+4
-4
@@ -177,7 +177,7 @@ func (m *DNSModule) processProject(ctx context.Context, projectID string, logger
|
||||
// Get zones
|
||||
zones, err := ds.Zones(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_DNS_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate DNS zones in project %s", projectID))
|
||||
return
|
||||
@@ -204,7 +204,7 @@ func (m *DNSModule) processProject(ctx context.Context, projectID string, logger
|
||||
// Get records for each zone (outside of lock to avoid holding mutex across API call)
|
||||
records, err := ds.Records(projectID, zone.Name)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_DNS_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate DNS records in zone %s", zone.Name))
|
||||
continue
|
||||
@@ -486,7 +486,7 @@ func (m *DNSModule) writeHierarchicalOutput(ctx context.Context, logger internal
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing hierarchical output: %v", err), globals.GCP_DNS_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -545,6 +545,6 @@ func (m *DNSModule) writeFlatOutput(ctx context.Context, logger internal.Logger)
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), globals.GCP_DNS_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
@@ -144,7 +144,7 @@ func (m *DomainWideDelegationModule) processProject(ctx context.Context, project
|
||||
svc := domainwidedelegationservice.New()
|
||||
accounts, err := svc.GetDWDServiceAccounts(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_DOMAINWIDEDELEGATION_MODULE_NAME,
|
||||
fmt.Sprintf("Could not check DWD service accounts in project %s", projectID))
|
||||
return
|
||||
@@ -753,6 +753,6 @@ func (m *DomainWideDelegationModule) writeFlatOutput(ctx context.Context, logger
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), globals.GCP_DOMAINWIDEDELEGATION_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
@@ -167,7 +167,7 @@ func (m *EndpointsModule) processProject(ctx context.Context, projectID string,
|
||||
|
||||
computeService, err := compute.NewService(ctx)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, "endpoints",
|
||||
fmt.Sprintf("Could not create Compute service in project %s", projectID))
|
||||
} else {
|
||||
@@ -1532,6 +1532,6 @@ func (m *EndpointsModule) writeFlatOutput(ctx context.Context, logger internal.L
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), "endpoints")
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ func (m *FilestoreModule) processProject(ctx context.Context, projectID string,
|
||||
svc := filestoreservice.New()
|
||||
instances, err := svc.ListInstances(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_FILESTORE_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate Filestore instances in project %s", projectID))
|
||||
return
|
||||
|
||||
@@ -187,7 +187,7 @@ func (m *FirewallModule) processProject(ctx context.Context, projectID string, l
|
||||
var err error
|
||||
networks, err = ns.Networks(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_FIREWALL_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate networks in project %s", projectID))
|
||||
}
|
||||
@@ -195,7 +195,7 @@ func (m *FirewallModule) processProject(ctx context.Context, projectID string, l
|
||||
// Get subnets
|
||||
subnets, err = ns.Subnets(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_FIREWALL_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate subnets in project %s", projectID))
|
||||
}
|
||||
@@ -203,7 +203,7 @@ func (m *FirewallModule) processProject(ctx context.Context, projectID string, l
|
||||
// Get firewall rules
|
||||
rules, err = ns.FirewallRulesEnhanced(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_FIREWALL_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate firewall rules in project %s", projectID))
|
||||
}
|
||||
@@ -530,7 +530,7 @@ func (m *FirewallModule) writeHierarchicalOutput(ctx context.Context, logger int
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing hierarchical output: %v", err), globals.GCP_FIREWALL_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,7 +576,7 @@ func (m *FirewallModule) writeFlatOutput(ctx context.Context, logger internal.Lo
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), globals.GCP_FIREWALL_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/BishopFox/cloudfox/gcp/shared"
|
||||
|
||||
FunctionsService "github.com/BishopFox/cloudfox/gcp/services/functionsService"
|
||||
IAMService "github.com/BishopFox/cloudfox/gcp/services/iamService"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
gcpinternal "github.com/BishopFox/cloudfox/internal/gcp"
|
||||
@@ -148,12 +149,32 @@ func (m *FunctionsModule) processProject(ctx context.Context, projectID string,
|
||||
fs := FunctionsService.New()
|
||||
functions, err := fs.Functions(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_FUNCTIONS_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate functions in project %s", projectID))
|
||||
return
|
||||
}
|
||||
|
||||
// Inject inherited project-level IAM bindings into each function
|
||||
iamSvc := IAMService.New()
|
||||
projectPolicies, projErr := iamSvc.Policies(projectID, "project")
|
||||
if projErr == nil {
|
||||
for i := range functions {
|
||||
for _, pb := range projectPolicies {
|
||||
if !shared.IsInheritedRole("function", pb.Role) {
|
||||
continue
|
||||
}
|
||||
for _, member := range pb.Members {
|
||||
functions[i].IAMBindings = append(functions[i].IAMBindings, FunctionsService.IAMBinding{
|
||||
Role: pb.Role,
|
||||
Member: member,
|
||||
Source: "Project",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Thread-safe store per-project
|
||||
m.mu.Lock()
|
||||
m.ProjectFunctions[projectID] = functions
|
||||
@@ -329,7 +350,7 @@ func (m *FunctionsModule) writeHierarchicalOutput(ctx context.Context, logger in
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing hierarchical output: %v", err), globals.GCP_FUNCTIONS_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,7 +393,7 @@ func (m *FunctionsModule) writeFlatOutput(ctx context.Context, logger internal.L
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), globals.GCP_FUNCTIONS_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -495,6 +516,7 @@ func (m *FunctionsModule) getTableHeader() []string {
|
||||
"VPC Access",
|
||||
"IAM Binding Role",
|
||||
"IAM Binding Principal",
|
||||
"Binding Source",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -572,6 +594,7 @@ func (m *FunctionsModule) functionsToTableBody(functions []FunctionsService.Func
|
||||
vpcAccess,
|
||||
binding.Role,
|
||||
binding.Member,
|
||||
shared.BindingSource(binding.Source),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
@@ -592,6 +615,7 @@ func (m *FunctionsModule) functionsToTableBody(functions []FunctionsService.Func
|
||||
vpcAccess,
|
||||
"-",
|
||||
"-",
|
||||
"-",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -166,7 +166,7 @@ func (m *GKEModule) processProject(ctx context.Context, projectID string, logger
|
||||
gs := GKEService.New()
|
||||
clusters, nodePools, err := gs.Clusters(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_GKE_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate GKE clusters in project %s", projectID))
|
||||
return
|
||||
@@ -295,7 +295,7 @@ func (m *GKEModule) writeHierarchicalOutput(ctx context.Context, logger internal
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing hierarchical output: %v", err), globals.GCP_GKE_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,7 +340,7 @@ func (m *GKEModule) writeFlatOutput(ctx context.Context, logger internal.Logger)
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), globals.GCP_GKE_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -290,6 +290,7 @@ func (m *HiddenAdminsModule) analyzeOrganizationIAM(ctx context.Context, logger
|
||||
|
||||
policy, err := orgsClient.GetIamPolicy(ctx, &iampb.GetIamPolicyRequest{
|
||||
Resource: org.Name,
|
||||
Options: &iampb.GetPolicyOptions{RequestedPolicyVersion: 3},
|
||||
})
|
||||
if err != nil {
|
||||
continue
|
||||
@@ -332,6 +333,7 @@ func (m *HiddenAdminsModule) analyzeFolderIAM(ctx context.Context, logger intern
|
||||
|
||||
policy, err := foldersClient.GetIamPolicy(ctx, &iampb.GetIamPolicyRequest{
|
||||
Resource: folder.Name,
|
||||
Options: &iampb.GetPolicyOptions{RequestedPolicyVersion: 3},
|
||||
})
|
||||
if err != nil {
|
||||
continue
|
||||
@@ -352,7 +354,9 @@ func (m *HiddenAdminsModule) analyzeProjectIAM(ctx context.Context, logger inter
|
||||
return
|
||||
}
|
||||
|
||||
policy, err := crmService.Projects.GetIamPolicy(projectID, &crmv1.GetIamPolicyRequest{}).Do()
|
||||
policy, err := crmService.Projects.GetIamPolicy(projectID, &crmv1.GetIamPolicyRequest{
|
||||
Options: &crmv1.GetPolicyOptions{RequestedPolicyVersion: 3},
|
||||
}).Do()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
+256
-20
@@ -7,6 +7,7 @@ import (
|
||||
"sync"
|
||||
|
||||
IAMService "github.com/BishopFox/cloudfox/gcp/services/iamService"
|
||||
resourceiamservice "github.com/BishopFox/cloudfox/gcp/services/resourceIAMService"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
gcpinternal "github.com/BishopFox/cloudfox/internal/gcp"
|
||||
@@ -78,6 +79,28 @@ var highPrivilegeRoles = map[string]bool{
|
||||
"roles/orgpolicy.policyAdmin": true,
|
||||
}
|
||||
|
||||
// adminRoles are roles that grant broad administrative control. Used as a
|
||||
// fallback for the Admin column when FoxMapper data is not available.
|
||||
var adminRoles = map[string]bool{
|
||||
"roles/owner": true,
|
||||
"roles/editor": true,
|
||||
"roles/resourcemanager.projectIamAdmin": true,
|
||||
"roles/resourcemanager.folderAdmin": true,
|
||||
"roles/resourcemanager.folderIamAdmin": true,
|
||||
"roles/resourcemanager.organizationAdmin": true,
|
||||
"roles/iam.securityAdmin": true,
|
||||
"roles/iam.roleAdmin": true,
|
||||
}
|
||||
|
||||
// isAdminRole returns "Yes" if the role is a known admin role, "" otherwise.
|
||||
// Used as a fallback when FoxMapper is not available.
|
||||
func isAdminRole(role string) string {
|
||||
if adminRoles[role] {
|
||||
return "Yes"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// Module Struct with embedded BaseGCPModule
|
||||
// ------------------------------
|
||||
@@ -85,11 +108,12 @@ type IAMModule struct {
|
||||
gcpinternal.BaseGCPModule
|
||||
|
||||
// Module-specific fields - using enhanced data
|
||||
ScopeBindings []IAMService.ScopeBinding
|
||||
ServiceAccounts []IAMService.ServiceAccountInfo
|
||||
CustomRoles []IAMService.CustomRole
|
||||
Groups []IAMService.GroupInfo
|
||||
MFAStatus map[string]*IAMService.MFAStatus
|
||||
ScopeBindings []IAMService.ScopeBinding
|
||||
ServiceAccounts []IAMService.ServiceAccountInfo
|
||||
CustomRoles []IAMService.CustomRole
|
||||
Groups []IAMService.GroupInfo
|
||||
MFAStatus map[string]*IAMService.MFAStatus
|
||||
ResourceBindings []resourceiamservice.ResourceIAMBinding
|
||||
|
||||
// Per-scope loot for inheritance-aware output
|
||||
OrgLoot map[string]*internal.LootFile // orgID -> loot commands
|
||||
@@ -132,11 +156,12 @@ func runGCPIAMCommand(cmd *cobra.Command, args []string) {
|
||||
// Create module instance
|
||||
module := &IAMModule{
|
||||
BaseGCPModule: gcpinternal.NewBaseGCPModule(cmdCtx),
|
||||
ScopeBindings: []IAMService.ScopeBinding{},
|
||||
ServiceAccounts: []IAMService.ServiceAccountInfo{},
|
||||
CustomRoles: []IAMService.CustomRole{},
|
||||
Groups: []IAMService.GroupInfo{},
|
||||
MFAStatus: make(map[string]*IAMService.MFAStatus),
|
||||
ScopeBindings: []IAMService.ScopeBinding{},
|
||||
ServiceAccounts: []IAMService.ServiceAccountInfo{},
|
||||
CustomRoles: []IAMService.CustomRole{},
|
||||
Groups: []IAMService.GroupInfo{},
|
||||
MFAStatus: make(map[string]*IAMService.MFAStatus),
|
||||
ResourceBindings: []resourceiamservice.ResourceIAMBinding{},
|
||||
OrgLoot: make(map[string]*internal.LootFile),
|
||||
FolderLoot: make(map[string]*internal.LootFile),
|
||||
ProjectLoot: make(map[string]*internal.LootFile),
|
||||
@@ -171,7 +196,7 @@ func (m *IAMModule) Execute(ctx context.Context, logger internal.Logger) {
|
||||
iamService := IAMService.New()
|
||||
iamData, err := iamService.CombinedIAMEnhanced(ctx, m.ProjectIDs, m.ProjectNames)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_IAM_MODULE_NAME, "Failed to enumerate IAM")
|
||||
return
|
||||
}
|
||||
@@ -182,6 +207,16 @@ func (m *IAMModule) Execute(ctx context.Context, logger internal.Logger) {
|
||||
m.Groups = iamData.Groups
|
||||
m.MFAStatus = iamData.MFAStatus
|
||||
|
||||
// Enumerate resource-level IAM bindings (SA, buckets, secrets, functions, etc.)
|
||||
resourceIAMSvc := resourceiamservice.New()
|
||||
for _, projectID := range m.ProjectIDs {
|
||||
resourceBindings, err := resourceIAMSvc.GetAllResourceIAM(ctx, projectID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
m.ResourceBindings = append(m.ResourceBindings, resourceBindings...)
|
||||
}
|
||||
|
||||
// Try to enumerate group memberships to build reverse lookup
|
||||
enrichedGroups := iamService.GetGroupMemberships(ctx, m.Groups)
|
||||
m.Groups = enrichedGroups
|
||||
@@ -220,8 +255,9 @@ func (m *IAMModule) Execute(ctx context.Context, logger internal.Logger) {
|
||||
}
|
||||
}
|
||||
|
||||
logger.SuccessM(fmt.Sprintf("Found %d binding(s) across %d org(s), %d folder(s), %d project(s); %d SA(s), %d custom role(s), %d group(s)",
|
||||
logger.SuccessM(fmt.Sprintf("Found %d binding(s) across %d org(s), %d folder(s), %d project(s); %d resource-level binding(s); %d SA(s), %d custom role(s), %d group(s)",
|
||||
len(m.ScopeBindings), orgCount, folderCount, projectCount,
|
||||
len(m.ResourceBindings),
|
||||
len(m.ServiceAccounts), len(m.CustomRoles), len(m.Groups)), globals.GCP_IAM_MODULE_NAME)
|
||||
|
||||
// Write output
|
||||
@@ -631,9 +667,11 @@ func (m *IAMModule) buildTables() []internal.TableFile {
|
||||
|
||||
// Add scope bindings (one row per binding)
|
||||
for _, sb := range m.ScopeBindings {
|
||||
// Check admin status from FoxMapper only - shows Org/Folder/Project or No
|
||||
// This is different from "high privilege roles" - Admin means broad IAM control
|
||||
// Check admin status: FoxMapper (granular) > role-based fallback
|
||||
adminStatus := gcpinternal.GetAdminStatusFromCache(m.FoxMapperCache, sb.MemberEmail)
|
||||
if adminStatus == "" {
|
||||
adminStatus = isAdminRole(sb.Role)
|
||||
}
|
||||
if adminStatus == "" {
|
||||
adminStatus = "No"
|
||||
}
|
||||
@@ -698,6 +736,91 @@ func (m *IAMModule) buildTables() []internal.TableFile {
|
||||
})
|
||||
}
|
||||
|
||||
// Add resource-level IAM bindings (one row per binding)
|
||||
for _, rb := range m.ResourceBindings {
|
||||
memberType := resourceiamservice.DetermineMemberType(rb.Member)
|
||||
memberEmail := rb.MemberEmail
|
||||
|
||||
// Check admin status: FoxMapper (granular) > role-based fallback
|
||||
adminStatus := gcpinternal.GetAdminStatusFromCache(m.FoxMapperCache, memberEmail)
|
||||
if adminStatus == "" {
|
||||
adminStatus = isAdminRole(rb.Role)
|
||||
}
|
||||
if adminStatus == "" {
|
||||
adminStatus = "No"
|
||||
}
|
||||
|
||||
isCustom := "No"
|
||||
if strings.HasPrefix(rb.Role, "projects/") || strings.HasPrefix(rb.Role, "organizations/") {
|
||||
isCustom = "Yes"
|
||||
}
|
||||
|
||||
condition := "No"
|
||||
if rb.HasCondition && rb.ConditionTitle != "" {
|
||||
condition = rb.ConditionTitle
|
||||
} else if rb.HasCondition {
|
||||
condition = "Yes"
|
||||
}
|
||||
|
||||
// Get MFA status
|
||||
mfa := "-"
|
||||
if memberType == "User" {
|
||||
if status, ok := m.MFAStatus[memberEmail]; ok {
|
||||
if status.Error != "" {
|
||||
mfa = "Unknown"
|
||||
} else if status.HasMFA {
|
||||
mfa = "Yes"
|
||||
} else {
|
||||
mfa = "No"
|
||||
}
|
||||
}
|
||||
} else if memberType == "ServiceAccount" {
|
||||
mfa = "N/A"
|
||||
}
|
||||
|
||||
// Get groups this member belongs to
|
||||
groups := "-"
|
||||
if memberGroups, ok := m.MemberToGroups[memberEmail]; ok && len(memberGroups) > 0 {
|
||||
groups = strings.Join(memberGroups, ", ")
|
||||
}
|
||||
|
||||
// Check for federated identity
|
||||
federated := formatFederatedInfo(parseFederatedIdentity(memberEmail))
|
||||
|
||||
// Check attack paths for service account principals
|
||||
attackPaths := "-"
|
||||
if memberType == "ServiceAccount" {
|
||||
attackPaths = gcpinternal.GetAttackSummaryFromCaches(m.FoxMapperCache, nil, memberEmail)
|
||||
}
|
||||
|
||||
body = append(body, []string{
|
||||
rb.ResourceType,
|
||||
rb.ResourceID,
|
||||
rb.ResourceName,
|
||||
memberType,
|
||||
memberEmail,
|
||||
rb.Role,
|
||||
adminStatus,
|
||||
isCustom,
|
||||
"-",
|
||||
condition,
|
||||
mfa,
|
||||
groups,
|
||||
federated,
|
||||
attackPaths,
|
||||
})
|
||||
}
|
||||
|
||||
// Build SA email -> roles lookup from scope bindings for admin fallback.
|
||||
// ServiceAccountsBasic() doesn't populate the Roles field, so we
|
||||
// extract roles from the already-enumerated bindings.
|
||||
saRolesMap := make(map[string][]string)
|
||||
for _, sb := range m.ScopeBindings {
|
||||
if sb.MemberType == "ServiceAccount" {
|
||||
saRolesMap[sb.MemberEmail] = append(saRolesMap[sb.MemberEmail], sb.Role)
|
||||
}
|
||||
}
|
||||
|
||||
// Add service accounts
|
||||
for _, sa := range m.ServiceAccounts {
|
||||
hasKeys := "No"
|
||||
@@ -716,8 +839,16 @@ func (m *IAMModule) buildTables() []internal.TableFile {
|
||||
groups = strings.Join(memberGroups, ", ")
|
||||
}
|
||||
|
||||
// Check admin status from FoxMapper
|
||||
// Check admin status: FoxMapper (granular) > role-based fallback
|
||||
adminStatus := gcpinternal.GetAdminStatusFromCache(m.FoxMapperCache, sa.Email)
|
||||
if adminStatus == "" {
|
||||
for _, role := range saRolesMap[sa.Email] {
|
||||
if adminRoles[role] {
|
||||
adminStatus = "Yes"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if adminStatus == "" {
|
||||
adminStatus = "No"
|
||||
}
|
||||
@@ -964,7 +1095,7 @@ func (m *IAMModule) writeHierarchicalOutput(ctx context.Context, logger internal
|
||||
err := internal.HandleHierarchicalOutputSmart("gcp", m.Format, m.Verbosity, m.WrapTable, pathBuilder, outputData)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing hierarchical output: %v", err), globals.GCP_IAM_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -998,6 +1129,12 @@ func (m *IAMModule) writeHierarchicalOutputTee(ctx context.Context, logger inter
|
||||
}
|
||||
}
|
||||
|
||||
// Build reverse lookup: for each resource ID, which project it belongs to
|
||||
resourceToProject := make(map[string]string)
|
||||
for _, rb := range m.ResourceBindings {
|
||||
resourceToProject[rb.ResourceID] = rb.ProjectID
|
||||
}
|
||||
|
||||
// Create a row router that routes based on scope type and OrgCache
|
||||
rowRouter := func(row []string) []string {
|
||||
// Row format: [ScopeType, ScopeID, ScopeName, ...]
|
||||
@@ -1021,6 +1158,11 @@ func (m *IAMModule) writeHierarchicalOutputTee(ctx context.Context, logger inter
|
||||
}
|
||||
return m.ProjectIDs
|
||||
default:
|
||||
// Resource-level binding (serviceAccount, bucket, secret, etc.)
|
||||
// Route to the project that owns this resource
|
||||
if projectID, ok := resourceToProject[scopeID]; ok {
|
||||
return []string{projectID}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1041,7 +1183,7 @@ func (m *IAMModule) writeHierarchicalOutputTee(ctx context.Context, logger inter
|
||||
err := internal.HandleHierarchicalOutputTee(config)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing tee streaming output: %v", err), globals.GCP_IAM_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1099,8 +1241,11 @@ func (m *IAMModule) buildTablesForProject(projectID string) []internal.TableFile
|
||||
continue
|
||||
}
|
||||
|
||||
// Check admin status from FoxMapper only - shows Org/Folder/Project or No
|
||||
// Check admin status: FoxMapper (granular) > role-based fallback
|
||||
adminStatus := gcpinternal.GetAdminStatusFromCache(m.FoxMapperCache, sb.MemberEmail)
|
||||
if adminStatus == "" {
|
||||
adminStatus = isAdminRole(sb.Role)
|
||||
}
|
||||
if adminStatus == "" {
|
||||
adminStatus = "No"
|
||||
}
|
||||
@@ -1161,6 +1306,89 @@ func (m *IAMModule) buildTablesForProject(projectID string) []internal.TableFile
|
||||
})
|
||||
}
|
||||
|
||||
// Add resource-level IAM bindings for this project
|
||||
for _, rb := range m.ResourceBindings {
|
||||
if rb.ProjectID != projectID {
|
||||
continue
|
||||
}
|
||||
|
||||
memberType := resourceiamservice.DetermineMemberType(rb.Member)
|
||||
memberEmail := rb.MemberEmail
|
||||
|
||||
// Check admin status: FoxMapper (granular) > role-based fallback
|
||||
adminStatus := gcpinternal.GetAdminStatusFromCache(m.FoxMapperCache, memberEmail)
|
||||
if adminStatus == "" {
|
||||
adminStatus = isAdminRole(rb.Role)
|
||||
}
|
||||
if adminStatus == "" {
|
||||
adminStatus = "No"
|
||||
}
|
||||
|
||||
isCustom := "No"
|
||||
if strings.HasPrefix(rb.Role, "projects/") || strings.HasPrefix(rb.Role, "organizations/") {
|
||||
isCustom = "Yes"
|
||||
}
|
||||
|
||||
condition := "No"
|
||||
if rb.HasCondition && rb.ConditionTitle != "" {
|
||||
condition = rb.ConditionTitle
|
||||
} else if rb.HasCondition {
|
||||
condition = "Yes"
|
||||
}
|
||||
|
||||
mfa := "-"
|
||||
if memberType == "User" {
|
||||
if status, ok := m.MFAStatus[memberEmail]; ok {
|
||||
if status.Error != "" {
|
||||
mfa = "Unknown"
|
||||
} else if status.HasMFA {
|
||||
mfa = "Yes"
|
||||
} else {
|
||||
mfa = "No"
|
||||
}
|
||||
}
|
||||
} else if memberType == "ServiceAccount" {
|
||||
mfa = "N/A"
|
||||
}
|
||||
|
||||
groups := "-"
|
||||
if memberGroups, ok := m.MemberToGroups[memberEmail]; ok && len(memberGroups) > 0 {
|
||||
groups = strings.Join(memberGroups, ", ")
|
||||
}
|
||||
|
||||
federated := formatFederatedInfo(parseFederatedIdentity(memberEmail))
|
||||
|
||||
attackPaths := "-"
|
||||
if memberType == "ServiceAccount" {
|
||||
attackPaths = gcpinternal.GetAttackSummaryFromCaches(m.FoxMapperCache, nil, memberEmail)
|
||||
}
|
||||
|
||||
body = append(body, []string{
|
||||
rb.ResourceType,
|
||||
rb.ResourceID,
|
||||
rb.ResourceName,
|
||||
memberType,
|
||||
memberEmail,
|
||||
rb.Role,
|
||||
adminStatus,
|
||||
isCustom,
|
||||
"-",
|
||||
condition,
|
||||
mfa,
|
||||
groups,
|
||||
federated,
|
||||
attackPaths,
|
||||
})
|
||||
}
|
||||
|
||||
// Build SA email -> roles lookup from scope bindings for admin fallback
|
||||
saRolesMap := make(map[string][]string)
|
||||
for _, sb := range m.ScopeBindings {
|
||||
if sb.MemberType == "ServiceAccount" {
|
||||
saRolesMap[sb.MemberEmail] = append(saRolesMap[sb.MemberEmail], sb.Role)
|
||||
}
|
||||
}
|
||||
|
||||
// Add service accounts for this project only
|
||||
for _, sa := range m.ServiceAccounts {
|
||||
if sa.ProjectID != projectID {
|
||||
@@ -1182,8 +1410,16 @@ func (m *IAMModule) buildTablesForProject(projectID string) []internal.TableFile
|
||||
groups = strings.Join(memberGroups, ", ")
|
||||
}
|
||||
|
||||
// Check admin status from FoxMapper
|
||||
// Check admin status: FoxMapper (granular) > role-based fallback
|
||||
adminStatus := gcpinternal.GetAdminStatusFromCache(m.FoxMapperCache, sa.Email)
|
||||
if adminStatus == "" {
|
||||
for _, role := range saRolesMap[sa.Email] {
|
||||
if adminRoles[role] {
|
||||
adminStatus = "Yes"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if adminStatus == "" {
|
||||
adminStatus = "No"
|
||||
}
|
||||
@@ -1330,6 +1566,6 @@ func (m *IAMModule) writeFlatOutput(ctx context.Context, logger internal.Logger)
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), globals.GCP_IAM_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -99,7 +99,7 @@ func (m *IAPModule) processProject(ctx context.Context, projectID string, logger
|
||||
// Get tunnel destination groups
|
||||
groups, err := svc.ListTunnelDestGroups(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_IAP_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate IAP tunnel groups in project %s", projectID))
|
||||
} else {
|
||||
|
||||
@@ -376,7 +376,7 @@ func (m *IdentityFederationModule) writeHierarchicalOutput(ctx context.Context,
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing hierarchical output: %v", err), globals.GCP_IDENTITY_FEDERATION_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,7 +420,7 @@ func (m *IdentityFederationModule) writeFlatOutput(ctx context.Context, logger i
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), globals.GCP_IDENTITY_FEDERATION_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,6 +448,7 @@ func (m *IdentityFederationModule) buildTables(
|
||||
"Display Name",
|
||||
"State",
|
||||
"Disabled",
|
||||
"Full Resource Path",
|
||||
}
|
||||
|
||||
var poolsBody [][]string
|
||||
@@ -462,6 +463,7 @@ func (m *IdentityFederationModule) buildTables(
|
||||
pool.DisplayName,
|
||||
pool.State,
|
||||
disabled,
|
||||
pool.Name,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -482,6 +484,7 @@ func (m *IdentityFederationModule) buildTables(
|
||||
"OIDC Issuer / AWS Account",
|
||||
"Trust Scope",
|
||||
"Access Condition",
|
||||
"Full Resource Path",
|
||||
}
|
||||
|
||||
var providersBody [][]string
|
||||
@@ -508,6 +511,7 @@ func (m *IdentityFederationModule) buildTables(
|
||||
issuerOrAccount,
|
||||
trustScope,
|
||||
attrCond,
|
||||
p.Name,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+62
-15
@@ -9,6 +9,8 @@ import (
|
||||
"sync"
|
||||
|
||||
ComputeEngineService "github.com/BishopFox/cloudfox/gcp/services/computeEngineService"
|
||||
IAMService "github.com/BishopFox/cloudfox/gcp/services/iamService"
|
||||
orgpolicyservice "github.com/BishopFox/cloudfox/gcp/services/orgpolicyService"
|
||||
"github.com/BishopFox/cloudfox/gcp/shared"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
@@ -39,6 +41,7 @@ Security Columns:
|
||||
- BroadScopes: Has cloud-platform or other broad OAuth scopes
|
||||
- OSLogin: OS Login enabled (recommended for access control)
|
||||
- OSLogin2FA: OS Login with 2FA required
|
||||
- OSLoginPolicy: Whether compute.requireOsLogin org policy is enforced (inherited)
|
||||
- BlockProjKeys: Instance blocks project-wide SSH keys
|
||||
- SerialPort: Serial port access enabled (security risk if exposed)
|
||||
- CanIPForward: Can forward packets (potential for lateral movement)
|
||||
@@ -55,11 +58,12 @@ type InstancesModule struct {
|
||||
gcpinternal.BaseGCPModule
|
||||
|
||||
// Module-specific fields - per-project for hierarchical output
|
||||
ProjectInstances map[string][]ComputeEngineService.ComputeEngineInfo // projectID -> instances
|
||||
ProjectMetadata map[string]*ComputeEngineService.ProjectMetadataInfo // projectID -> metadata
|
||||
LootMap map[string]map[string]*internal.LootFile // projectID -> loot files
|
||||
FoxMapperCache *gcpinternal.FoxMapperCache // FoxMapper graph data (preferred)
|
||||
mu sync.Mutex
|
||||
ProjectInstances map[string][]ComputeEngineService.ComputeEngineInfo // projectID -> instances
|
||||
ProjectMetadata map[string]*ComputeEngineService.ProjectMetadataInfo // projectID -> metadata
|
||||
ProjectOSLoginPolicy map[string]string // projectID -> "Enforced", "Not Enforced", "Unknown"
|
||||
LootMap map[string]map[string]*internal.LootFile // projectID -> loot files
|
||||
FoxMapperCache *gcpinternal.FoxMapperCache // FoxMapper graph data (preferred)
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
@@ -85,10 +89,11 @@ func runGCPInstancesCommand(cmd *cobra.Command, args []string) {
|
||||
|
||||
// Create module instance
|
||||
module := &InstancesModule{
|
||||
BaseGCPModule: gcpinternal.NewBaseGCPModule(cmdCtx),
|
||||
ProjectInstances: make(map[string][]ComputeEngineService.ComputeEngineInfo),
|
||||
ProjectMetadata: make(map[string]*ComputeEngineService.ProjectMetadataInfo),
|
||||
LootMap: make(map[string]map[string]*internal.LootFile),
|
||||
BaseGCPModule: gcpinternal.NewBaseGCPModule(cmdCtx),
|
||||
ProjectInstances: make(map[string][]ComputeEngineService.ComputeEngineInfo),
|
||||
ProjectMetadata: make(map[string]*ComputeEngineService.ProjectMetadataInfo),
|
||||
ProjectOSLoginPolicy: make(map[string]string),
|
||||
LootMap: make(map[string]map[string]*internal.LootFile),
|
||||
}
|
||||
|
||||
// Execute enumeration
|
||||
@@ -142,16 +147,47 @@ func (m *InstancesModule) processProject(ctx context.Context, projectID string,
|
||||
ces := ComputeEngineService.New()
|
||||
instances, projectMeta, err := ces.InstancesWithMetadata(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_INSTANCES_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate instances in project %s", projectID))
|
||||
return
|
||||
}
|
||||
|
||||
// Inject inherited project-level IAM bindings into each instance
|
||||
iamSvc := IAMService.New()
|
||||
projectPolicies, projErr := iamSvc.Policies(projectID, "project")
|
||||
if projErr == nil {
|
||||
for i := range instances {
|
||||
for _, pb := range projectPolicies {
|
||||
if !shared.IsInheritedRole("instance", pb.Role) {
|
||||
continue
|
||||
}
|
||||
for _, member := range pb.Members {
|
||||
instances[i].IAMBindings = append(instances[i].IAMBindings, ComputeEngineService.IAMBinding{
|
||||
Role: pb.Role,
|
||||
Member: member,
|
||||
Source: "Project",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check effective compute.requireOsLogin org policy for this project
|
||||
orgPolicySvc := orgpolicyservice.New()
|
||||
effectivePolicy, policyErr := orgPolicySvc.GetEffectivePolicy("project", projectID, "compute.requireOsLogin")
|
||||
osLoginPolicy := "Unknown"
|
||||
if policyErr == nil && effectivePolicy != nil && effectivePolicy.Enforced {
|
||||
osLoginPolicy = "Enforced"
|
||||
} else if policyErr == nil {
|
||||
osLoginPolicy = "Not Enforced"
|
||||
}
|
||||
|
||||
// Thread-safe store per-project
|
||||
m.mu.Lock()
|
||||
m.ProjectInstances[projectID] = instances
|
||||
m.ProjectMetadata[projectID] = projectMeta
|
||||
m.ProjectOSLoginPolicy[projectID] = osLoginPolicy
|
||||
|
||||
// Initialize loot for this project
|
||||
if m.LootMap[projectID] == nil {
|
||||
@@ -582,7 +618,7 @@ func (m *InstancesModule) writeHierarchicalOutput(ctx context.Context, logger in
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing hierarchical output: %v", err), globals.GCP_INSTANCES_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -663,7 +699,7 @@ func (m *InstancesModule) writeFlatOutput(ctx context.Context, logger internal.L
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), globals.GCP_INSTANCES_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -672,7 +708,7 @@ func (m *InstancesModule) writeFlatOutput(ctx context.Context, logger internal.L
|
||||
// - Identity: Project, Name, Type, Zone, State, Machine Type
|
||||
// - Network: External IP, Internal IP, IP Forward
|
||||
// - Service Account: Service Account, SA Attack Paths, Scopes, Default SA, Broad Scopes
|
||||
// - Access Control: OS Login, OS Login 2FA, Block Proj Keys, Serial Port
|
||||
// - Access Control: OS Login, OS Login 2FA, OS Login Policy, Block Proj Keys, Serial Port
|
||||
// - Protection: Delete Protect, Last Snapshot
|
||||
// - Hardware Security: Shielded VM, Secure Boot, vTPM, Integrity, Confidential
|
||||
// - Disk Encryption: Encryption, KMS Key
|
||||
@@ -699,6 +735,7 @@ func (m *InstancesModule) getInstancesTableHeader() []string {
|
||||
// Access Control
|
||||
"OS Login",
|
||||
"OS Login 2FA",
|
||||
"OS Login Policy",
|
||||
"Block Proj Keys",
|
||||
"Serial Port",
|
||||
// Protection
|
||||
@@ -716,6 +753,7 @@ func (m *InstancesModule) getInstancesTableHeader() []string {
|
||||
// IAM
|
||||
"IAM Binding Role",
|
||||
"IAM Binding Principal",
|
||||
"Binding Source",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -885,6 +923,12 @@ func (m *InstancesModule) instancesToTableBody(instances []ComputeEngineService.
|
||||
kmsKey = "-"
|
||||
}
|
||||
|
||||
// OS Login Policy display
|
||||
osLoginPolicy := "Unknown"
|
||||
if val, ok := m.ProjectOSLoginPolicy[instance.ProjectID]; ok {
|
||||
osLoginPolicy = val
|
||||
}
|
||||
|
||||
// Instance type for contextual display
|
||||
instType := instance.InstanceType
|
||||
if instType == "" {
|
||||
@@ -916,6 +960,7 @@ func (m *InstancesModule) instancesToTableBody(instances []ComputeEngineService.
|
||||
// Access Control
|
||||
shared.BoolToYesNo(instance.OSLoginEnabled),
|
||||
shared.BoolToYesNo(instance.OSLogin2FAEnabled),
|
||||
osLoginPolicy,
|
||||
shared.BoolToYesNo(instance.BlockProjectSSHKeys),
|
||||
shared.BoolToYesNo(instance.SerialPortEnabled),
|
||||
// Protection - Delete protection is NOT expected for managed instances (they're ephemeral)
|
||||
@@ -936,18 +981,20 @@ func (m *InstancesModule) instancesToTableBody(instances []ComputeEngineService.
|
||||
// If instance has IAM bindings, create one row per binding
|
||||
if len(instance.IAMBindings) > 0 {
|
||||
for _, binding := range instance.IAMBindings {
|
||||
row := make([]string, len(baseRow)+2)
|
||||
row := make([]string, len(baseRow)+3)
|
||||
copy(row, baseRow)
|
||||
row[len(baseRow)] = binding.Role
|
||||
row[len(baseRow)+1] = binding.Member
|
||||
row[len(baseRow)+2] = shared.BindingSource(binding.Source)
|
||||
body = append(body, row)
|
||||
}
|
||||
} else {
|
||||
// No IAM bindings - single row
|
||||
row := make([]string, len(baseRow)+2)
|
||||
row := make([]string, len(baseRow)+3)
|
||||
copy(row, baseRow)
|
||||
row[len(baseRow)] = "-"
|
||||
row[len(baseRow)+1] = "-"
|
||||
row[len(baseRow)+2] = "-"
|
||||
body = append(body, row)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,7 +523,7 @@ func (m *KeysModule) writeHierarchicalOutput(ctx context.Context, logger interna
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing hierarchical output: %v", err), globals.GCP_KEYS_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -569,6 +569,6 @@ func (m *KeysModule) writeFlatOutput(ctx context.Context, logger internal.Logger
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), globals.GCP_KEYS_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
+28
-4
@@ -7,6 +7,7 @@ import (
|
||||
"sync"
|
||||
|
||||
KMSService "github.com/BishopFox/cloudfox/gcp/services/kmsService"
|
||||
IAMService "github.com/BishopFox/cloudfox/gcp/services/iamService"
|
||||
"github.com/BishopFox/cloudfox/gcp/shared"
|
||||
"github.com/BishopFox/cloudfox/globals"
|
||||
"github.com/BishopFox/cloudfox/internal"
|
||||
@@ -170,7 +171,7 @@ func (m *KMSModule) processProject(ctx context.Context, projectID string, logger
|
||||
// Get key rings
|
||||
keyRings, err := ks.KeyRings(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_KMS_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate KMS key rings in project %s", projectID))
|
||||
return
|
||||
@@ -179,11 +180,31 @@ func (m *KMSModule) processProject(ctx context.Context, projectID string, logger
|
||||
// Get crypto keys
|
||||
keys, err := ks.CryptoKeys(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_KMS_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate KMS keys in project %s", projectID))
|
||||
}
|
||||
|
||||
// Inject inherited project-level IAM bindings into each key
|
||||
iamSvc := IAMService.New()
|
||||
projectPolicies, projErr := iamSvc.Policies(projectID, "project")
|
||||
if projErr == nil {
|
||||
for i := range keys {
|
||||
for _, pb := range projectPolicies {
|
||||
if !shared.IsInheritedRole("cryptoKey", pb.Role) {
|
||||
continue
|
||||
}
|
||||
for _, member := range pb.Members {
|
||||
keys[i].IAMBindings = append(keys[i].IAMBindings, KMSService.IAMBinding{
|
||||
Role: pb.Role,
|
||||
Member: member,
|
||||
Source: "Project",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Thread-safe store per-project
|
||||
m.mu.Lock()
|
||||
m.ProjectKeyRings[projectID] = keyRings
|
||||
@@ -295,6 +316,7 @@ func (m *KMSModule) getKeysHeader() []string {
|
||||
"Public Decrypt",
|
||||
"IAM Binding Role",
|
||||
"IAM Binding Principal",
|
||||
"Binding Source",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,6 +363,7 @@ func (m *KMSModule) keysToTableBody(keys []KMSService.CryptoKeyInfo) [][]string
|
||||
shared.BoolToYesNo(key.IsPublicDecrypt),
|
||||
binding.Role,
|
||||
binding.Member,
|
||||
shared.BindingSource(binding.Source),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
@@ -359,6 +382,7 @@ func (m *KMSModule) keysToTableBody(keys []KMSService.CryptoKeyInfo) [][]string
|
||||
shared.BoolToYesNo(key.IsPublicDecrypt),
|
||||
"-",
|
||||
"-",
|
||||
"-",
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -449,7 +473,7 @@ func (m *KMSModule) writeHierarchicalOutput(ctx context.Context, logger internal
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing hierarchical output: %v", err), globals.GCP_KMS_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,7 +536,7 @@ func (m *KMSModule) writeFlatOutput(ctx context.Context, logger internal.Logger)
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), globals.GCP_KMS_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -399,7 +399,7 @@ func (m *LateralMovementModule) findImpersonationChains(ctx context.Context, pro
|
||||
// Get all service accounts (without keys - not needed for impersonation analysis)
|
||||
serviceAccounts, err := iamService.ServiceAccountsBasic(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, GCP_LATERALMOVEMENT_MODULE_NAME,
|
||||
fmt.Sprintf("Could not get service accounts in project %s", projectID))
|
||||
return
|
||||
@@ -1009,6 +1009,6 @@ func (m *LateralMovementModule) writeFlatOutput(ctx context.Context, logger inte
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), GCP_LATERALMOVEMENT_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
@@ -133,7 +133,7 @@ func (m *LoadBalancersModule) processProject(ctx context.Context, projectID stri
|
||||
// Get load balancers
|
||||
lbs, err := svc.ListLoadBalancers(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_LOADBALANCERS_MODULE_NAME,
|
||||
fmt.Sprintf("Could not list load balancers in project %s", projectID))
|
||||
} else {
|
||||
@@ -148,7 +148,7 @@ func (m *LoadBalancersModule) processProject(ctx context.Context, projectID stri
|
||||
// Get SSL policies
|
||||
sslPolicies, err := svc.ListSSLPolicies(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_LOADBALANCERS_MODULE_NAME,
|
||||
fmt.Sprintf("Could not list SSL policies in project %s", projectID))
|
||||
} else {
|
||||
@@ -160,7 +160,7 @@ func (m *LoadBalancersModule) processProject(ctx context.Context, projectID stri
|
||||
// Get backend services
|
||||
backends, err := svc.ListBackendServices(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_LOADBALANCERS_MODULE_NAME,
|
||||
fmt.Sprintf("Could not list backend services in project %s", projectID))
|
||||
} else {
|
||||
|
||||
@@ -130,7 +130,7 @@ func (m *LogEnumModule) processProject(ctx context.Context, projectID string, lo
|
||||
|
||||
entries, err := svc.EnumerateSensitiveLogs(projectID, m.Hours, m.MaxEntries, m.LogNameFilter)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_LOGGINGENUM_MODULE_NAME,
|
||||
fmt.Sprintf("Could not scan logs in project %s", projectID))
|
||||
return
|
||||
|
||||
@@ -175,7 +175,7 @@ func (m *LoggingModule) processProject(ctx context.Context, projectID string, lo
|
||||
// Get sinks
|
||||
sinks, err := ls.Sinks(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_LOGGING_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate logging sinks in project %s", projectID))
|
||||
} else {
|
||||
@@ -185,7 +185,7 @@ func (m *LoggingModule) processProject(ctx context.Context, projectID string, lo
|
||||
// Get metrics
|
||||
metrics, err := ls.Metrics(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_LOGGING_MODULE_NAME,
|
||||
fmt.Sprintf("Could not enumerate log metrics in project %s", projectID))
|
||||
} else {
|
||||
@@ -545,7 +545,7 @@ func (m *LoggingModule) writeHierarchicalOutput(ctx context.Context, logger inte
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing hierarchical output: %v", err), globals.GCP_LOGGING_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,7 +616,7 @@ func (m *LoggingModule) writeFlatOutput(ctx context.Context, logger internal.Log
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorM(fmt.Sprintf("Error writing output: %v", err), globals.GCP_LOGGING_MODULE_NAME)
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ func (m *MemorystoreModule) processProject(ctx context.Context, projectID string
|
||||
svc := memorystoreservice.New()
|
||||
instances, err := svc.ListRedisInstances(projectID)
|
||||
if err != nil {
|
||||
m.CommandCounter.IncrError()
|
||||
m.CommandCounter.Error++
|
||||
gcpinternal.HandleGCPError(err, logger, globals.GCP_MEMORYSTORE_MODULE_NAME,
|
||||
fmt.Sprintf("Could not list Redis instances in project %s", projectID))
|
||||
return
|
||||
|
||||
Loaded 100 of 188 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user