├── .github ├── CODEOWNERS ├── images │ ├── cloudfox-output-p1.png │ ├── cloudfox-output-p2.png │ └── cloudfox-output.png └── workflows │ ├── autorelease.yml │ └── codespell.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── aws ├── access-keys.go ├── api-gws.go ├── api-gws_test.go ├── buckets.go ├── buckets_test.go ├── cape-tui.go ├── cape.go ├── client-initializers.go ├── cloudformation.go ├── cloudformation_test.go ├── codebuild.go ├── codebuild_test.go ├── databases.go ├── databases_test.go ├── directory-services.go ├── ecr.go ├── ecr_test.go ├── ecs-tasks.go ├── ecs-tasks_test.go ├── eks.go ├── eks_test.go ├── elastic-network-interfaces.go ├── elastic-network-interfaces_test.go ├── endpoints.go ├── env-vars.go ├── filesystems.go ├── graph.go ├── graph │ ├── collectors │ │ └── accounts.go │ └── ingester │ │ ├── ingestor.go │ │ └── schema │ │ ├── models │ │ ├── account.go │ │ ├── constants.go │ │ ├── org.go │ │ ├── resource.go │ │ ├── roles.go │ │ └── users.go │ │ └── schema.go ├── iam-simulator.go ├── instances.go ├── inventory.go ├── lambda.go ├── network-ports.go ├── org.go ├── outbound-assumed-roles.go ├── permissions.go ├── pmapper.go ├── principals.go ├── ram.go ├── resource-trusts.go ├── resource-trusts_test.go ├── role-trusts.go ├── route53.go ├── sdk │ ├── apigateway.go │ ├── apigateway_mocks.go │ ├── apigatewayv2.go │ ├── apigatewayv2_mocks.go │ ├── apprunner.go │ ├── apprunner_mocks.go │ ├── athena.go │ ├── athena_mocks.go │ ├── cloud9.go │ ├── cloud9_mocks.go │ ├── cloudformation.go │ ├── cloudformation_mocks.go │ ├── cloudfront.go │ ├── cloudfront_mocks.go │ ├── codeartifact.go │ ├── codeartifact_mocks.go │ ├── codebuild.go │ ├── codebuild_mocks.go │ ├── codecommit.go │ ├── codecommit_mocks.go │ ├── codedeploy.go │ ├── codedeploy_mocks.go │ ├── datapipeline.go │ ├── datapipeline_mocks.go │ ├── docdb.go │ ├── docsdb_mocks.go │ ├── ds.go │ ├── dynamodb.go │ ├── dynamodb_mocks.go │ ├── ec2.go │ ├── ec2_mocks.go │ ├── ecr.go │ ├── ecr_mocks.go │ ├── ecs.go │ ├── ecs_mocks.go │ ├── efs.go │ ├── efs_mocks.go │ ├── eks.go │ ├── eks_mocks.go │ ├── elasticache.go │ ├── elasticache_mocks.go │ ├── elasticbeanstalk.go │ ├── elasticbeanstalk_mocks.go │ ├── elb.go │ ├── elb_mocks.go │ ├── elbv2.go │ ├── elbv2_mocks.go │ ├── emr.go │ ├── emr_mocks.go │ ├── glue.go │ ├── glue_mocks.go │ ├── grafana.go │ ├── grafana_mocks.go │ ├── iam.go │ ├── iam_mocks.go │ ├── kinesis.go │ ├── kinesis_mocks.go │ ├── lambda.go │ ├── lambda_mocks.go │ ├── lightsail.go │ ├── lightsail_mocks.go │ ├── mq.go │ ├── mq_mocks.go │ ├── opensearch.go │ ├── opensearch_mocks.go │ ├── org.go │ ├── org_mocks.go │ ├── rds.go │ ├── rds_mocks.go │ ├── redshift.go │ ├── redshift_mocks.go │ ├── route53.go │ ├── route53_mocks.go │ ├── s3.go │ ├── s3_mocks.go │ ├── secretsmanager.go │ ├── secretsmanager_mocks.go │ ├── shared.go │ ├── sns.go │ ├── sns_mocks.go │ ├── sqs.go │ ├── sqs_mocks.go │ ├── ssm.go │ ├── ssm_mocks.go │ ├── stepfunctions.go │ └── stepfunctions_mocks.go ├── secrets.go ├── shared.go ├── sns.go ├── sns_test.go ├── sqs.go ├── sqs_test.go ├── tags.go ├── tags_test.go ├── test-data │ ├── cloudformation-describestacks.json │ ├── cloudformation-getTemplate.json │ ├── describe-network-interfaces.json │ └── describe-tasks.json └── workloads.go ├── azure ├── inventory.go ├── inventory_test.go ├── rbac.go ├── rbac_test.go ├── shared.go ├── storage.go ├── storage_test.go ├── test-data │ ├── nics.json │ ├── public-ips.json │ ├── resources.json │ ├── role-assignments.json │ ├── role-definitions.json │ ├── storage-accounts.json │ ├── storage-blobs.json │ ├── users.json │ └── vms.json ├── vms.go ├── vms_test.go ├── whoami.go └── whoami_test.go ├── cli ├── aws.go ├── azure.go └── gcp.go ├── gcp ├── commands │ ├── artifact-registry.go │ ├── bigquery.go │ ├── buckets.go │ ├── iam.go │ ├── instances.go │ ├── secrets.go │ └── whoami.go └── services │ ├── artifactRegistryService │ ├── artifactRegistryService.go │ ├── artifactRegistryService_test.go │ └── models.go │ ├── bigqueryService │ ├── bigqueryService.go │ └── bigqueryService_test.go │ ├── cloudStorageService │ ├── cloudStorageService.go │ └── cloudStorageService_test.go │ ├── computeEngineService │ ├── computeEngineService.go │ └── computeEngineService_test.go │ ├── iamService │ ├── access-tokens.go │ ├── iamService.go │ └── iamService_test.go │ ├── models │ └── models.go │ ├── networkService │ ├── networkService.go │ └── networkService_test.go │ ├── oauthService │ └── oauthService.go │ └── secretsService │ ├── secretsService.go │ └── secretsService_test.go ├── globals ├── azure.go ├── gcp.go └── utils.go ├── go.mod ├── go.sum ├── internal ├── array.go ├── aws.go ├── aws │ └── policy │ │ ├── condition.go │ │ ├── condition_test.go │ │ ├── policy.go │ │ ├── policy_test.go │ │ ├── principal.go │ │ ├── role-trust-policies.go │ │ ├── statement.go │ │ └── testdata │ │ ├── amazon-ec2-full-access.json │ │ ├── sns-conditionally-public.json │ │ ├── sns-shared-via-condition.json │ │ ├── sns-shared-with-org.json │ │ ├── sqs-conditionally-public.json │ │ ├── sqs-public.json │ │ ├── sqs-shared-with-condition.json │ │ └── sqs-shared.json ├── aws_test.go ├── azure.go ├── azure_test.go ├── cache.go ├── common │ └── common.go ├── log.go ├── log_test.go ├── output.go ├── output2.go ├── output2_test.go └── output_test.go ├── main.go └── misc └── aws └── cloudfox-policy.json /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @bishopfaure @dbravo-bishopfox 2 | -------------------------------------------------------------------------------- /.github/images/cloudfox-output-p1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BishopFox/cloudfox/2cb5ad2b8356dc906e1a4bb1ee317de7a40b8c3a/.github/images/cloudfox-output-p1.png -------------------------------------------------------------------------------- /.github/images/cloudfox-output-p2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BishopFox/cloudfox/2cb5ad2b8356dc906e1a4bb1ee317de7a40b8c3a/.github/images/cloudfox-output-p2.png -------------------------------------------------------------------------------- /.github/images/cloudfox-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BishopFox/cloudfox/2cb5ad2b8356dc906e1a4bb1ee317de7a40b8c3a/.github/images/cloudfox-output.png -------------------------------------------------------------------------------- /.github/workflows/autorelease.yml: -------------------------------------------------------------------------------- 1 | name: Create Releases 2 | on: 3 | push: 4 | tags: v[0-9]+.[0-9]+.[0-9]+ 5 | branches: main 6 | 7 | workflow_dispatch: 8 | 9 | jobs: 10 | 11 | tagged-release: 12 | name: "Tagged Release" 13 | if: startsWith( github.ref, 'refs/tags/v') 14 | runs-on: "ubuntu-latest" 15 | 16 | steps: 17 | 18 | - name: Go 1.21 19 | uses: actions/setup-go@v4 20 | with: 21 | go-version: ^1.20 22 | id: go 23 | 24 | - id: install-secret-key 25 | name: GPG Secret Key(s) 26 | run: | 27 | cat <(echo -e "${{ secrets.CLOUDFOX_GPG }}") | gpg --batch --import 28 | gpg --list-secret-keys --keyid-format LONG 29 | 30 | - name: Check Out Code 31 | uses: actions/checkout@v4 32 | 33 | - name: Git Fetch Tags 34 | run: git fetch --prune --unshallow --tags -f 35 | 36 | - name: Make binaries 37 | working-directory: . 38 | run: make release 39 | 40 | 41 | - name: Release binaries 42 | uses: "marvinpinto/action-automatic-releases@latest" 43 | with: 44 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 45 | prerelease: false 46 | files: | 47 | ./cloudfox/* 48 | -------------------------------------------------------------------------------- /.github/workflows/codespell.yml: -------------------------------------------------------------------------------- 1 | # This Action uses minimal steps to run in ~5 seconds to rapidly: 2 | # Looks for typos in the codebase using codespell 3 | # https://github.com/codespell-project/codespell#readme 4 | name: codespell 5 | on: 6 | push: 7 | branches: [main] 8 | pull_request: 9 | branches: [main] 10 | jobs: 11 | codespell: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - run: pip install --user codespell 16 | - run: codespell --ignore-words-list="aks" --skip="*.sum" 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Go: 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | .vscode 18 | .idea 19 | launch.json 20 | 21 | ###################################################### 22 | 23 | # Terraform: 24 | # Local .terraform directories 25 | **/.terraform/* 26 | 27 | # .tfstate files 28 | *.tfstate 29 | *.tfstate.* 30 | 31 | # Crash log files 32 | crash.log 33 | 34 | # Exclude all .tfvars files, which are likely to contain sentitive data, such as 35 | # password, private keys, and other secrets. These should not be part of version 36 | # control as they are data points which are potentially sensitive and subject 37 | # to change depending on the environment. 38 | # 39 | *.tfvars 40 | 41 | # Ignore override files as they are usually used to override resources locally and so 42 | # are not checked in 43 | override.tf 44 | override.tf.json 45 | *_override.tf 46 | *_override.tf.json 47 | 48 | # Include override files you do wish to add to version control using negated pattern 49 | # 50 | # !example_override.tf 51 | 52 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 53 | # example: *tfplan* 54 | 55 | # Ignore CLI configuration files 56 | .terraformrc 57 | terraform.rc 58 | 59 | # Other 60 | .terraform 61 | .terraform.lock.hcl 62 | .DS_Store 63 | 64 | untracked/* 65 | output/* 66 | *cloudfox-output* 67 | cloudfox 68 | *.log 69 | *.bak 70 | *.txt 71 | *.json 72 | *.csv 73 | *.log 74 | dist/ 75 | 76 | # graphvis files 77 | *.gv 78 | *.svg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Bishop Fox 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | run: 2 | go run main.go 3 | 4 | windows: 5 | GOOS=windows GOARCH=386 go build 6 | sha1sum cloudfox.exe > sha1sum-win.txt 7 | zip cloudfox-win.zip cloudfox.exe sha1sum-win.txt 8 | rm cloudfox.exe sha1sum-win.txt 9 | 10 | linux: 11 | GOOS=linux GOARCH=386 go build 12 | sha1sum cloudfox > sha1sum-linux.txt 13 | zip cloudfox-linux.zip cloudfox sha1sum-linux.txt 14 | rm cloudfox sha1sum-linux.txt 15 | 16 | linux-arm64: 17 | GOOS=linux GOARCH=arm64 go build 18 | sha1sum cloudfox > sha1sum-linux-arm64.txt 19 | zip cloudfox-linux-arm64.zip cloudfox sha1sum-linux-arm64.txt 20 | rm cloudfox sha1sum-linux-arm64.txt 21 | 22 | macos: 23 | GOOS=darwin GOARCH=amd64 go build 24 | sha1sum cloudfox > sha1sum-mac.txt 25 | zip cloudfox-mac.zip cloudfox sha1sum-mac.txt 26 | rm cloudfox sha1sum-mac.txt 27 | 28 | all: windows linux macos 29 | 30 | .PHONY: release 31 | release: clean 32 | mkdir -p ./cloudfox 33 | 34 | GOOS=windows GOARCH=amd64 go build -o ./cloudfox/cloudfox.exe . 35 | zip ./cloudfox/cloudfox-windows-amd64.zip ./cloudfox/cloudfox.exe 36 | rm -rf ./cloudfox/cloudfox.exe 37 | 38 | GOOS=linux GOARCH=amd64 go build -o ./cloudfox/cloudfox . 39 | zip ./cloudfox/cloudfox-linux-amd64.zip ./cloudfox/cloudfox . 40 | rm -rf ./cloudfox/cloudfox 41 | 42 | GOOS=linux GOARCH=386 go build -o ./cloudfox/cloudfox . 43 | zip ./cloudfox/cloudfox-linux-386.zip ./cloudfox/cloudfox . 44 | rm -rf ./cloudfox/cloudfox 45 | 46 | GOOS=linux GOARCH=arm64 go build -o ./cloudfox/cloudfox . 47 | zip ./cloudfox/cloudfox-linux-arm64.zip ./cloudfox/cloudfox . 48 | rm -rf ./cloudfox/cloudfox 49 | 50 | GOOS=darwin GOARCH=amd64 go build -o ./cloudfox/cloudfox . 51 | zip ./cloudfox/cloudfox-macos-amd64.zip ./cloudfox/cloudfox 52 | rm -rf ./cloudfox/cloudfox 53 | 54 | GOOS=darwin GOARCH=arm64 go build -o ./cloudfox/cloudfox . 55 | zip ./cloudfox/cloudfox-macos-arm64.zip ./cloudfox/cloudfox 56 | rm -rf ./cloudfox/cloudfox 57 | 58 | clean: 59 | rm -rf ./cloudfox 60 | -------------------------------------------------------------------------------- /aws/api-gws_test.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "path/filepath" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/BishopFox/cloudfox/aws/sdk" 9 | "github.com/BishopFox/cloudfox/internal" 10 | "github.com/aws/aws-sdk-go-v2/aws" 11 | "github.com/aws/aws-sdk-go-v2/service/sts" 12 | "github.com/aws/smithy-go/ptr" 13 | "github.com/spf13/afero" 14 | ) 15 | 16 | func TestApiGw(t *testing.T) { 17 | 18 | m := ApiGwModule{ 19 | AWSProfile: "unittesting", 20 | AWSRegions: []string{"us-east-1"}, 21 | Caller: sts.GetCallerIdentityOutput{ 22 | Arn: aws.String("arn:aws:iam::123456789012:user/Alice"), 23 | Account: aws.String("123456789012"), 24 | }, 25 | Goroutines: 3, 26 | WrapTable: false, 27 | APIGatewayClient: &sdk.MockedAWSAPIGatewayClient{}, 28 | APIGatewayv2Client: &sdk.MockedAWSAPIGatewayv2Client{}, 29 | } 30 | 31 | fs := internal.MockFileSystem(true) 32 | defer internal.MockFileSystem(false) 33 | //tmpDir := "~/.cloudfox/" 34 | tmpDir := ptr.ToString(internal.GetLogDirPath()) 35 | 36 | m.PrintApiGws(tmpDir, 2) 37 | 38 | resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/unittesting-123456789012/table/api-gw.txt") 39 | resultsFile, err := afero.ReadFile(fs, resultsFilePath) 40 | if err != nil { 41 | t.Fatalf("Cannot read output file at %s: %s", resultsFilePath, err) 42 | } 43 | //print the results file to the screen 44 | //fmt.Println(string(resultsFile)) 45 | 46 | // I want a test that runs the main function and checks the output to see if the following items are in the output: "https://qwerty.execute-api.us-east-1.amazonaws.com/stage1/path1", "https://asdfsdfasdf.execute-api.us-east-1.amazonaws.com/stage1/route2" 47 | 48 | expectedResults := []string{ 49 | "https://qwerty.execute-api.us-east-1.amazonaws.com/stage1/path1", 50 | "https://asdfsdfasdf.execute-api.us-east-1.amazonaws.com/stage1/route2", 51 | "https://asdfsdfasdf.execute-api.us-east-1.amazonaws.com/stage2/route1", 52 | "23oieuwefo3rfs", 53 | } 54 | 55 | for _, expected := range expectedResults { 56 | if !strings.Contains(string(resultsFile), expected) { 57 | t.Errorf("Expected %s to be in the results file", expected) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /aws/cloudformation_test.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | 7 | "github.com/BishopFox/cloudfox/aws/sdk" 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/aws" 10 | "github.com/aws/aws-sdk-go-v2/service/sts" 11 | ) 12 | 13 | func TestCloudFormation(t *testing.T) { 14 | subtests := []struct { 15 | name string 16 | outputDirectory string 17 | verbosity int 18 | testModule CloudformationModule 19 | expectedResult []CFStack 20 | }{ 21 | { 22 | name: "test1", 23 | outputDirectory: ".", 24 | verbosity: 2, 25 | testModule: CloudformationModule{ 26 | CloudFormationClient: &sdk.MockedCloudformationClient{}, 27 | Caller: sts.GetCallerIdentityOutput{Arn: aws.String("test")}, 28 | AWSProfile: "test", 29 | Goroutines: 30, 30 | AWSRegions: AWSRegions, 31 | }, 32 | expectedResult: []CFStack{{ 33 | Name: "myteststack", 34 | Role: "role123", 35 | }}, 36 | }, 37 | } 38 | internal.MockFileSystem(true) 39 | for _, subtest := range subtests { 40 | t.Run(subtest.name, func(t *testing.T) { 41 | subtest.testModule.PrintCloudformationStacks(subtest.outputDirectory, subtest.verbosity) 42 | for index, expectedStack := range subtest.expectedResult { 43 | if expectedStack.Name != subtest.testModule.CFStacks[index].Name { 44 | log.Fatal("Stack name does not match expected name") 45 | } 46 | if expectedStack.Role != subtest.testModule.CFStacks[index].Role { 47 | log.Fatal("Stack role does not match expected name") 48 | } 49 | 50 | } 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /aws/databases_test.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "path/filepath" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/BishopFox/cloudfox/aws/sdk" 9 | "github.com/BishopFox/cloudfox/internal" 10 | "github.com/aws/aws-sdk-go-v2/aws" 11 | "github.com/aws/aws-sdk-go-v2/service/sts" 12 | "github.com/aws/smithy-go/ptr" 13 | "github.com/spf13/afero" 14 | ) 15 | 16 | func TestDatabasesCommand(t *testing.T) { 17 | 18 | m := DatabasesModule{ 19 | AWSProfile: "unittesting", 20 | AWSRegions: []string{"us-east-1"}, 21 | Caller: sts.GetCallerIdentityOutput{ 22 | Arn: aws.String("arn:aws:iam::123456789012:user/Alice"), 23 | Account: aws.String("123456789012"), 24 | }, 25 | Goroutines: 3, 26 | WrapTable: false, 27 | RDSClient: &sdk.MockedRDSClient{}, 28 | DynamoDBClient: &sdk.MockedAWSDynamoDBClient{}, 29 | RedshiftClient: &sdk.MockedRedshiftClient{}, 30 | } 31 | 32 | fs := internal.MockFileSystem(true) 33 | defer internal.MockFileSystem(false) 34 | tmpDir := ptr.ToString(internal.GetLogDirPath()) 35 | 36 | m.PrintDatabases(tmpDir, 2) 37 | //resultsFile, err := afero.ReadFile(fs, "table/databases.txt") 38 | resultsFilePath := filepath.Join(tmpDir, "cloudfox-output/aws/unittesting-123456789012/table/databases.txt") 39 | resultsFile, err := afero.ReadFile(fs, resultsFilePath) 40 | if err != nil { 41 | t.Fatalf("Cannot read output file at %s: %s", resultsFile, err) 42 | } 43 | 44 | expectedResults := []string{ 45 | "db1.cluster-123456789012.us-west-2.rds.amazonaws.com", // make sure it includes the Aurora clusters 46 | "db2.cluster-123456789012.us-west-2.rds.amazonaws.com", // make sure it includes the Aurora clusters 47 | "db3.cluster-123456789012.us-west-2.neptune.amazonaws.com", // make sure it includes the Neptune instances 48 | "db4.cluster-123456789012.us-west-2.docdb.amazonaws.com", // make sure it includes the DocumentDB instances 49 | "db1-instances-1.blah.us-west-2.rds.amazonaws.com", // make sure it includes the RDS instances 50 | } 51 | 52 | for _, expected := range expectedResults { 53 | if !strings.Contains(string(resultsFile), expected) { 54 | t.Errorf("Expected %s to be in the results file", expected) 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /aws/ecr_test.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | 7 | "github.com/BishopFox/cloudfox/aws/sdk" 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/aws" 10 | "github.com/aws/aws-sdk-go-v2/service/sts" 11 | ) 12 | 13 | func TestDescribeRepos(t *testing.T) { 14 | 15 | subtests := []struct { 16 | name string 17 | outputDirectory string 18 | verbosity int 19 | testModule ECRModule 20 | expectedResult []Repository 21 | }{ 22 | { 23 | name: "test1", 24 | outputDirectory: ".", 25 | verbosity: 2, 26 | testModule: ECRModule{ 27 | ECRClient: &sdk.MockedECRClient{}, 28 | Caller: sts.GetCallerIdentityOutput{ 29 | Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests"), 30 | Account: aws.String("123456789012"), 31 | }, 32 | AWSProfile: "test", 33 | Goroutines: 30, 34 | AWSRegions: []string{"us-east-1"}, 35 | }, 36 | expectedResult: []Repository{ 37 | { 38 | Name: "repo1", 39 | URI: "11111111111111.dkr.ecr.us-east-1.amazonaws.com/repo1", 40 | PushedAt: "2022-10-25 15:14:00", 41 | ImageTags: "customtag, tag2", 42 | ImageSize: 123456, 43 | }, 44 | { 45 | Name: "repo2", 46 | URI: "11111111111111.dkr.ecr.us-east-1.amazonaws.com/repo2", 47 | PushedAt: "2021-10-15 11:14:00", 48 | ImageTags: "latest", 49 | ImageSize: 2222222, 50 | }}, 51 | }, 52 | } 53 | 54 | internal.MockFileSystem(true) 55 | for _, subtest := range subtests { 56 | t.Run(subtest.name, func(t *testing.T) { 57 | subtest.testModule.PrintECR(subtest.outputDirectory, subtest.verbosity) 58 | for index, expectedRepo := range subtest.expectedResult { 59 | if expectedRepo.Name != subtest.testModule.Repositories[index].Name { 60 | log.Fatal("Repo name does not match expected name") 61 | } 62 | 63 | } 64 | }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /aws/ecs-tasks_test.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | 7 | "github.com/BishopFox/cloudfox/aws/sdk" 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/aws" 10 | "github.com/aws/aws-sdk-go-v2/service/sts" 11 | ) 12 | 13 | func TestECSTasks(t *testing.T) { 14 | subtests := []struct { 15 | name string 16 | outputDirectory string 17 | verbosity int 18 | testModule ECSTasksModule 19 | expectedResult []MappedECSTask 20 | }{ 21 | { 22 | name: "test1", 23 | outputDirectory: ".", 24 | verbosity: 2, 25 | testModule: ECSTasksModule{ 26 | 27 | AWSProfile: "default", 28 | AWSRegions: []string{"us-east-1", "us-west-1"}, 29 | Caller: sts.GetCallerIdentityOutput{Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests")}, 30 | SkipAdminCheck: true, 31 | Goroutines: 30, 32 | EC2Client: &sdk.MockedEC2Client2{}, 33 | ECSClient: &sdk.MockedECSClient{}, 34 | }, 35 | expectedResult: []MappedECSTask{{ 36 | Cluster: "MyCluster", 37 | ID: "74de0355a10a4f979ac495c14EXAMPLE", 38 | ExternalIP: "203.0.113.12", 39 | Role: "test123", 40 | }}, 41 | }, 42 | } 43 | internal.MockFileSystem(true) 44 | for _, subtest := range subtests { 45 | t.Run(subtest.name, func(t *testing.T) { 46 | subtest.testModule.ECSTasks(subtest.outputDirectory, subtest.verbosity) 47 | for index, expectedTask := range subtest.expectedResult { 48 | if expectedTask.Cluster != subtest.testModule.MappedECSTasks[index].Cluster { 49 | log.Fatal("Cluster name does not match expected value") 50 | } 51 | } 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /aws/elastic-network-interfaces_test.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/BishopFox/cloudfox/aws/sdk" 7 | "github.com/BishopFox/cloudfox/internal" 8 | 9 | "github.com/aws/aws-sdk-go-v2/aws" 10 | 11 | "github.com/aws/aws-sdk-go-v2/service/sts" 12 | ) 13 | 14 | func TestElasticNetworkInterfaces(t *testing.T) { 15 | 16 | m := ElasticNetworkInterfacesModule{ 17 | AWSProfile: "default", 18 | AWSRegions: []string{"us-east-1", "us-west-1"}, 19 | Caller: sts.GetCallerIdentityOutput{Arn: aws.String("arn:aws:iam::123456789012:user/cloudfox_unit_tests")}, 20 | EC2Client: &sdk.MockedEC2Client2{}, 21 | } 22 | 23 | //m.ElasticNetworkInterfaces("table", ".", 3) 24 | subtests := []struct { 25 | name string 26 | testModule ElasticNetworkInterfacesModule 27 | expectedResult []MappedENI 28 | }{ 29 | { 30 | name: "Test ElasticNetworkInterfaces", 31 | testModule: m, 32 | expectedResult: []MappedENI{ 33 | { 34 | PrivateIP: "10.0.1.17", 35 | ExternalIP: "203.0.113.12", 36 | }, 37 | { 38 | PrivateIP: "10.0.1.149", 39 | ExternalIP: "198.51.100.0", 40 | }, 41 | }, 42 | }, 43 | } 44 | internal.MockFileSystem(true) 45 | for _, subtest := range subtests { 46 | t.Run(subtest.name, func(t *testing.T) { 47 | subtest.testModule.ElasticNetworkInterfaces(".", 3) 48 | for index, expectedTask := range subtest.expectedResult { 49 | if expectedTask.ExternalIP != subtest.testModule.MappedENIs[index].ExternalIP { 50 | t.Errorf("expected %s, got %s", expectedTask.ExternalIP, subtest.testModule.MappedENIs[index].ExternalIP) 51 | } 52 | if expectedTask.PrivateIP != subtest.testModule.MappedENIs[index].PrivateIP { 53 | t.Errorf("expected %s, got %s", expectedTask.PrivateIP, subtest.testModule.MappedENIs[index].PrivateIP) 54 | } 55 | 56 | } 57 | }) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /aws/graph/collectors/accounts.go: -------------------------------------------------------------------------------- 1 | package collectors 2 | -------------------------------------------------------------------------------- /aws/graph/ingester/schema/models/account.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "github.com/BishopFox/cloudfox/aws/graph/ingester/schema" 4 | 5 | type Account struct { 6 | Id string 7 | Arn string 8 | Email string 9 | Name string 10 | Status string 11 | JoinedMethod string 12 | JoinedTimestamp string 13 | IsOrgMgmt bool 14 | IsChildAccount bool 15 | OrgMgmtAccountID string 16 | OrganizationID string 17 | } 18 | 19 | func (a *Account) MakeRelationships() []schema.Relationship { 20 | var relationships []schema.Relationship 21 | 22 | // make relationship from children accounts to parent org 23 | if a.IsChildAccount { 24 | // make relationship from child to org mgmt account 25 | relationships = append(relationships, schema.Relationship{ 26 | SourceNodeID: a.OrganizationID, 27 | TargetNodeID: a.Id, 28 | SourceLabel: schema.Organization, 29 | TargetLabel: schema.Account, 30 | RelationshipType: schema.Manages, 31 | }) 32 | // make relationship from parent org mgmt account to child account 33 | relationships = append(relationships, schema.Relationship{ 34 | SourceNodeID: a.Id, 35 | TargetNodeID: a.OrganizationID, 36 | SourceLabel: schema.Account, 37 | TargetLabel: schema.Organization, 38 | RelationshipType: schema.MemberOf, 39 | }) 40 | 41 | } 42 | 43 | return relationships 44 | } 45 | -------------------------------------------------------------------------------- /aws/graph/ingester/schema/models/constants.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/BishopFox/cloudfox/aws/graph/ingester/schema" 5 | ) 6 | 7 | var NodeLabelToNodeMap = map[schema.NodeLabel]schema.Node{ 8 | schema.Organization: &Organization{}, 9 | schema.Account: &Account{}, 10 | schema.Role: &Role{}, 11 | } 12 | -------------------------------------------------------------------------------- /aws/graph/ingester/schema/models/org.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/BishopFox/cloudfox/aws/graph/ingester/schema" 5 | ) 6 | 7 | type Organization struct { 8 | Id string 9 | OrgId string 10 | Arn string 11 | MasterAccountArn string 12 | MasterAccountId string 13 | MasterAccountEmail string 14 | ChildAccounts []Account 15 | MgmtAccount Account 16 | } 17 | 18 | func (o *Organization) MakeRelationships() []schema.Relationship { 19 | return []schema.Relationship{} 20 | } 21 | -------------------------------------------------------------------------------- /aws/graph/ingester/schema/models/resource.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Resource struct { 4 | ARN string 5 | } 6 | -------------------------------------------------------------------------------- /aws/graph/ingester/schema/models/users.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/BishopFox/cloudfox/aws/graph/ingester/schema" 7 | ) 8 | 9 | type User struct { 10 | Id string 11 | ARN string 12 | Name string 13 | IsAdmin string 14 | CanPrivEscToAdmin string 15 | IdValue string 16 | } 17 | 18 | func (a *User) MakeRelationships() []schema.Relationship { 19 | // make a relationship between each role and the account it belongs to 20 | relationships := []schema.Relationship{} 21 | 22 | // get thisAccount id from user arn 23 | var thisAccount string 24 | if len(a.ARN) >= 25 { 25 | thisAccount = a.ARN[13:25] 26 | } else { 27 | fmt.Sprintf("Could not get account number from this user arn%s", a.ARN) 28 | } 29 | 30 | relationships = append(relationships, schema.Relationship{ 31 | SourceNodeID: a.Id, 32 | TargetNodeID: thisAccount, 33 | SourceLabel: schema.User, 34 | TargetLabel: schema.Account, 35 | RelationshipType: schema.MemberOf, 36 | Properties: map[string]interface{}{}, 37 | }) 38 | return relationships 39 | } 40 | 41 | func (a *User) GenerateAttributes() map[string]string { 42 | return map[string]string{ 43 | "Id": a.Id, 44 | "ARN": a.ARN, 45 | "Name": a.Name, 46 | "IsAdmin": a.IsAdmin, 47 | "CanPrivEscToAdmin": a.CanPrivEscToAdmin, 48 | "IdValue": a.IdValue, 49 | } 50 | } 51 | 52 | func (a *User) MergeAttributes(other map[string]string) { 53 | if other["Id"] != "" { 54 | a.Id = other["Id"] 55 | } 56 | if other["ARN"] != "" { 57 | a.ARN = other["ARN"] 58 | } 59 | if other["Name"] != "" { 60 | a.Name = other["Name"] 61 | } 62 | if other["IsAdmin"] != "" { 63 | a.IsAdmin = other["IsAdmin"] 64 | } 65 | if other["CanPrivEscToAdmin"] != "" { 66 | a.CanPrivEscToAdmin = other["CanPrivEscToAdmin"] 67 | } 68 | if other["IdValue"] != "" { 69 | a.IdValue = other["IdValue"] 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /aws/resource-trusts_test.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIsResourcePolicyInteresting(t *testing.T) { 8 | testCases := []struct { 9 | name string 10 | input string 11 | expected bool 12 | }{ 13 | { 14 | name: "test1", 15 | input: "Everyone can sqs:SendMessage & can sqs:ReceiveMessage", 16 | expected: true, 17 | }, 18 | { 19 | name: "PrincipalOrgPaths", 20 | input: "aws:PrincipalOrgPaths", 21 | expected: true, 22 | }, 23 | { 24 | name: "Empty", 25 | input: "", 26 | expected: false, 27 | }, 28 | { 29 | name: "NotInteresting", 30 | input: "sns.amazonaws.com can lambda:InvokeFunction", 31 | expected: false, 32 | }, 33 | } 34 | 35 | for _, tc := range testCases { 36 | t.Run(tc.name, func(t *testing.T) { 37 | actual := isResourcePolicyInteresting(tc.input) 38 | if actual != tc.expected { 39 | t.Errorf("Expected %v but got %v for input %s", tc.expected, actual, tc.input) 40 | } 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /aws/sdk/apigatewayv2_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/apigatewayv2" 8 | apiGatwayV2Types "github.com/aws/aws-sdk-go-v2/service/apigatewayv2/types" 9 | ) 10 | 11 | type MockedAWSAPIGatewayv2Client struct { 12 | } 13 | 14 | func (m *MockedAWSAPIGatewayv2Client) GetApis(ctx context.Context, input *apigatewayv2.GetApisInput, options ...func(*apigatewayv2.Options)) (*apigatewayv2.GetApisOutput, error) { 15 | return &apigatewayv2.GetApisOutput{ 16 | Items: []apiGatwayV2Types.Api{ 17 | { 18 | ApiId: aws.String("asdfsdfasdf"), 19 | Name: aws.String("api1"), 20 | ApiEndpoint: aws.String("https://asdfsdfasdf.execute-api.us-east-1.amazonaws.com"), 21 | }, 22 | { 23 | ApiId: aws.String("qwertyqwerty"), 24 | Name: aws.String("api2"), 25 | ApiEndpoint: aws.String("https://qwertyqwerty.execute-api.us-east-1.amazonaws.com"), 26 | }, 27 | }, 28 | }, nil 29 | } 30 | 31 | func (m *MockedAWSAPIGatewayv2Client) GetDomainNames(ctx context.Context, input *apigatewayv2.GetDomainNamesInput, options ...func(*apigatewayv2.Options)) (*apigatewayv2.GetDomainNamesOutput, error) { 32 | return &apigatewayv2.GetDomainNamesOutput{ 33 | Items: []apiGatwayV2Types.DomainName{ 34 | { 35 | DomainName: aws.String("domain1"), 36 | }, 37 | { 38 | DomainName: aws.String("domain2"), 39 | }, 40 | }, 41 | }, nil 42 | } 43 | 44 | func (m *MockedAWSAPIGatewayv2Client) GetApiMappings(ctx context.Context, input *apigatewayv2.GetApiMappingsInput, options ...func(*apigatewayv2.Options)) (*apigatewayv2.GetApiMappingsOutput, error) { 45 | return &apigatewayv2.GetApiMappingsOutput{ 46 | Items: []apiGatwayV2Types.ApiMapping{ 47 | { 48 | ApiMappingId: aws.String("apiMapping1"), 49 | }, 50 | { 51 | ApiMappingId: aws.String("apiMapping2"), 52 | }, 53 | }, 54 | }, nil 55 | } 56 | 57 | func (m *MockedAWSAPIGatewayv2Client) GetStages(ctx context.Context, input *apigatewayv2.GetStagesInput, options ...func(*apigatewayv2.Options)) (*apigatewayv2.GetStagesOutput, error) { 58 | return &apigatewayv2.GetStagesOutput{ 59 | Items: []apiGatwayV2Types.Stage{ 60 | { 61 | StageName: aws.String("stage1"), 62 | }, 63 | { 64 | StageName: aws.String("stage2"), 65 | }, 66 | }, 67 | }, nil 68 | } 69 | 70 | func (m *MockedAWSAPIGatewayv2Client) GetRoutes(ctx context.Context, input *apigatewayv2.GetRoutesInput, options ...func(*apigatewayv2.Options)) (*apigatewayv2.GetRoutesOutput, error) { 71 | return &apigatewayv2.GetRoutesOutput{ 72 | Items: []apiGatwayV2Types.Route{ 73 | { 74 | RouteId: aws.String("route1"), 75 | RouteKey: aws.String("POST /route1"), 76 | }, 77 | { 78 | RouteId: aws.String("route2"), 79 | RouteKey: aws.String("GET /route2"), 80 | }, 81 | }, 82 | }, nil 83 | } 84 | -------------------------------------------------------------------------------- /aws/sdk/apprunner.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/service/apprunner" 10 | apprunnerTypes "github.com/aws/aws-sdk-go-v2/service/apprunner/types" 11 | "github.com/patrickmn/go-cache" 12 | ) 13 | 14 | type AppRunnerClientInterface interface { 15 | ListServices(context.Context, *apprunner.ListServicesInput, ...func(*apprunner.Options)) (*apprunner.ListServicesOutput, error) 16 | } 17 | 18 | func init() { 19 | gob.Register([]apprunnerTypes.Service{}) 20 | gob.Register([]apprunnerTypes.ServiceSummary{}) 21 | } 22 | 23 | func CachedAppRunnerListServices(client AppRunnerClientInterface, accountID string, region string) ([]apprunnerTypes.ServiceSummary, error) { 24 | var PaginationControl *string 25 | var services []apprunnerTypes.ServiceSummary 26 | cacheKey := fmt.Sprintf("%s-apprunner-ListServices-%s", accountID, region) 27 | cached, found := internal.Cache.Get(cacheKey) 28 | if found { 29 | return cached.([]apprunnerTypes.ServiceSummary), nil 30 | } 31 | for { 32 | ListServices, err := client.ListServices( 33 | context.TODO(), 34 | &apprunner.ListServicesInput{ 35 | NextToken: PaginationControl, 36 | }, 37 | func(o *apprunner.Options) { 38 | o.Region = region 39 | }, 40 | ) 41 | 42 | if err != nil { 43 | return services, err 44 | } 45 | 46 | services = append(services, ListServices.ServiceSummaryList...) 47 | 48 | //pagination 49 | if ListServices.NextToken == nil { 50 | break 51 | } 52 | PaginationControl = ListServices.NextToken 53 | } 54 | 55 | internal.Cache.Set(cacheKey, services, cache.DefaultExpiration) 56 | return services, nil 57 | } 58 | -------------------------------------------------------------------------------- /aws/sdk/apprunner_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/apprunner" 8 | apprunnerTypes "github.com/aws/aws-sdk-go-v2/service/apprunner/types" 9 | ) 10 | 11 | type MockedAppRunnerClient struct { 12 | } 13 | 14 | func (m *MockedAppRunnerClient) ListServices(ctx context.Context, input *apprunner.ListServicesInput, options ...func(*apprunner.Options)) (*apprunner.ListServicesOutput, error) { 15 | return &apprunner.ListServicesOutput{ 16 | ServiceSummaryList: []apprunnerTypes.ServiceSummary{ 17 | { 18 | ServiceName: aws.String("service1"), 19 | }, 20 | { 21 | ServiceName: aws.String("service2"), 22 | }, 23 | }, 24 | }, nil 25 | } 26 | -------------------------------------------------------------------------------- /aws/sdk/athena.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/service/athena" 10 | athenaTypes "github.com/aws/aws-sdk-go-v2/service/athena/types" 11 | "github.com/patrickmn/go-cache" 12 | ) 13 | 14 | type AWSAthenaClientInterface interface { 15 | ListDataCatalogs(context.Context, *athena.ListDataCatalogsInput, ...func(*athena.Options)) (*athena.ListDataCatalogsOutput, error) 16 | ListDatabases(context.Context, *athena.ListDatabasesInput, ...func(*athena.Options)) (*athena.ListDatabasesOutput, error) 17 | } 18 | 19 | func init() { 20 | gob.Register([]athenaTypes.DataCatalogSummary{}) 21 | } 22 | 23 | func CachedAthenaListDataCatalogs(client AWSAthenaClientInterface, accountID string, region string) ([]athenaTypes.DataCatalogSummary, error) { 24 | var PaginationControl *string 25 | var dataCatalogs []athenaTypes.DataCatalogSummary 26 | cacheKey := fmt.Sprintf("%s-athena-ListDataCatalogs-%s", accountID, region) 27 | cached, found := internal.Cache.Get(cacheKey) 28 | if found { 29 | return cached.([]athenaTypes.DataCatalogSummary), nil 30 | } 31 | for { 32 | ListDataCatalogs, err := client.ListDataCatalogs( 33 | context.TODO(), 34 | &athena.ListDataCatalogsInput{ 35 | NextToken: PaginationControl, 36 | }, 37 | func(o *athena.Options) { 38 | o.Region = region 39 | }, 40 | ) 41 | 42 | if err != nil { 43 | return dataCatalogs, err 44 | } 45 | 46 | dataCatalogs = append(dataCatalogs, ListDataCatalogs.DataCatalogsSummary...) 47 | 48 | //pagination 49 | if ListDataCatalogs.NextToken == nil { 50 | break 51 | } 52 | PaginationControl = ListDataCatalogs.NextToken 53 | } 54 | 55 | internal.Cache.Set(cacheKey, dataCatalogs, cache.DefaultExpiration) 56 | return dataCatalogs, nil 57 | } 58 | 59 | func CachedAthenaListDatabases(client AWSAthenaClientInterface, accountID string, region string, catalogName string) ([]string, error) { 60 | var PaginationControl *string 61 | var databases []string 62 | cacheKey := fmt.Sprintf("%s-athena-ListDatabases-%s-%s", accountID, region, catalogName) 63 | cached, found := internal.Cache.Get(cacheKey) 64 | if found { 65 | return cached.([]string), nil 66 | } 67 | for { 68 | ListDatabases, err := client.ListDatabases( 69 | context.TODO(), 70 | &athena.ListDatabasesInput{ 71 | CatalogName: &catalogName, 72 | NextToken: PaginationControl, 73 | }, 74 | func(o *athena.Options) { 75 | o.Region = region 76 | }, 77 | ) 78 | 79 | if err != nil { 80 | return databases, err 81 | } 82 | 83 | for _, database := range ListDatabases.DatabaseList { 84 | databases = append(databases, *database.Name) 85 | } 86 | 87 | //pagination 88 | if ListDatabases.NextToken == nil { 89 | break 90 | } 91 | PaginationControl = ListDatabases.NextToken 92 | } 93 | 94 | internal.Cache.Set(cacheKey, databases, cache.DefaultExpiration) 95 | return databases, nil 96 | } 97 | -------------------------------------------------------------------------------- /aws/sdk/athena_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/athena" 8 | athenaTypes "github.com/aws/aws-sdk-go-v2/service/athena/types" 9 | ) 10 | 11 | type MockedAWSAthenaClient struct { 12 | } 13 | 14 | func (m *MockedAWSAthenaClient) ListDatabases(ctx context.Context, input *athena.ListDatabasesInput, options ...func(*athena.Options)) (*athena.ListDatabasesOutput, error) { 15 | return &athena.ListDatabasesOutput{ 16 | DatabaseList: []athenaTypes.Database{ 17 | { 18 | Name: aws.String("db1"), 19 | }, 20 | { 21 | Name: aws.String("db2"), 22 | }, 23 | }, 24 | }, nil 25 | } 26 | 27 | func (m *MockedAWSAthenaClient) ListDataCatalogs(ctx context.Context, input *athena.ListDataCatalogsInput, options ...func(*athena.Options)) (*athena.ListDataCatalogsOutput, error) { 28 | return &athena.ListDataCatalogsOutput{ 29 | DataCatalogsSummary: []athenaTypes.DataCatalogSummary{ 30 | { 31 | CatalogName: aws.String("catalog1"), 32 | }, 33 | { 34 | CatalogName: aws.String("catalog2"), 35 | }, 36 | }, 37 | }, nil 38 | } 39 | -------------------------------------------------------------------------------- /aws/sdk/cloud9.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | 7 | "github.com/BishopFox/cloudfox/internal" 8 | "github.com/aws/aws-sdk-go-v2/service/cloud9" 9 | cloud9Types "github.com/aws/aws-sdk-go-v2/service/cloud9/types" 10 | "github.com/patrickmn/go-cache" 11 | ) 12 | 13 | type AWSCloud9ClientInterface interface { 14 | ListEnvironments(context.Context, *cloud9.ListEnvironmentsInput, ...func(*cloud9.Options)) (*cloud9.ListEnvironmentsOutput, error) 15 | DescribeEnvironments(context.Context, *cloud9.DescribeEnvironmentsInput, ...func(*cloud9.Options)) (*cloud9.DescribeEnvironmentsOutput, error) 16 | } 17 | 18 | func init() { 19 | gob.Register([]string{}) 20 | } 21 | 22 | func CachedCloud9ListEnvironments(client AWSCloud9ClientInterface, accountID string, region string) ([]string, error) { 23 | var PaginationControl *string 24 | var environments []string 25 | cacheKey := "cloud9-ListEnvironments-" + accountID + "-" + region 26 | cached, found := internal.Cache.Get(cacheKey) 27 | if found { 28 | return cached.([]string), nil 29 | } 30 | for { 31 | ListEnvironments, err := client.ListEnvironments( 32 | context.TODO(), 33 | &cloud9.ListEnvironmentsInput{ 34 | NextToken: PaginationControl, 35 | }, 36 | func(o *cloud9.Options) { 37 | o.Region = region 38 | }, 39 | ) 40 | 41 | if err != nil { 42 | return environments, err 43 | } 44 | 45 | environments = append(environments, ListEnvironments.EnvironmentIds...) 46 | 47 | //pagination 48 | if ListEnvironments.NextToken == nil { 49 | break 50 | } 51 | PaginationControl = ListEnvironments.NextToken 52 | } 53 | 54 | internal.Cache.Set(cacheKey, environments, cache.DefaultExpiration) 55 | return environments, nil 56 | } 57 | 58 | func CachedCloud9DescribeEnvironments(client AWSCloud9ClientInterface, accountID string, region string, environmentIDs []string) ([]cloud9Types.Environment, error) { 59 | var environments []cloud9Types.Environment 60 | cacheKey := "cloud9-DescribeEnvironments-" + accountID + "-" + region 61 | cached, found := internal.Cache.Get(cacheKey) 62 | if found { 63 | return cached.([]cloud9Types.Environment), nil 64 | } 65 | for _, environmentID := range environmentIDs { 66 | DescribeEnvironments, err := client.DescribeEnvironments( 67 | context.TODO(), 68 | &cloud9.DescribeEnvironmentsInput{ 69 | EnvironmentIds: []string{environmentID}, 70 | }, 71 | func(o *cloud9.Options) { 72 | o.Region = region 73 | }, 74 | ) 75 | 76 | if err != nil { 77 | return environments, err 78 | } 79 | 80 | environments = append(environments, DescribeEnvironments.Environments...) 81 | 82 | } 83 | 84 | internal.Cache.Set(cacheKey, environments, cache.DefaultExpiration) 85 | return environments, nil 86 | } 87 | -------------------------------------------------------------------------------- /aws/sdk/cloud9_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/cloud9" 8 | "github.com/aws/aws-sdk-go-v2/service/cloud9/types" 9 | ) 10 | 11 | type MockedAWSCloud9Client struct { 12 | } 13 | 14 | func (m *MockedAWSCloud9Client) ListEnvironments(ctx context.Context, input *cloud9.ListEnvironmentsInput, options ...func(*cloud9.Options)) (*cloud9.ListEnvironmentsOutput, error) { 15 | return &cloud9.ListEnvironmentsOutput{ 16 | EnvironmentIds: []string{ 17 | "env1", 18 | "env2", 19 | }, 20 | }, nil 21 | } 22 | 23 | func (m *MockedAWSCloud9Client) DescribeEnvironments(ctx context.Context, input *cloud9.DescribeEnvironmentsInput, options ...func(*cloud9.Options)) (*cloud9.DescribeEnvironmentsOutput, error) { 24 | return &cloud9.DescribeEnvironmentsOutput{ 25 | Environments: []types.Environment{ 26 | { 27 | Name: aws.String("env1"), 28 | Arn: aws.String("arn:aws:cloud9:us-east-1:123456789012:environment/env1"), 29 | }, 30 | { 31 | Name: aws.String("env2"), 32 | Arn: aws.String("arn:aws:cloud9:us-east-1:123456789012:environment/env2"), 33 | }, 34 | }, 35 | }, nil 36 | } 37 | -------------------------------------------------------------------------------- /aws/sdk/cloudformation_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "log" 7 | "time" 8 | 9 | "github.com/aws/aws-sdk-go-v2/service/cloudformation" 10 | "github.com/aws/aws-sdk-go-v2/service/cloudformation/types" 11 | ) 12 | 13 | const DESCRIBE_STACKS_TEST_FILE = "./test-data/cloudformation-describestacks.json" 14 | const TEMPLATE_BODY_TEST_FILE = "./test-data/cloudformation-getTemplate.json" 15 | 16 | type Stacks struct { 17 | Stacks []struct { 18 | StackID string `json:"StackId"` 19 | Description string `json:"Description"` 20 | Tags []interface{} `json:"Tags"` 21 | Outputs []types.Output `json:"Outputs"` 22 | Parameters []types.Parameter `json:"Parameters"` 23 | StackStatusReason interface{} `json:"StackStatusReason"` 24 | CreationTime time.Time `json:"CreationTime"` 25 | Capabilities []interface{} `json:"Capabilities"` 26 | StackName string `json:"StackName"` 27 | RoleArn string `json:"RoleArn"` 28 | StackStatus string `json:"StackStatus"` 29 | DisableRollback bool `json:"DisableRollback"` 30 | } `json:"Stacks"` 31 | } 32 | 33 | type TemplateBody struct { 34 | TemplateBody string `json:"TemplateBody"` 35 | } 36 | 37 | type MockedCloudformationClient struct { 38 | describeStacks Stacks 39 | getTemplateBody TemplateBody 40 | } 41 | 42 | func (m *MockedCloudformationClient) DescribeStacks(ctx context.Context, params *cloudformation.DescribeStacksInput, optFns ...func(*cloudformation.Options)) (*cloudformation.DescribeStacksOutput, error) { 43 | 44 | err := json.Unmarshal(readTestFile(DESCRIBE_STACKS_TEST_FILE), &m.describeStacks) 45 | if err != nil { 46 | log.Fatalf("can't unmarshall file %s", DESCRIBE_STACKS_TEST_FILE) 47 | } 48 | var stacks []types.Stack 49 | for _, stack := range m.describeStacks.Stacks { 50 | stacks = append(stacks, types.Stack{ 51 | StackName: &stack.StackName, 52 | RoleARN: &stack.RoleArn, 53 | Outputs: stack.Outputs, 54 | Parameters: stack.Parameters, 55 | }) 56 | 57 | } 58 | 59 | return &cloudformation.DescribeStacksOutput{Stacks: stacks}, nil 60 | } 61 | 62 | func (m *MockedCloudformationClient) GetTemplate(ctx context.Context, params *cloudformation.GetTemplateInput, optFns ...func(*cloudformation.Options)) (*cloudformation.GetTemplateOutput, error) { 63 | err := json.Unmarshal(readTestFile(DESCRIBE_STACKS_TEST_FILE), &m.getTemplateBody) 64 | if err != nil { 65 | log.Fatalf("can't unmarshall file %s", TEMPLATE_BODY_TEST_FILE) 66 | } 67 | 68 | return &cloudformation.GetTemplateOutput{TemplateBody: &m.getTemplateBody.TemplateBody}, nil 69 | } 70 | 71 | func (m *MockedCloudformationClient) ListStacks(ctx context.Context, params *cloudformation.ListStacksInput, optFns ...func(*cloudformation.Options)) (*cloudformation.ListStacksOutput, error) { 72 | return &cloudformation.ListStacksOutput{}, nil 73 | } 74 | -------------------------------------------------------------------------------- /aws/sdk/cloudfront.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | 7 | "github.com/BishopFox/cloudfox/internal" 8 | "github.com/aws/aws-sdk-go-v2/service/cloudfront" 9 | cloudfrontTypes "github.com/aws/aws-sdk-go-v2/service/cloudfront/types" 10 | "github.com/patrickmn/go-cache" 11 | ) 12 | 13 | type AWSCloudFrontClientInterface interface { 14 | ListDistributions(ctx context.Context, params *cloudfront.ListDistributionsInput, optFns ...func(*cloudfront.Options)) (*cloudfront.ListDistributionsOutput, error) 15 | } 16 | 17 | func init() { 18 | gob.Register([]cloudfrontTypes.DistributionSummary{}) 19 | gob.Register(cloudfrontTypes.DistributionSummary{}) 20 | } 21 | 22 | func CachedCloudFrontListDistributions(CloudFrontClient AWSCloudFrontClientInterface, accountID string) ([]cloudfrontTypes.DistributionSummary, error) { 23 | var PaginationControl *string 24 | var distributions []cloudfrontTypes.DistributionSummary 25 | cacheKey := "cloudfront-ListDistributions-" + accountID 26 | cached, found := internal.Cache.Get(cacheKey) 27 | if found { 28 | sharedLogger.Debug("Using cached CloudFront distributions data") 29 | return cached.([]cloudfrontTypes.DistributionSummary), nil 30 | } 31 | 32 | for { 33 | ListDistributions, err := CloudFrontClient.ListDistributions( 34 | context.TODO(), 35 | &cloudfront.ListDistributionsInput{ 36 | Marker: PaginationControl, 37 | }, 38 | ) 39 | if err != nil { 40 | sharedLogger.Error(err.Error()) 41 | break 42 | } 43 | 44 | distributions = append(distributions, ListDistributions.DistributionList.Items...) 45 | 46 | // Pagination control. 47 | if ListDistributions.DistributionList.NextMarker != nil { 48 | PaginationControl = ListDistributions.DistributionList.NextMarker 49 | } else { 50 | PaginationControl = nil 51 | break 52 | } 53 | } 54 | 55 | internal.Cache.Set(cacheKey, distributions, cache.DefaultExpiration) 56 | return distributions, nil 57 | } 58 | -------------------------------------------------------------------------------- /aws/sdk/cloudfront_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/cloudfront" 8 | cloudfrontTypes "github.com/aws/aws-sdk-go-v2/service/cloudfront/types" 9 | ) 10 | 11 | type MockedAWSCloudFrontClient struct { 12 | } 13 | 14 | func (m *MockedAWSCloudFrontClient) ListDistributions(ctx context.Context, input *cloudfront.ListDistributionsInput, options ...func(*cloudfront.Options)) (*cloudfront.ListDistributionsOutput, error) { 15 | return &cloudfront.ListDistributionsOutput{ 16 | DistributionList: &cloudfrontTypes.DistributionList{ 17 | Items: []cloudfrontTypes.DistributionSummary{ 18 | { 19 | Id: aws.String("distribution1"), 20 | }, 21 | { 22 | Id: aws.String("distribution2"), 23 | }, 24 | }, 25 | }, 26 | }, nil 27 | } 28 | -------------------------------------------------------------------------------- /aws/sdk/codeartifact.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/service/codeartifact" 10 | codeArtifactTypes "github.com/aws/aws-sdk-go-v2/service/codeartifact/types" 11 | "github.com/patrickmn/go-cache" 12 | ) 13 | 14 | type AWSCodeArtifactClientInterface interface { 15 | ListDomains(context.Context, *codeartifact.ListDomainsInput, ...func(*codeartifact.Options)) (*codeartifact.ListDomainsOutput, error) 16 | ListRepositories(context.Context, *codeartifact.ListRepositoriesInput, ...func(*codeartifact.Options)) (*codeartifact.ListRepositoriesOutput, error) 17 | } 18 | 19 | func init() { 20 | gob.Register([]codeArtifactTypes.DomainSummary{}) 21 | gob.Register([]codeArtifactTypes.RepositorySummary{}) 22 | } 23 | 24 | func CachedCodeArtifactListDomains(client AWSCodeArtifactClientInterface, accountID string, region string) ([]codeArtifactTypes.DomainSummary, error) { 25 | var PaginationControl *string 26 | var domains []codeArtifactTypes.DomainSummary 27 | cacheKey := fmt.Sprintf("%s-codeartifact-ListDomains-%s", accountID, region) 28 | cached, found := internal.Cache.Get(cacheKey) 29 | if found { 30 | return cached.([]codeArtifactTypes.DomainSummary), nil 31 | } 32 | for { 33 | ListDomains, err := client.ListDomains( 34 | context.TODO(), 35 | &codeartifact.ListDomainsInput{ 36 | NextToken: PaginationControl, 37 | }, 38 | func(o *codeartifact.Options) { 39 | o.Region = region 40 | }, 41 | ) 42 | 43 | if err != nil { 44 | return domains, err 45 | } 46 | 47 | domains = append(domains, ListDomains.Domains...) 48 | 49 | //pagination 50 | if ListDomains.NextToken == nil { 51 | break 52 | } 53 | PaginationControl = ListDomains.NextToken 54 | } 55 | 56 | internal.Cache.Set(cacheKey, domains, cache.DefaultExpiration) 57 | return domains, nil 58 | } 59 | 60 | func CachedCodeArtifactListRepositories(client AWSCodeArtifactClientInterface, accountID string, region string) ([]codeArtifactTypes.RepositorySummary, error) { 61 | var PaginationControl *string 62 | var repositories []codeArtifactTypes.RepositorySummary 63 | cacheKey := fmt.Sprintf("%s-codeartifact-ListRepositories-%s", accountID, region) 64 | cached, found := internal.Cache.Get(cacheKey) 65 | if found { 66 | return cached.([]codeArtifactTypes.RepositorySummary), nil 67 | } 68 | for { 69 | ListRepositories, err := client.ListRepositories( 70 | context.TODO(), 71 | &codeartifact.ListRepositoriesInput{ 72 | NextToken: PaginationControl, 73 | }, 74 | func(o *codeartifact.Options) { 75 | o.Region = region 76 | }, 77 | ) 78 | 79 | if err != nil { 80 | return repositories, err 81 | } 82 | 83 | repositories = append(repositories, ListRepositories.Repositories...) 84 | 85 | //pagination 86 | if ListRepositories.NextToken == nil { 87 | break 88 | } 89 | PaginationControl = ListRepositories.NextToken 90 | } 91 | 92 | internal.Cache.Set(cacheKey, repositories, cache.DefaultExpiration) 93 | return repositories, nil 94 | } 95 | -------------------------------------------------------------------------------- /aws/sdk/codeartifact_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/codeartifact" 8 | codeArtifactTypes "github.com/aws/aws-sdk-go-v2/service/codeartifact/types" 9 | ) 10 | 11 | type MockedAWSCodeArtifactClient struct { 12 | } 13 | 14 | func (m *MockedAWSCodeArtifactClient) ListRepositories(ctx context.Context, input *codeartifact.ListRepositoriesInput, options ...func(*codeartifact.Options)) (*codeartifact.ListRepositoriesOutput, error) { 15 | return &codeartifact.ListRepositoriesOutput{ 16 | NextToken: nil, 17 | Repositories: []codeArtifactTypes.RepositorySummary{ 18 | { 19 | Name: aws.String("repo1"), 20 | Arn: aws.String("arn:aws:codeartifact:us-east-1:123456789012:repository/repo1"), 21 | DomainName: aws.String("domain1"), 22 | }, 23 | { 24 | Name: aws.String("repo2"), 25 | Arn: aws.String("arn:aws:codeartifact:us-east-1:123456789012:repository/repo2"), 26 | DomainName: aws.String("domain1"), 27 | }, 28 | }, 29 | }, nil 30 | } 31 | 32 | func (m *MockedAWSCodeArtifactClient) ListDomains(ctx context.Context, input *codeartifact.ListDomainsInput, options ...func(*codeartifact.Options)) (*codeartifact.ListDomainsOutput, error) { 33 | return &codeartifact.ListDomainsOutput{ 34 | NextToken: nil, 35 | Domains: []codeArtifactTypes.DomainSummary{ 36 | { 37 | Name: aws.String("domain1"), 38 | Arn: aws.String("arn:aws:codeartifact:us-east-1:123456789012:domain/domain1"), 39 | }, 40 | { 41 | Name: aws.String("domain2"), 42 | Arn: aws.String("arn:aws:codeartifact:us-east-1:123456789012:domain/domain2"), 43 | }, 44 | }, 45 | }, nil 46 | } 47 | -------------------------------------------------------------------------------- /aws/sdk/codebuild_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/codebuild" 8 | ) 9 | 10 | type MockedCodeBuildClient struct { 11 | } 12 | 13 | func (m *MockedCodeBuildClient) ListProjects(ctx context.Context, input *codebuild.ListProjectsInput, options ...func(*codebuild.Options)) (*codebuild.ListProjectsOutput, error) { 14 | return &codebuild.ListProjectsOutput{ 15 | Projects: []string{ 16 | "project1", 17 | "project2", 18 | }, 19 | }, nil 20 | } 21 | 22 | func (m *MockedCodeBuildClient) GetResourcePolicy(ctx context.Context, input *codebuild.GetResourcePolicyInput, options ...func(*codebuild.Options)) (*codebuild.GetResourcePolicyOutput, error) { 23 | return &codebuild.GetResourcePolicyOutput{ 24 | Policy: aws.String(`{ 25 | "Version": "2012-10-17", 26 | "Statement": [ 27 | { 28 | "Effect": "Allow", 29 | "Action": "codebuild:BatchGetProjects", 30 | "Resource": "*", 31 | "Principal": { 32 | "AWS": "arn:aws:iam::123456789012:root" 33 | }, 34 | } 35 | ] 36 | } 37 | `), 38 | }, nil 39 | } 40 | -------------------------------------------------------------------------------- /aws/sdk/codecommit.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/service/codecommit" 10 | codeCommitTypes "github.com/aws/aws-sdk-go-v2/service/codecommit/types" 11 | "github.com/patrickmn/go-cache" 12 | ) 13 | 14 | type AWSCodeCommitClientInterface interface { 15 | ListRepositories(context.Context, *codecommit.ListRepositoriesInput, ...func(*codecommit.Options)) (*codecommit.ListRepositoriesOutput, error) 16 | } 17 | 18 | func init() { 19 | gob.Register([]codeCommitTypes.RepositoryNameIdPair{}) 20 | } 21 | 22 | func CachedCodeCommitListRepositories(client AWSCodeCommitClientInterface, accountID string, region string) ([]codeCommitTypes.RepositoryNameIdPair, error) { 23 | var PaginationControl *string 24 | var repositories []codeCommitTypes.RepositoryNameIdPair 25 | cacheKey := fmt.Sprintf("%s-codecommit-ListRepositories-%s", accountID, region) 26 | cached, found := internal.Cache.Get(cacheKey) 27 | if found { 28 | return cached.([]codeCommitTypes.RepositoryNameIdPair), nil 29 | } 30 | for { 31 | ListRepositories, err := client.ListRepositories( 32 | context.TODO(), 33 | &codecommit.ListRepositoriesInput{ 34 | NextToken: PaginationControl, 35 | }, 36 | func(o *codecommit.Options) { 37 | o.Region = region 38 | }, 39 | ) 40 | 41 | if err != nil { 42 | return repositories, err 43 | } 44 | 45 | repositories = append(repositories, ListRepositories.Repositories...) 46 | 47 | //pagination 48 | if ListRepositories.NextToken == nil { 49 | break 50 | } 51 | PaginationControl = ListRepositories.NextToken 52 | } 53 | 54 | internal.Cache.Set(cacheKey, repositories, cache.DefaultExpiration) 55 | return repositories, nil 56 | } 57 | -------------------------------------------------------------------------------- /aws/sdk/codecommit_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/codecommit" 8 | codeCommitTypes "github.com/aws/aws-sdk-go-v2/service/codecommit/types" 9 | ) 10 | 11 | // create mocks for codecommit ListRepositories 12 | 13 | type MockedAWSCodeCommitClient struct { 14 | } 15 | 16 | func (m *MockedAWSCodeCommitClient) ListRepositories(ctx context.Context, input *codecommit.ListRepositoriesInput, options ...func(*codecommit.Options)) (*codecommit.ListRepositoriesOutput, error) { 17 | return &codecommit.ListRepositoriesOutput{ 18 | NextToken: nil, 19 | Repositories: []codeCommitTypes.RepositoryNameIdPair{ 20 | { 21 | RepositoryId: aws.String("repo1"), 22 | RepositoryName: aws.String("repo1"), 23 | }, 24 | { 25 | RepositoryId: aws.String("repo2"), 26 | RepositoryName: aws.String("repo2"), 27 | }, 28 | }, 29 | }, nil 30 | } 31 | -------------------------------------------------------------------------------- /aws/sdk/codedeploy_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/aws/aws-sdk-go-v2/aws" 8 | "github.com/aws/aws-sdk-go-v2/service/codedeploy" 9 | codedeployTypes "github.com/aws/aws-sdk-go-v2/service/codedeploy/types" 10 | ) 11 | 12 | type MockedCodedeployClient struct { 13 | } 14 | 15 | func (m *MockedCodedeployClient) ListApplications(ctx context.Context, input *codedeploy.ListApplicationsInput, options ...func(*codedeploy.Options)) (*codedeploy.ListApplicationsOutput, error) { 16 | return &codedeploy.ListApplicationsOutput{ 17 | Applications: []string{ 18 | "app1", 19 | "app2", 20 | }, 21 | }, nil 22 | } 23 | 24 | func (m *MockedCodedeployClient) ListDeployments(ctx context.Context, input *codedeploy.ListDeploymentsInput, options ...func(*codedeploy.Options)) (*codedeploy.ListDeploymentsOutput, error) { 25 | return &codedeploy.ListDeploymentsOutput{ 26 | Deployments: []string{ 27 | "deployment1", 28 | "deployment2", 29 | }, 30 | }, nil 31 | } 32 | 33 | func (m *MockedCodedeployClient) ListDeploymentConfigs(ctx context.Context, input *codedeploy.ListDeploymentConfigsInput, options ...func(*codedeploy.Options)) (*codedeploy.ListDeploymentConfigsOutput, error) { 34 | return &codedeploy.ListDeploymentConfigsOutput{ 35 | DeploymentConfigsList: []string{ 36 | "deploymentConfig1", 37 | "deploymentConfig2", 38 | }, 39 | }, nil 40 | } 41 | 42 | func (m *MockedCodedeployClient) GetApplication(ctx context.Context, input *codedeploy.GetApplicationInput, options ...func(*codedeploy.Options)) (*codedeploy.GetApplicationOutput, error) { 43 | return &codedeploy.GetApplicationOutput{ 44 | Application: &codedeployTypes.ApplicationInfo{ 45 | ApplicationName: aws.String("application1"), 46 | ApplicationId: aws.String("application1"), 47 | CreateTime: aws.Time(time.Now()), 48 | GitHubAccountName: aws.String("github"), 49 | LinkedToGitHub: true, 50 | }, 51 | }, nil 52 | } 53 | 54 | func (m *MockedCodedeployClient) GetDeployment(ctx context.Context, input *codedeploy.GetDeploymentInput, options ...func(*codedeploy.Options)) (*codedeploy.GetDeploymentOutput, error) { 55 | return &codedeploy.GetDeploymentOutput{ 56 | DeploymentInfo: &codedeployTypes.DeploymentInfo{ 57 | ApplicationName: aws.String("application1"), 58 | CreateTime: aws.Time(time.Now()), 59 | DeploymentId: aws.String("deployment1"), 60 | DeploymentOverview: &codedeployTypes.DeploymentOverview{ 61 | Failed: 0, 62 | InProgress: 0, 63 | Pending: 0, 64 | Ready: 0, 65 | Skipped: 0, 66 | Succeeded: 0, 67 | }, 68 | DeploymentConfigName: aws.String("deploymentConfig1"), 69 | Status: codedeployTypes.DeploymentStatusSucceeded, 70 | }, 71 | }, nil 72 | } 73 | 74 | func (m *MockedCodedeployClient) GetDeploymentConfig(ctx context.Context, input *codedeploy.GetDeploymentConfigInput, options ...func(*codedeploy.Options)) (*codedeploy.GetDeploymentConfigOutput, error) { 75 | return &codedeploy.GetDeploymentConfigOutput{ 76 | DeploymentConfigInfo: &codedeployTypes.DeploymentConfigInfo{ 77 | DeploymentConfigName: aws.String("deploymentConfig1"), 78 | ComputePlatform: codedeployTypes.ComputePlatformServer, 79 | MinimumHealthyHosts: &codedeployTypes.MinimumHealthyHosts{ 80 | Type: codedeployTypes.MinimumHealthyHostsTypeFleetPercent, 81 | Value: 100, 82 | }, 83 | }, 84 | }, nil 85 | } 86 | -------------------------------------------------------------------------------- /aws/sdk/datapipeline.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/service/datapipeline" 10 | dataPipelineTypes "github.com/aws/aws-sdk-go-v2/service/datapipeline/types" 11 | "github.com/patrickmn/go-cache" 12 | ) 13 | 14 | type AWSDataPipelineClientInterface interface { 15 | ListPipelines(ctx context.Context, input *datapipeline.ListPipelinesInput, opts ...func(*datapipeline.Options)) (*datapipeline.ListPipelinesOutput, error) 16 | } 17 | 18 | func init() { 19 | gob.Register([]dataPipelineTypes.PipelineIdName{}) 20 | } 21 | 22 | func CachedDataPipelineListPipelines(client AWSDataPipelineClientInterface, accountID string, region string) ([]dataPipelineTypes.PipelineIdName, error) { 23 | var PaginationControl *string 24 | var pipelines []dataPipelineTypes.PipelineIdName 25 | cacheKey := fmt.Sprintf("%s-datapipeline-ListPipelines-%s", accountID, region) 26 | cached, found := internal.Cache.Get(cacheKey) 27 | if found { 28 | return cached.([]dataPipelineTypes.PipelineIdName), nil 29 | } 30 | for { 31 | ListPipelines, err := client.ListPipelines( 32 | context.TODO(), 33 | &datapipeline.ListPipelinesInput{ 34 | Marker: PaginationControl, 35 | }, 36 | func(o *datapipeline.Options) { 37 | o.Region = region 38 | }, 39 | ) 40 | 41 | if err != nil { 42 | return pipelines, err 43 | } 44 | 45 | pipelines = append(pipelines, ListPipelines.PipelineIdList...) 46 | 47 | //pagination 48 | if ListPipelines.Marker == nil { 49 | break 50 | } 51 | PaginationControl = ListPipelines.Marker 52 | } 53 | 54 | internal.Cache.Set(cacheKey, pipelines, cache.DefaultExpiration) 55 | return pipelines, nil 56 | } 57 | -------------------------------------------------------------------------------- /aws/sdk/datapipeline_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/datapipeline" 8 | dataPipelineTypes "github.com/aws/aws-sdk-go-v2/service/datapipeline/types" 9 | ) 10 | 11 | type MockedDataPipelineClient struct { 12 | } 13 | 14 | func (m *MockedDataPipelineClient) ListPipelines(ctx context.Context, input *datapipeline.ListPipelinesInput, options ...func(*datapipeline.Options)) (*datapipeline.ListPipelinesOutput, error) { 15 | return &datapipeline.ListPipelinesOutput{ 16 | PipelineIdList: []dataPipelineTypes.PipelineIdName{ 17 | { 18 | Id: aws.String("pipeline1"), 19 | Name: aws.String("pipeline1"), 20 | }, 21 | { 22 | Id: aws.String("pipeline2"), 23 | Name: aws.String("pipeline2"), 24 | }, 25 | }, 26 | }, nil 27 | } 28 | -------------------------------------------------------------------------------- /aws/sdk/docsdb_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/docdb" 8 | docdbTypes "github.com/aws/aws-sdk-go-v2/service/docdb/types" 9 | ) 10 | 11 | type MockedAWSDocsDBClient struct { 12 | } 13 | 14 | func (m *MockedAWSDocsDBClient) DescribeDBClusters(ctx context.Context, input *docdb.DescribeDBClustersInput, options ...func(*docdb.Options)) (*docdb.DescribeDBClustersOutput, error) { 15 | return &docdb.DescribeDBClustersOutput{ 16 | DBClusters: []docdbTypes.DBCluster{ 17 | { 18 | DBClusterIdentifier: aws.String("cluster1"), 19 | }, 20 | { 21 | DBClusterIdentifier: aws.String("cluster2"), 22 | }, 23 | }, 24 | }, nil 25 | } 26 | -------------------------------------------------------------------------------- /aws/sdk/ds.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/patrickmn/go-cache" 9 | "github.com/BishopFox/cloudfox/internal" 10 | "github.com/aws/aws-sdk-go-v2/service/directoryservice" 11 | dsTypes "github.com/aws/aws-sdk-go-v2/service/directoryservice/types" 12 | ) 13 | 14 | type AWSDSClientInterface interface { 15 | DescribeDirectories(context.Context, *directoryservice.DescribeDirectoriesInput, ...func(*directoryservice.Options)) (*directoryservice.DescribeDirectoriesOutput, error) 16 | DescribeTrusts(context.Context, *directoryservice.DescribeTrustsInput, ...func(*directoryservice.Options)) (*directoryservice.DescribeTrustsOutput, error) 17 | } 18 | 19 | func init() { 20 | gob.Register([]dsTypes.DirectoryDescription{}) 21 | gob.Register([]dsTypes.Trust{}) 22 | 23 | } 24 | 25 | func CachedDSDescribeDirectories(client AWSDSClientInterface, accountID string, region string) ([]dsTypes.DirectoryDescription, error) { 26 | var PaginationControl *string 27 | var directories []dsTypes.DirectoryDescription 28 | cacheKey := fmt.Sprintf("%s-ds-DescribeDirectories-%s", accountID, region) 29 | cached, found := internal.Cache.Get(cacheKey) 30 | if found { 31 | return cached.([]dsTypes.DirectoryDescription), nil 32 | } 33 | for { 34 | DescribeDirectories, err := client.DescribeDirectories( 35 | context.TODO(), 36 | &directoryservice.DescribeDirectoriesInput{ 37 | NextToken: PaginationControl, 38 | }, 39 | func(o *directoryservice.Options) { 40 | o.Region = region 41 | }, 42 | ) 43 | 44 | if err != nil { 45 | return directories, err 46 | } 47 | 48 | directories = append(directories, DescribeDirectories.DirectoryDescriptions...) 49 | 50 | //pagination 51 | if DescribeDirectories.NextToken == nil { 52 | break 53 | } 54 | PaginationControl = DescribeDirectories.NextToken 55 | } 56 | 57 | internal.Cache.Set(cacheKey, directories, cache.DefaultExpiration) 58 | return directories, nil 59 | } 60 | 61 | func CachedDSDescribeTrusts(client AWSDSClientInterface, accountID string, region string, directoryId string) ([]dsTypes.Trust, error) { 62 | var PaginationControl *string 63 | var trusts []dsTypes.Trust 64 | cacheKey := fmt.Sprintf("%s-ds-DescribeTrusts-%s-%s", accountID, region, directoryId) 65 | cached, found := internal.Cache.Get(cacheKey) 66 | if found { 67 | return cached.([]dsTypes.Trust), nil 68 | } 69 | for { 70 | DescribeDirectoryTrusts, err := client.DescribeTrusts( 71 | context.TODO(), 72 | &directoryservice.DescribeTrustsInput{ 73 | DirectoryId: &directoryId, 74 | NextToken: PaginationControl, 75 | }, 76 | func(o *directoryservice.Options) { 77 | o.Region = region 78 | }, 79 | ) 80 | if err != nil { 81 | return trusts, err 82 | } 83 | 84 | trusts = append(trusts, DescribeDirectoryTrusts.Trusts...) 85 | 86 | //pagination 87 | if DescribeDirectoryTrusts.NextToken == nil { 88 | break 89 | } 90 | PaginationControl = DescribeDirectoryTrusts.NextToken 91 | } 92 | internal.Cache.Set(cacheKey, trusts, cache.DefaultExpiration) 93 | 94 | return trusts, nil 95 | } 96 | -------------------------------------------------------------------------------- /aws/sdk/dynamodb.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/service/dynamodb" 10 | dynamoDBTypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" 11 | "github.com/patrickmn/go-cache" 12 | ) 13 | 14 | type DynamoDBClientInterface interface { 15 | ListTables(context.Context, *dynamodb.ListTablesInput, ...func(*dynamodb.Options)) (*dynamodb.ListTablesOutput, error) 16 | DescribeTable(context.Context, *dynamodb.DescribeTableInput, ...func(*dynamodb.Options)) (*dynamodb.DescribeTableOutput, error) 17 | } 18 | 19 | func init() { 20 | gob.Register([]string{}) 21 | gob.Register(dynamoDBTypes.TableDescription{}) 22 | } 23 | 24 | func CachedDynamoDBListTables(client DynamoDBClientInterface, accountID string, region string) ([]string, error) { 25 | var PaginationControl *string 26 | var tables []string 27 | cacheKey := fmt.Sprintf("%s-dynamodb-ListTables-%s", accountID, region) 28 | cached, found := internal.Cache.Get(cacheKey) 29 | if found { 30 | return cached.([]string), nil 31 | } 32 | for { 33 | ListTables, err := client.ListTables( 34 | context.TODO(), 35 | &dynamodb.ListTablesInput{ 36 | ExclusiveStartTableName: PaginationControl, 37 | }, 38 | func(o *dynamodb.Options) { 39 | o.Region = region 40 | }, 41 | ) 42 | 43 | if err != nil { 44 | return tables, err 45 | } 46 | 47 | tables = append(tables, ListTables.TableNames...) 48 | 49 | //pagination 50 | if ListTables.LastEvaluatedTableName == nil { 51 | break 52 | } 53 | PaginationControl = ListTables.LastEvaluatedTableName 54 | } 55 | 56 | internal.Cache.Set(cacheKey, tables, cache.DefaultExpiration) 57 | return tables, nil 58 | } 59 | 60 | func CachedDynamoDBDescribeTable(client DynamoDBClientInterface, accountID string, region string, tableName string) (dynamoDBTypes.TableDescription, error) { 61 | var tableDescription dynamoDBTypes.TableDescription 62 | cacheKey := fmt.Sprintf("%s-dynamodb-DescribeTable-%s-%s", accountID, region, tableName) 63 | cached, found := internal.Cache.Get(cacheKey) 64 | if found { 65 | return cached.(dynamoDBTypes.TableDescription), nil 66 | } 67 | 68 | DescribeTable, err := client.DescribeTable( 69 | context.TODO(), 70 | &dynamodb.DescribeTableInput{ 71 | TableName: &tableName, 72 | }, 73 | func(o *dynamodb.Options) { 74 | o.Region = region 75 | }, 76 | ) 77 | 78 | if err != nil { 79 | return tableDescription, err 80 | } 81 | tableDescription = *DescribeTable.Table 82 | 83 | internal.Cache.Set(cacheKey, tableDescription, cache.DefaultExpiration) 84 | return tableDescription, nil 85 | } 86 | -------------------------------------------------------------------------------- /aws/sdk/dynamodb_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/service/dynamodb" 7 | dynamodbTypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" 8 | ) 9 | 10 | type MockedAWSDynamoDBClient struct { 11 | } 12 | 13 | func (m *MockedAWSDynamoDBClient) DescribeTable(ctx context.Context, input *dynamodb.DescribeTableInput, options ...func(*dynamodb.Options)) (*dynamodb.DescribeTableOutput, error) { 14 | return &dynamodb.DescribeTableOutput{ 15 | Table: &dynamodbTypes.TableDescription{ 16 | TableName: input.TableName, 17 | }, 18 | }, nil 19 | } 20 | 21 | func (m *MockedAWSDynamoDBClient) ListTables(ctx context.Context, input *dynamodb.ListTablesInput, options ...func(*dynamodb.Options)) (*dynamodb.ListTablesOutput, error) { 22 | return &dynamodb.ListTablesOutput{ 23 | TableNames: []string{"table1", "table2"}, 24 | }, nil 25 | } 26 | -------------------------------------------------------------------------------- /aws/sdk/ecr_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/aws/aws-sdk-go-v2/aws" 8 | "github.com/aws/aws-sdk-go-v2/service/ecr" 9 | ecrTypes "github.com/aws/aws-sdk-go-v2/service/ecr/types" 10 | ) 11 | 12 | type MockedECRClient struct { 13 | } 14 | 15 | func (m *MockedECRClient) DescribeRepositories(ctx context.Context, input *ecr.DescribeRepositoriesInput, options ...func(*ecr.Options)) (*ecr.DescribeRepositoriesOutput, error) { 16 | return &ecr.DescribeRepositoriesOutput{ 17 | Repositories: []ecrTypes.Repository{ 18 | { 19 | RepositoryName: aws.String("repo1"), 20 | RepositoryUri: aws.String("11111111111111.dkr.ecr.us-east-1.amazonaws.com/repo1"), 21 | }, 22 | { 23 | RepositoryName: aws.String("repo2"), 24 | RepositoryUri: aws.String("11111111111111.dkr.ecr.us-east-1.amazonaws.com/repo2"), 25 | }, 26 | }, 27 | }, nil 28 | } 29 | 30 | func (m *MockedECRClient) DescribeImages(ctx context.Context, input *ecr.DescribeImagesInput, options ...func(*ecr.Options)) (*ecr.DescribeImagesOutput, error) { 31 | if aws.ToString(input.RepositoryName) == "repo1" { 32 | return &ecr.DescribeImagesOutput{ 33 | ImageDetails: []ecrTypes.ImageDetail{ 34 | { 35 | ImageTags: []string{ 36 | "customtag", 37 | "tag2", 38 | }, 39 | ImagePushedAt: aws.Time(time.Date(2022, 10, 25, 15, 14, 0, 0, time.UTC)), 40 | ImageSizeInBytes: aws.Int64(123456), 41 | }, 42 | }, 43 | }, nil 44 | } else if aws.ToString(input.RepositoryName) == "repo2" { 45 | return &ecr.DescribeImagesOutput{ 46 | ImageDetails: []ecrTypes.ImageDetail{ 47 | { 48 | ImageTags: []string{ 49 | "latest", 50 | }, 51 | ImagePushedAt: aws.Time(time.Date(2021, 10, 15, 11, 14, 0, 0, time.UTC)), 52 | ImageSizeInBytes: aws.Int64(2222222), 53 | }, 54 | }, 55 | }, nil 56 | } else { 57 | return &ecr.DescribeImagesOutput{ 58 | ImageDetails: []ecrTypes.ImageDetail{ 59 | { 60 | ImageTags: []string{ 61 | "customtag", 62 | "tag2", 63 | }, 64 | ImagePushedAt: aws.Time(time.Date(2022, 10, 25, 15, 14, 0, 0, time.UTC)), 65 | ImageSizeInBytes: aws.Int64(111), 66 | }, 67 | { 68 | ImageTags: []string{ 69 | "latest", 70 | }, 71 | ImagePushedAt: aws.Time(time.Date(2021, 10, 15, 11, 14, 0, 0, time.UTC)), 72 | ImageSizeInBytes: aws.Int64(333), 73 | }, 74 | }, 75 | }, nil 76 | } 77 | 78 | } 79 | 80 | func (m *MockedECRClient) GetRepositoryPolicy(ctx context.Context, input *ecr.GetRepositoryPolicyInput, options ...func(*ecr.Options)) (*ecr.GetRepositoryPolicyOutput, error) { 81 | return &ecr.GetRepositoryPolicyOutput{ 82 | PolicyText: aws.String(`{ 83 | "Version": "2008-10-17", 84 | "Statement": [ 85 | { 86 | "Sid": "AllowPushPull", 87 | "Effect": "Allow", 88 | "Principal": { 89 | "AWS": [ 90 | "arn:aws:iam::123456789012:root", 91 | "arn:aws:iam::123456789012:user/MyUser" 92 | ] 93 | }, 94 | "Action": [ 95 | 96 | "ecr:GetDownloadUrlForLayer", 97 | "ecr:BatchGetImage", 98 | "ecr:BatchCheckLayerAvailability", 99 | "ecr:PutImage", 100 | "ecr:InitiateLayerUpload", 101 | "ecr:UploadLayerPart", 102 | "ecr:CompleteLayerUpload" 103 | ] 104 | 105 | } 106 | ] 107 | }`), 108 | }, nil 109 | } 110 | -------------------------------------------------------------------------------- /aws/sdk/efs_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/efs" 8 | efsTypes "github.com/aws/aws-sdk-go-v2/service/efs/types" 9 | ) 10 | 11 | type MockedEfsClient struct { 12 | } 13 | 14 | func (m *MockedEfsClient) DescribeFileSystems(ctx context.Context, input *efs.DescribeFileSystemsInput, options ...func(*efs.Options)) (*efs.DescribeFileSystemsOutput, error) { 15 | return &efs.DescribeFileSystemsOutput{ 16 | FileSystems: []efsTypes.FileSystemDescription{ 17 | { 18 | FileSystemId: aws.String("fs-12345678"), 19 | }, 20 | { 21 | FileSystemId: aws.String("fs-87654321"), 22 | }, 23 | }, 24 | }, nil 25 | } 26 | 27 | func (m *MockedEfsClient) DescribeMountTargets(ctx context.Context, input *efs.DescribeMountTargetsInput, options ...func(*efs.Options)) (*efs.DescribeMountTargetsOutput, error) { 28 | return &efs.DescribeMountTargetsOutput{ 29 | MountTargets: []efsTypes.MountTargetDescription{ 30 | { 31 | MountTargetId: aws.String("fsmt-12345678"), 32 | FileSystemId: aws.String("fs-12345678"), 33 | IpAddress: aws.String("10.1.1.1"), 34 | }, 35 | { 36 | MountTargetId: aws.String("fsmt-87654321"), 37 | FileSystemId: aws.String("fs-87654321"), 38 | IpAddress: aws.String("10.2.2.2.2"), 39 | }, 40 | }, 41 | }, nil 42 | } 43 | 44 | func (m *MockedEfsClient) DescribeAccessPoints(ctx context.Context, input *efs.DescribeAccessPointsInput, options ...func(*efs.Options)) (*efs.DescribeAccessPointsOutput, error) { 45 | return &efs.DescribeAccessPointsOutput{ 46 | AccessPoints: []efsTypes.AccessPointDescription{ 47 | { 48 | AccessPointId: aws.String("fsap-12345678"), 49 | FileSystemId: aws.String("fs-12345678"), 50 | Name: aws.String("fsap-12345678"), 51 | PosixUser: &efsTypes.PosixUser{ 52 | Gid: aws.Int64(1000), 53 | Uid: aws.Int64(1000), 54 | }, 55 | }, 56 | { 57 | AccessPointId: aws.String("fsap-87654321"), 58 | FileSystemId: aws.String("fs-87654321"), 59 | Name: aws.String("fsap-12345679"), 60 | PosixUser: &efsTypes.PosixUser{ 61 | Gid: aws.Int64(1000), 62 | Uid: aws.Int64(1000), 63 | }, 64 | }, 65 | }, 66 | }, nil 67 | } 68 | -------------------------------------------------------------------------------- /aws/sdk/eks_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/eks" 8 | eksTypes "github.com/aws/aws-sdk-go-v2/service/eks/types" 9 | ) 10 | 11 | type MockedAWSEksClient struct { 12 | } 13 | 14 | func (m *MockedAWSEksClient) ListClusters(ctx context.Context, input *eks.ListClustersInput, options ...func(*eks.Options)) (*eks.ListClustersOutput, error) { 15 | return &eks.ListClustersOutput{ 16 | Clusters: []string{ 17 | "cluster1", 18 | "cluster2", 19 | }, 20 | }, nil 21 | } 22 | 23 | func (m *MockedAWSEksClient) DescribeCluster(ctx context.Context, input *eks.DescribeClusterInput, options ...func(*eks.Options)) (*eks.DescribeClusterOutput, error) { 24 | return &eks.DescribeClusterOutput{ 25 | Cluster: &eksTypes.Cluster{ 26 | Arn: aws.String("arn:aws:eks:us-east-1:123456789012:cluster/cluster1"), 27 | Endpoint: aws.String("https://cluster1.us-east-1.eks.amazonaws.com"), 28 | Name: aws.String("cluster1"), 29 | PlatformVersion: aws.String("eks.1"), 30 | Version: aws.String("1.18"), 31 | RoleArn: aws.String("arn:aws:iam::123456789012:role/eks-role"), 32 | }, 33 | }, nil 34 | } 35 | 36 | func (m *MockedAWSEksClient) ListNodegroups(ctx context.Context, input *eks.ListNodegroupsInput, options ...func(*eks.Options)) (*eks.ListNodegroupsOutput, error) { 37 | return &eks.ListNodegroupsOutput{ 38 | Nodegroups: []string{ 39 | "nodegroup1", 40 | "nodegroup2", 41 | }, 42 | }, nil 43 | } 44 | 45 | func (m *MockedAWSEksClient) DescribeNodegroup(ctx context.Context, input *eks.DescribeNodegroupInput, options ...func(*eks.Options)) (*eks.DescribeNodegroupOutput, error) { 46 | return &eks.DescribeNodegroupOutput{ 47 | Nodegroup: &eksTypes.Nodegroup{ 48 | ClusterName: aws.String("cluster1"), 49 | NodeRole: aws.String("arn:aws:iam::123456789012:role/eks-role"), 50 | NodegroupName: aws.String("nodegroup1"), 51 | NodegroupArn: aws.String("arn:aws:eks:us-east-1:123456789012:nodegroup/cluster1/nodegroup1"), 52 | }, 53 | }, nil 54 | } 55 | -------------------------------------------------------------------------------- /aws/sdk/elasticache.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/service/elasticache" 10 | elasticacheTypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types" 11 | "github.com/patrickmn/go-cache" 12 | ) 13 | 14 | type AWSElastiCacheClientInterface interface { 15 | DescribeCacheClusters(context.Context, *elasticache.DescribeCacheClustersInput, ...func(*elasticache.Options)) (*elasticache.DescribeCacheClustersOutput, error) 16 | } 17 | 18 | func init() { 19 | gob.Register([]elasticacheTypes.CacheCluster{}) 20 | 21 | } 22 | 23 | func CachedElastiCacheDescribeCacheClusters(client AWSElastiCacheClientInterface, accountID string, region string) ([]elasticacheTypes.CacheCluster, error) { 24 | var PaginationControl *string 25 | var clusters []elasticacheTypes.CacheCluster 26 | cacheKey := fmt.Sprintf("%s-elasticache-DescribeCacheClusters-%s", accountID, region) 27 | cached, found := internal.Cache.Get(cacheKey) 28 | if found { 29 | return cached.([]elasticacheTypes.CacheCluster), nil 30 | } 31 | for { 32 | DescribeCacheClusters, err := client.DescribeCacheClusters( 33 | context.TODO(), 34 | &elasticache.DescribeCacheClustersInput{ 35 | Marker: PaginationControl, 36 | }, 37 | func(o *elasticache.Options) { 38 | o.Region = region 39 | }, 40 | ) 41 | 42 | if err != nil { 43 | return clusters, err 44 | } 45 | 46 | clusters = append(clusters, DescribeCacheClusters.CacheClusters...) 47 | 48 | //pagination 49 | if DescribeCacheClusters.Marker == nil { 50 | break 51 | } 52 | PaginationControl = DescribeCacheClusters.Marker 53 | } 54 | 55 | internal.Cache.Set(cacheKey, clusters, cache.DefaultExpiration) 56 | return clusters, nil 57 | } 58 | -------------------------------------------------------------------------------- /aws/sdk/elasticache_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/elasticache" 8 | elasticacheTypes "github.com/aws/aws-sdk-go-v2/service/elasticache/types" 9 | ) 10 | 11 | type MockedElasticacheClient struct { 12 | } 13 | 14 | func (m *MockedElasticacheClient) DescribeCacheClusters(ctx context.Context, input *elasticache.DescribeCacheClustersInput, options ...func(*elasticache.Options)) (*elasticache.DescribeCacheClustersOutput, error) { 15 | return &elasticache.DescribeCacheClustersOutput{ 16 | CacheClusters: []elasticacheTypes.CacheCluster{ 17 | { 18 | CacheClusterId: aws.String("test"), 19 | ARN: aws.String("arn:aws:elasticache:us-east-1:123456789012:cluster:myCluster"), 20 | Engine: aws.String("redis"), 21 | EngineVersion: aws.String("6.x"), 22 | CacheNodeType: aws.String("cache.t3.micro"), 23 | NumCacheNodes: aws.Int32(1), 24 | CacheClusterStatus: aws.String("available"), 25 | PreferredAvailabilityZone: aws.String("us-east-1a"), 26 | CacheSubnetGroupName: aws.String("default"), 27 | ReplicationGroupId: aws.String("test"), 28 | SecurityGroups: []elasticacheTypes.SecurityGroupMembership{ 29 | { 30 | SecurityGroupId: aws.String("test"), 31 | Status: aws.String("active"), 32 | }, 33 | }, 34 | 35 | AutoMinorVersionUpgrade: aws.Bool(true), 36 | PreferredMaintenanceWindow: aws.String("sun:05:00-sun:06:00"), 37 | }, 38 | }, 39 | }, nil 40 | } 41 | -------------------------------------------------------------------------------- /aws/sdk/elasticbeanstalk.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | 7 | "github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk" 8 | elasticbeanstalkTypes "github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk/types" 9 | ) 10 | 11 | type AWSElasticBeanstalkClientInterface interface { 12 | DescribeApplications(context.Context, *elasticbeanstalk.DescribeApplicationsInput, ...func(*elasticbeanstalk.Options)) (*elasticbeanstalk.DescribeApplicationsOutput, error) 13 | } 14 | 15 | func init() { 16 | gob.Register([]elasticbeanstalkTypes.ApplicationDescription{}) 17 | } 18 | 19 | func CachedElasticBeanstalkDescribeApplications(client AWSElasticBeanstalkClientInterface, accountID string, region string) ([]elasticbeanstalkTypes.ApplicationDescription, error) { 20 | var applications []elasticbeanstalkTypes.ApplicationDescription 21 | DescribeApplications, err := client.DescribeApplications( 22 | context.TODO(), 23 | &elasticbeanstalk.DescribeApplicationsInput{}, 24 | func(o *elasticbeanstalk.Options) { 25 | o.Region = region 26 | }, 27 | ) 28 | 29 | if err != nil { 30 | return applications, err 31 | } 32 | 33 | applications = append(applications, DescribeApplications.Applications...) 34 | return applications, nil 35 | } 36 | -------------------------------------------------------------------------------- /aws/sdk/elasticbeanstalk_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk" 8 | elasticbeanstalkTypes "github.com/aws/aws-sdk-go-v2/service/elasticbeanstalk/types" 9 | ) 10 | 11 | type MockedElasticBeanstalkClient struct { 12 | } 13 | 14 | func (m *MockedElasticBeanstalkClient) DescribeApplications(ctx context.Context, input *elasticbeanstalk.DescribeApplicationsInput, options ...func(*elasticbeanstalk.Options)) (*elasticbeanstalk.DescribeApplicationsOutput, error) { 15 | return &elasticbeanstalk.DescribeApplicationsOutput{ 16 | Applications: []elasticbeanstalkTypes.ApplicationDescription{ 17 | { 18 | ApplicationName: aws.String("app1"), 19 | ApplicationArn: aws.String("arn:aws:elasticbeanstalk:us-east-1:123456789012:application/app1"), 20 | ConfigurationTemplates: []string{ 21 | "template1", 22 | }, 23 | }, 24 | { 25 | ApplicationName: aws.String("app2"), 26 | ApplicationArn: aws.String("arn:aws:elasticbeanstalk:us-east-1:123456789012:application/app2"), 27 | ConfigurationTemplates: []string{ 28 | "template2", 29 | }, 30 | }, 31 | }, 32 | }, nil 33 | } 34 | -------------------------------------------------------------------------------- /aws/sdk/elb.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing" 10 | elbTypes "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing/types" 11 | "github.com/patrickmn/go-cache" 12 | ) 13 | 14 | type ELBClientInterface interface { 15 | DescribeLoadBalancers(context.Context, *elasticloadbalancing.DescribeLoadBalancersInput, ...func(*elasticloadbalancing.Options)) (*elasticloadbalancing.DescribeLoadBalancersOutput, error) 16 | } 17 | 18 | func init() { 19 | gob.Register([]elbTypes.LoadBalancerDescription{}) 20 | } 21 | 22 | func CachedELBDescribeLoadBalancers(client ELBClientInterface, accountID string, region string) ([]elbTypes.LoadBalancerDescription, error) { 23 | var PaginationControl *string 24 | var loadbalancers []elbTypes.LoadBalancerDescription 25 | cacheKey := fmt.Sprintf("%s-elb-DescribeLoadBalancers-%s", accountID, region) 26 | cached, found := internal.Cache.Get(cacheKey) 27 | if found { 28 | return cached.([]elbTypes.LoadBalancerDescription), nil 29 | } 30 | for { 31 | DescribeLoadBalancers, err := client.DescribeLoadBalancers( 32 | context.TODO(), 33 | &elasticloadbalancing.DescribeLoadBalancersInput{ 34 | Marker: PaginationControl, 35 | }, 36 | func(o *elasticloadbalancing.Options) { 37 | o.Region = region 38 | }, 39 | ) 40 | 41 | if err != nil { 42 | return loadbalancers, err 43 | } 44 | 45 | loadbalancers = append(loadbalancers, DescribeLoadBalancers.LoadBalancerDescriptions...) 46 | 47 | //pagination 48 | if DescribeLoadBalancers.NextMarker == nil { 49 | break 50 | } 51 | PaginationControl = DescribeLoadBalancers.NextMarker 52 | } 53 | 54 | internal.Cache.Set(cacheKey, loadbalancers, cache.DefaultExpiration) 55 | return loadbalancers, nil 56 | } 57 | -------------------------------------------------------------------------------- /aws/sdk/elb_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing" 8 | elbTypes "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing/types" 9 | ) 10 | 11 | type MockedElbClient struct { 12 | } 13 | 14 | func (m *MockedElbClient) DescribeLoadBalancers(ctx context.Context, input *elasticloadbalancing.DescribeLoadBalancersInput, options ...func(*elasticloadbalancing.Options)) (*elasticloadbalancing.DescribeLoadBalancersOutput, error) { 15 | return &elasticloadbalancing.DescribeLoadBalancersOutput{ 16 | LoadBalancerDescriptions: []elbTypes.LoadBalancerDescription{ 17 | { 18 | LoadBalancerName: aws.String("elb1"), 19 | DNSName: aws.String("elb1"), 20 | Instances: []elbTypes.Instance{ 21 | { 22 | InstanceId: aws.String("i-1234567890abcdef0"), 23 | }, 24 | }, 25 | }, 26 | { 27 | LoadBalancerName: aws.String("elb2"), 28 | DNSName: aws.String("elb2"), 29 | Instances: []elbTypes.Instance{ 30 | { 31 | InstanceId: aws.String("i-1234567890abcdef1"), 32 | }, 33 | }, 34 | }, 35 | }, 36 | }, nil 37 | } 38 | -------------------------------------------------------------------------------- /aws/sdk/elbv2.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" 10 | elbV2Types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" 11 | "github.com/patrickmn/go-cache" 12 | ) 13 | 14 | type ELBv2ClientInterface interface { 15 | DescribeLoadBalancers(context.Context, *elasticloadbalancingv2.DescribeLoadBalancersInput, ...func(*elasticloadbalancingv2.Options)) (*elasticloadbalancingv2.DescribeLoadBalancersOutput, error) 16 | } 17 | 18 | func init() { 19 | gob.Register([]elbV2Types.LoadBalancer{}) 20 | } 21 | 22 | func CachedELBv2DescribeLoadBalancers(client ELBv2ClientInterface, accountID string, region string) ([]elbV2Types.LoadBalancer, error) { 23 | var PaginationControl *string 24 | var loadbalancers []elbV2Types.LoadBalancer 25 | cacheKey := fmt.Sprintf("%s-elbv2-DescribeLoadBalancers-%s", accountID, region) 26 | cached, found := internal.Cache.Get(cacheKey) 27 | if found { 28 | return cached.([]elbV2Types.LoadBalancer), nil 29 | } 30 | for { 31 | DescribeLoadBalancers, err := client.DescribeLoadBalancers( 32 | context.TODO(), 33 | &elasticloadbalancingv2.DescribeLoadBalancersInput{ 34 | Marker: PaginationControl, 35 | }, 36 | func(o *elasticloadbalancingv2.Options) { 37 | o.Region = region 38 | }, 39 | ) 40 | 41 | if err != nil { 42 | return loadbalancers, err 43 | } 44 | 45 | loadbalancers = append(loadbalancers, DescribeLoadBalancers.LoadBalancers...) 46 | 47 | //pagination 48 | if DescribeLoadBalancers.NextMarker == nil { 49 | break 50 | } 51 | PaginationControl = DescribeLoadBalancers.NextMarker 52 | } 53 | 54 | internal.Cache.Set(cacheKey, loadbalancers, cache.DefaultExpiration) 55 | return loadbalancers, nil 56 | } 57 | -------------------------------------------------------------------------------- /aws/sdk/elbv2_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/aws/aws-sdk-go-v2/aws" 8 | "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" 9 | elbv2Types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" 10 | ) 11 | 12 | type MockedElbv2Client struct { 13 | } 14 | 15 | func (m *MockedElbv2Client) DescribeLoadBalancers(ctx context.Context, input *elasticloadbalancingv2.DescribeLoadBalancersInput, options ...func(*elasticloadbalancingv2.Options)) (*elasticloadbalancingv2.DescribeLoadBalancersOutput, error) { 16 | return &elasticloadbalancingv2.DescribeLoadBalancersOutput{ 17 | LoadBalancers: []elbv2Types.LoadBalancer{ 18 | { 19 | LoadBalancerArn: aws.String("arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188"), 20 | DNSName: aws.String("my-load-balancer-424835706.us-east-1.elb.amazonaws.com"), 21 | CanonicalHostedZoneId: aws.String("Z2P70J7EXAMPLE"), 22 | CreatedTime: aws.Time(time.Now()), 23 | LoadBalancerName: aws.String("my-load-balancer"), 24 | Scheme: elbv2Types.LoadBalancerSchemeEnumInternetFacing, 25 | VpcId: aws.String("vpc-3ac0fb5f"), 26 | State: &elbv2Types.LoadBalancerState{ 27 | Code: elbv2Types.LoadBalancerStateEnumActive, 28 | Reason: aws.String(""), 29 | }, 30 | Type: elbv2Types.LoadBalancerTypeEnumApplication, 31 | AvailabilityZones: []elbv2Types.AvailabilityZone{ 32 | { 33 | ZoneName: aws.String("us-east-1a"), 34 | SubnetId: aws.String("subnet-8360a9e7"), 35 | LoadBalancerAddresses: []elbv2Types.LoadBalancerAddress{ 36 | { 37 | IpAddress: aws.String("1.2.3.4"), 38 | }, 39 | }, 40 | }, 41 | { 42 | ZoneName: aws.String("us-east-1b"), 43 | SubnetId: aws.String("subnet-b7d581c0"), 44 | LoadBalancerAddresses: []elbv2Types.LoadBalancerAddress{ 45 | { 46 | IpAddress: aws.String("2.3.4.5"), 47 | }, 48 | }, 49 | }, 50 | }, 51 | SecurityGroups: []string{ 52 | "sg-5943793c", 53 | }, 54 | IpAddressType: elbv2Types.IpAddressTypeDualstack, 55 | }, 56 | }, 57 | }, nil 58 | } 59 | -------------------------------------------------------------------------------- /aws/sdk/emr.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/service/emr" 10 | emrTypes "github.com/aws/aws-sdk-go-v2/service/emr/types" 11 | "github.com/patrickmn/go-cache" 12 | ) 13 | 14 | type AWSEMRClientInterface interface { 15 | ListClusters(context.Context, *emr.ListClustersInput, ...func(*emr.Options)) (*emr.ListClustersOutput, error) 16 | ListInstances(context.Context, *emr.ListInstancesInput, ...func(*emr.Options)) (*emr.ListInstancesOutput, error) 17 | } 18 | 19 | func init() { 20 | gob.Register([]emrTypes.ClusterSummary{}) 21 | 22 | //need to do this to avoid conflicts with the Instance type in the ec2 package 23 | type EMRInstance emrTypes.Instance 24 | gob.Register([]EMRInstance{}) 25 | } 26 | 27 | func CachedEMRListClusters(client AWSEMRClientInterface, accountID string, region string) ([]emrTypes.ClusterSummary, error) { 28 | var PaginationControl *string 29 | var clusters []emrTypes.ClusterSummary 30 | cacheKey := fmt.Sprintf("%s-emr-ListClusters-%s", accountID, region) 31 | cached, found := internal.Cache.Get(cacheKey) 32 | if found { 33 | return cached.([]emrTypes.ClusterSummary), nil 34 | } 35 | for { 36 | ListClusters, err := client.ListClusters( 37 | context.TODO(), 38 | &emr.ListClustersInput{ 39 | Marker: PaginationControl, 40 | }, 41 | func(o *emr.Options) { 42 | o.Region = region 43 | }, 44 | ) 45 | 46 | if err != nil { 47 | return clusters, err 48 | } 49 | 50 | clusters = append(clusters, ListClusters.Clusters...) 51 | 52 | //pagination 53 | if ListClusters.Marker == nil { 54 | break 55 | } 56 | PaginationControl = ListClusters.Marker 57 | } 58 | 59 | internal.Cache.Set(cacheKey, clusters, cache.DefaultExpiration) 60 | return clusters, nil 61 | } 62 | 63 | func CachedEMRListInstances(client AWSEMRClientInterface, accountID string, region string, clusterID string) ([]emrTypes.Instance, error) { 64 | var PaginationControl *string 65 | var instances []emrTypes.Instance 66 | cacheKey := fmt.Sprintf("%s-emr-ListInstances-%s-%s", accountID, region, clusterID) 67 | cached, found := internal.Cache.Get(cacheKey) 68 | if found { 69 | return cached.([]emrTypes.Instance), nil 70 | } 71 | for { 72 | ListInstances, err := client.ListInstances( 73 | context.TODO(), 74 | &emr.ListInstancesInput{ 75 | ClusterId: &clusterID, 76 | Marker: PaginationControl, 77 | }, 78 | func(o *emr.Options) { 79 | o.Region = region 80 | }, 81 | ) 82 | 83 | if err != nil { 84 | return instances, err 85 | } 86 | 87 | instances = append(instances, ListInstances.Instances...) 88 | 89 | //pagination 90 | if ListInstances.Marker == nil { 91 | break 92 | } 93 | PaginationControl = ListInstances.Marker 94 | } 95 | 96 | internal.Cache.Set(cacheKey, instances, cache.DefaultExpiration) 97 | return instances, nil 98 | } 99 | -------------------------------------------------------------------------------- /aws/sdk/emr_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/emr" 8 | emrTypes "github.com/aws/aws-sdk-go-v2/service/emr/types" 9 | ) 10 | 11 | type MockedEMRClient struct { 12 | } 13 | 14 | func (m *MockedEMRClient) ListClusters(ctx context.Context, input *emr.ListClustersInput, options ...func(*emr.Options)) (*emr.ListClustersOutput, error) { 15 | return &emr.ListClustersOutput{ 16 | Clusters: []emrTypes.ClusterSummary{ 17 | { 18 | Id: aws.String("cluster1"), 19 | }, 20 | { 21 | Id: aws.String("cluster2"), 22 | }, 23 | }, 24 | }, nil 25 | } 26 | 27 | func (m *MockedEMRClient) ListInstances(ctx context.Context, input *emr.ListInstancesInput, options ...func(*emr.Options)) (*emr.ListInstancesOutput, error) { 28 | return &emr.ListInstancesOutput{ 29 | Instances: []emrTypes.Instance{ 30 | { 31 | Id: aws.String("instance1"), 32 | InstanceType: aws.String("m5.xlarge"), 33 | Ec2InstanceId: aws.String("i-1234567890"), 34 | PrivateDnsName: aws.String("ip-10-0-0-1.ec2.internal"), 35 | PublicDnsName: aws.String("ec2-1-2-3-4.compute-1.amazonaws.com"), 36 | PrivateIpAddress: aws.String("10.0.0.1"), 37 | PublicIpAddress: aws.String("1.2.3.4"), 38 | }, 39 | { 40 | Id: aws.String("instance2"), 41 | }, 42 | }, 43 | }, nil 44 | } 45 | -------------------------------------------------------------------------------- /aws/sdk/glue_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/glue" 8 | glueTypes "github.com/aws/aws-sdk-go-v2/service/glue/types" 9 | ) 10 | 11 | type MockedGlueClient struct { 12 | } 13 | 14 | func (m *MockedGlueClient) ListDevEndpoints(ctx context.Context, input *glue.ListDevEndpointsInput, options ...func(*glue.Options)) (*glue.ListDevEndpointsOutput, error) { 15 | return &glue.ListDevEndpointsOutput{ 16 | DevEndpointNames: []string{ 17 | "devendpoint1", 18 | "devendpoint2", 19 | }, 20 | }, nil 21 | } 22 | 23 | func (m *MockedGlueClient) ListJobs(ctx context.Context, input *glue.ListJobsInput, options ...func(*glue.Options)) (*glue.ListJobsOutput, error) { 24 | return &glue.ListJobsOutput{ 25 | JobNames: []string{ 26 | "job1", 27 | "job2", 28 | }, 29 | }, nil 30 | } 31 | 32 | func (m *MockedGlueClient) GetTables(ctx context.Context, input *glue.GetTablesInput, options ...func(*glue.Options)) (*glue.GetTablesOutput, error) { 33 | return &glue.GetTablesOutput{ 34 | TableList: []glueTypes.Table{ 35 | { 36 | Name: aws.String("table1"), 37 | DatabaseName: aws.String("database1"), 38 | Description: aws.String("description1"), 39 | Parameters: map[string]string{ 40 | "param1": "value1", 41 | "param2": "value2", 42 | }, 43 | }, 44 | { 45 | Name: aws.String("table2"), 46 | DatabaseName: aws.String("database2"), 47 | Description: aws.String("description2"), 48 | Parameters: map[string]string{ 49 | "param1": "value1", 50 | "param2": "value2", 51 | }, 52 | }, 53 | }, 54 | }, nil 55 | } 56 | 57 | func (m *MockedGlueClient) GetDatabases(ctx context.Context, input *glue.GetDatabasesInput, options ...func(*glue.Options)) (*glue.GetDatabasesOutput, error) { 58 | return &glue.GetDatabasesOutput{ 59 | DatabaseList: []glueTypes.Database{ 60 | { 61 | Name: aws.String("database1"), 62 | Description: aws.String("description1"), 63 | LocationUri: aws.String("s3://bucket1"), 64 | Parameters: map[string]string{ 65 | "param1": "value1", 66 | "param2": "value2", 67 | }, 68 | }, 69 | { 70 | Name: aws.String("database2"), 71 | Description: aws.String("description2"), 72 | LocationUri: aws.String("s3://bucket2"), 73 | Parameters: map[string]string{ 74 | "param1": "value1", 75 | "param2": "value2", 76 | }, 77 | }, 78 | }, 79 | }, nil 80 | } 81 | -------------------------------------------------------------------------------- /aws/sdk/grafana.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/service/grafana" 10 | grafanaTypes "github.com/aws/aws-sdk-go-v2/service/grafana/types" 11 | "github.com/patrickmn/go-cache" 12 | ) 13 | 14 | type GrafanaClientInterface interface { 15 | ListWorkspaces(context.Context, *grafana.ListWorkspacesInput, ...func(*grafana.Options)) (*grafana.ListWorkspacesOutput, error) 16 | } 17 | 18 | func init() { 19 | gob.Register([]grafanaTypes.WorkspaceSummary{}) 20 | } 21 | 22 | func CachedGrafanaListWorkspaces(client GrafanaClientInterface, accountID string, region string) ([]grafanaTypes.WorkspaceSummary, error) { 23 | var PaginationControl *string 24 | var workspaces []grafanaTypes.WorkspaceSummary 25 | cacheKey := fmt.Sprintf("%s-grafana-ListWorkspaces-%s", accountID, region) 26 | cached, found := internal.Cache.Get(cacheKey) 27 | if found { 28 | return cached.([]grafanaTypes.WorkspaceSummary), nil 29 | } 30 | 31 | for { 32 | 33 | ListWorkspaces, err := client.ListWorkspaces( 34 | context.TODO(), 35 | &grafana.ListWorkspacesInput{ 36 | NextToken: PaginationControl, 37 | }, 38 | func(o *grafana.Options) { 39 | o.Region = region 40 | }, 41 | ) 42 | if err != nil { 43 | return workspaces, err 44 | 45 | } 46 | 47 | workspaces = append(workspaces, ListWorkspaces.Workspaces...) 48 | 49 | //pagination 50 | if ListWorkspaces.NextToken == nil { 51 | break 52 | } 53 | PaginationControl = ListWorkspaces.NextToken 54 | } 55 | 56 | internal.Cache.Set(cacheKey, workspaces, cache.DefaultExpiration) 57 | return workspaces, nil 58 | } 59 | -------------------------------------------------------------------------------- /aws/sdk/grafana_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/aws/aws-sdk-go-v2/aws" 8 | "github.com/aws/aws-sdk-go-v2/service/grafana" 9 | grafanaTypes "github.com/aws/aws-sdk-go-v2/service/grafana/types" 10 | ) 11 | 12 | type MockedGrafanaClient struct { 13 | } 14 | 15 | func (m *MockedGrafanaClient) ListWorkspaces(ctx context.Context, input *grafana.ListWorkspacesInput, options ...func(*grafana.Options)) (*grafana.ListWorkspacesOutput, error) { 16 | return &grafana.ListWorkspacesOutput{ 17 | Workspaces: []grafanaTypes.WorkspaceSummary{ 18 | { 19 | Authentication: &grafanaTypes.AuthenticationSummary{ 20 | Providers: []grafanaTypes.AuthenticationProviderTypes{ 21 | grafanaTypes.AuthenticationProviderTypesAwsSso, 22 | }, 23 | }, 24 | Created: aws.Time(time.Now()), 25 | Id: aws.String("workspace1"), 26 | Name: aws.String("workspace1"), 27 | Status: grafanaTypes.WorkspaceStatusActive, 28 | }, 29 | { 30 | Authentication: &grafanaTypes.AuthenticationSummary{ 31 | Providers: []grafanaTypes.AuthenticationProviderTypes{ 32 | grafanaTypes.AuthenticationProviderTypesAwsSso, 33 | }, 34 | }, 35 | Created: aws.Time(time.Now()), 36 | Id: aws.String("workspace2"), 37 | Name: aws.String("workspace2"), 38 | Status: grafanaTypes.WorkspaceStatusActive, 39 | }, 40 | }, 41 | }, nil 42 | } 43 | -------------------------------------------------------------------------------- /aws/sdk/kinesis.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/service/kinesis" 10 | "github.com/patrickmn/go-cache" 11 | ) 12 | 13 | type AWSKinesisClientInterface interface { 14 | ListStreams(context.Context, *kinesis.ListStreamsInput, ...func(*kinesis.Options)) (*kinesis.ListStreamsOutput, error) 15 | } 16 | 17 | func init() { 18 | gob.Register([]string{}) 19 | } 20 | 21 | func CachedKinesisListStreams(client AWSKinesisClientInterface, accountID string, region string) ([]string, error) { 22 | var PaginationControl *string 23 | var streams []string 24 | cacheKey := fmt.Sprintf("%s-kinesis-ListStreams-%s", accountID, region) 25 | cached, found := internal.Cache.Get(cacheKey) 26 | if found { 27 | return cached.([]string), nil 28 | } 29 | for { 30 | ListStreams, err := client.ListStreams( 31 | context.TODO(), 32 | &kinesis.ListStreamsInput{ 33 | NextToken: PaginationControl, 34 | }, 35 | func(o *kinesis.Options) { 36 | o.Region = region 37 | }, 38 | ) 39 | 40 | if err != nil { 41 | return streams, err 42 | } 43 | 44 | streams = append(streams, ListStreams.StreamNames...) 45 | 46 | //pagination 47 | if ListStreams.NextToken == nil { 48 | break 49 | } 50 | PaginationControl = ListStreams.NextToken 51 | } 52 | 53 | internal.Cache.Set(cacheKey, streams, cache.DefaultExpiration) 54 | return streams, nil 55 | } 56 | -------------------------------------------------------------------------------- /aws/sdk/kinesis_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/kinesis" 8 | ) 9 | 10 | type MockedKinesisClient struct { 11 | } 12 | 13 | func (m *MockedKinesisClient) ListStreams(ctx context.Context, input *kinesis.ListStreamsInput, options ...func(*kinesis.Options)) (*kinesis.ListStreamsOutput, error) { 14 | return &kinesis.ListStreamsOutput{ 15 | HasMoreStreams: aws.Bool(false), 16 | StreamNames: []string{ 17 | "stream1", 18 | "stream2", 19 | }, 20 | }, nil 21 | } 22 | -------------------------------------------------------------------------------- /aws/sdk/lambda_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/lambda" 8 | lambdaTypes "github.com/aws/aws-sdk-go-v2/service/lambda/types" 9 | ) 10 | 11 | type MockedLambdaClient struct { 12 | } 13 | 14 | func (m *MockedLambdaClient) ListFunctions(ctx context.Context, input *lambda.ListFunctionsInput, options ...func(*lambda.Options)) (*lambda.ListFunctionsOutput, error) { 15 | return &lambda.ListFunctionsOutput{ 16 | Functions: []lambdaTypes.FunctionConfiguration{ 17 | { 18 | FunctionArn: aws.String("arn:aws:lambda:us-east-1:123456789012:function:my-function"), 19 | FunctionName: aws.String("my-function"), 20 | Handler: aws.String("index.handler"), 21 | Runtime: lambdaTypes.RuntimeNodejs18x, 22 | Environment: &lambdaTypes.EnvironmentResponse{ 23 | Variables: map[string]string{ 24 | "key1": "value1", 25 | "key2": "value2", 26 | }, 27 | }, 28 | }, 29 | { 30 | FunctionArn: aws.String("arn:aws:lambda:us-east-1:123456789012:function:my-function2"), 31 | FunctionName: aws.String("my-function2"), 32 | Handler: aws.String("index.handler"), 33 | Runtime: lambdaTypes.RuntimeNodejs18x, 34 | }, 35 | }, 36 | }, nil 37 | } 38 | 39 | func (m *MockedLambdaClient) GetFunction(ctx context.Context, input *lambda.GetFunctionInput, options ...func(*lambda.Options)) (*lambda.GetFunctionOutput, error) { 40 | return &lambda.GetFunctionOutput{ 41 | Configuration: &lambdaTypes.FunctionConfiguration{ 42 | FunctionArn: aws.String("arn:aws:lambda:us-east-1:123456789012:function:my-function"), 43 | FunctionName: aws.String("my-function"), 44 | Handler: aws.String("index.handler"), 45 | Runtime: lambdaTypes.RuntimeNodejs18x, 46 | Environment: &lambdaTypes.EnvironmentResponse{ 47 | Variables: map[string]string{ 48 | "key1": "value1", 49 | "key2": "value2", 50 | }, 51 | }, 52 | }, 53 | }, nil 54 | } 55 | 56 | func (m *MockedLambdaClient) GetFunctionUrlConfig(ctx context.Context, input *lambda.GetFunctionInput, options ...func(*lambda.Options)) (*lambda.GetFunctionUrlConfigOutput, error) { 57 | return &lambda.GetFunctionUrlConfigOutput{ 58 | FunctionUrl: aws.String("https://my-function.us-east-1.amazonaws.com/Prod/"), 59 | FunctionArn: aws.String("arn:aws:lambda:us-east-1:123456789012:function:my-function"), 60 | AuthType: lambdaTypes.FunctionUrlAuthTypeNone, 61 | }, nil 62 | } 63 | -------------------------------------------------------------------------------- /aws/sdk/lightsail.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/service/lightsail" 10 | lightsailTypes "github.com/aws/aws-sdk-go-v2/service/lightsail/types" 11 | "github.com/patrickmn/go-cache" 12 | ) 13 | 14 | type lightsailClientInterface interface { 15 | GetInstances(context.Context, *lightsail.GetInstancesInput, ...func(*lightsail.Options)) (*lightsail.GetInstancesOutput, error) 16 | GetContainerServices(context.Context, *lightsail.GetContainerServicesInput, ...func(*lightsail.Options)) (*lightsail.GetContainerServicesOutput, error) 17 | } 18 | 19 | func init() { 20 | 21 | //need to do this to avoid conflicts with the Instance type in the ec2 package 22 | type lightsailInstance lightsailTypes.Instance 23 | gob.Register([]lightsailInstance{}) 24 | gob.Register([]lightsailTypes.ContainerService{}) 25 | 26 | } 27 | 28 | func CachedLightsailGetInstances(client lightsailClientInterface, accountID string, region string) ([]lightsailTypes.Instance, error) { 29 | var PaginationControl *string 30 | var services []lightsailTypes.Instance 31 | 32 | // TODO: When caching is enabled, this []lightsailTypes.Instance type clashes with the EC instance type when it comes to gob.Register. need to figure out how to register both types 33 | // cacheKey := fmt.Sprintf("%s-lightsail-GetInstances-%s", accountID, region) 34 | // cached, found := internal.Cache.Get(cacheKey) 35 | // if found { 36 | // return cached.([]lightsailTypes.Instance), nil 37 | // } 38 | for { 39 | GetInstances, err := client.GetInstances( 40 | context.TODO(), 41 | &lightsail.GetInstancesInput{ 42 | PageToken: PaginationControl, 43 | }, 44 | func(o *lightsail.Options) { 45 | o.Region = region 46 | }, 47 | ) 48 | 49 | if err != nil { 50 | return services, err 51 | } 52 | 53 | services = append(services, GetInstances.Instances...) 54 | 55 | //pagination 56 | if GetInstances.NextPageToken == nil { 57 | break 58 | } 59 | PaginationControl = GetInstances.NextPageToken 60 | } 61 | 62 | // TODO: When caching is enabled, this []lightsailTypes.Instance type clashes with the EC instance type when it comes to gob.Register. need to figure out how to register both types 63 | //internal.Cache.Set(cacheKey, services, cache.DefaultExpiration) 64 | return services, nil 65 | } 66 | 67 | func CachedLightsailGetContainerServices(client lightsailClientInterface, accountID string, region string) ([]lightsailTypes.ContainerService, error) { 68 | var services []lightsailTypes.ContainerService 69 | cacheKey := fmt.Sprintf("%s-lightsail-GetContainerService-%s", accountID, region) 70 | cached, found := internal.Cache.Get(cacheKey) 71 | if found { 72 | return cached.([]lightsailTypes.ContainerService), nil 73 | } 74 | GetInstances, err := client.GetContainerServices( 75 | context.TODO(), 76 | &lightsail.GetContainerServicesInput{}, 77 | func(o *lightsail.Options) { 78 | o.Region = region 79 | }, 80 | ) 81 | 82 | if err != nil { 83 | return services, err 84 | } 85 | 86 | services = append(services, GetInstances.ContainerServices...) 87 | 88 | internal.Cache.Set(cacheKey, services, cache.DefaultExpiration) 89 | return services, nil 90 | } 91 | -------------------------------------------------------------------------------- /aws/sdk/lightsail_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/aws/aws-sdk-go-v2/aws" 8 | "github.com/aws/aws-sdk-go-v2/service/lightsail" 9 | lightsailTypes "github.com/aws/aws-sdk-go-v2/service/lightsail/types" 10 | ) 11 | 12 | type MockedLightsailClient struct { 13 | } 14 | 15 | func (m *MockedLightsailClient) GetInstances(ctx context.Context, input *lightsail.GetInstancesInput, options ...func(*lightsail.Options)) (*lightsail.GetInstancesOutput, error) { 16 | return &lightsail.GetInstancesOutput{ 17 | Instances: []lightsailTypes.Instance{ 18 | { 19 | BlueprintId: aws.String("blueprint1"), 20 | BundleId: aws.String("bundle1"), 21 | CreatedAt: aws.Time(time.Now()), 22 | Location: &lightsailTypes.ResourceLocation{ 23 | AvailabilityZone: aws.String("us-east-1a"), 24 | RegionName: lightsailTypes.RegionNameUsEast1, 25 | }, 26 | Name: aws.String("instance1"), 27 | Networking: &lightsailTypes.InstanceNetworking{ 28 | MonthlyTransfer: &lightsailTypes.MonthlyTransfer{ 29 | GbPerMonthAllocated: aws.Int32(1), 30 | }, 31 | }, 32 | PrivateIpAddress: aws.String("10.1.1.1"), 33 | PublicIpAddress: aws.String("1.2.3.4"), 34 | }, 35 | { 36 | BlueprintId: aws.String("blueprint2"), 37 | BundleId: aws.String("bundle2"), 38 | CreatedAt: aws.Time(time.Now()), 39 | Location: &lightsailTypes.ResourceLocation{ 40 | AvailabilityZone: aws.String("us-east-1b"), 41 | RegionName: lightsailTypes.RegionNameUsEast1, 42 | }, 43 | Name: aws.String("instance2"), 44 | Networking: &lightsailTypes.InstanceNetworking{ 45 | MonthlyTransfer: &lightsailTypes.MonthlyTransfer{ 46 | GbPerMonthAllocated: aws.Int32(2), 47 | }, 48 | }, 49 | PrivateIpAddress: aws.String("10.2.2.2"), 50 | PublicIpAddress: aws.String("2.3.4.4"), 51 | }, 52 | }, 53 | }, nil 54 | 55 | } 56 | 57 | func (m *MockedLightsailClient) GetContainerServices(ctx context.Context, input *lightsail.GetContainerServicesInput, options ...func(*lightsail.Options)) (*lightsail.GetContainerServicesOutput, error) { 58 | return &lightsail.GetContainerServicesOutput{ 59 | ContainerServices: []lightsailTypes.ContainerService{ 60 | { 61 | Arn: aws.String("arn1"), 62 | Location: &lightsailTypes.ResourceLocation{ 63 | AvailabilityZone: aws.String("us-east-1a"), 64 | RegionName: lightsailTypes.RegionNameUsEast1, 65 | }, 66 | Url: aws.String("https://container1"), 67 | PrivateDomainName: aws.String("container1"), 68 | }, 69 | { 70 | Arn: aws.String("arn2"), 71 | Location: &lightsailTypes.ResourceLocation{ 72 | AvailabilityZone: aws.String("us-east-1a"), 73 | RegionName: lightsailTypes.RegionNameUsEast1, 74 | }, 75 | Url: aws.String("https://container2"), 76 | PrivateDomainName: aws.String("container2"), 77 | }, 78 | }, 79 | }, nil 80 | } 81 | -------------------------------------------------------------------------------- /aws/sdk/mq.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/service/mq" 10 | mqTypes "github.com/aws/aws-sdk-go-v2/service/mq/types" 11 | "github.com/patrickmn/go-cache" 12 | ) 13 | 14 | type MQClientInterface interface { 15 | ListBrokers(context.Context, *mq.ListBrokersInput, ...func(*mq.Options)) (*mq.ListBrokersOutput, error) 16 | } 17 | 18 | func init() { 19 | gob.Register([]mqTypes.BrokerSummary{}) 20 | } 21 | 22 | // create CachedMQListBrokers function that uses go-cache and pagination 23 | func CachedMQListBrokers(client MQClientInterface, accountID string, region string) ([]mqTypes.BrokerSummary, error) { 24 | var PaginationControl *string 25 | var brokers []mqTypes.BrokerSummary 26 | cacheKey := fmt.Sprintf("%s-mq-ListBrokers-%s", accountID, region) 27 | cached, found := internal.Cache.Get(cacheKey) 28 | if found { 29 | return cached.([]mqTypes.BrokerSummary), nil 30 | } 31 | 32 | for { 33 | ListBrokers, err := client.ListBrokers( 34 | context.TODO(), 35 | &mq.ListBrokersInput{ 36 | NextToken: PaginationControl, 37 | }, 38 | func(o *mq.Options) { 39 | o.Region = region 40 | }, 41 | ) 42 | 43 | if err != nil { 44 | return brokers, err 45 | } 46 | 47 | brokers = append(brokers, ListBrokers.BrokerSummaries...) 48 | 49 | //pagination 50 | if ListBrokers.NextToken == nil { 51 | break 52 | } 53 | PaginationControl = ListBrokers.NextToken 54 | } 55 | 56 | internal.Cache.Set(cacheKey, brokers, cache.DefaultExpiration) 57 | return brokers, nil 58 | } 59 | -------------------------------------------------------------------------------- /aws/sdk/mq_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/aws/aws-sdk-go-v2/aws" 8 | "github.com/aws/aws-sdk-go-v2/service/mq" 9 | mqTypes "github.com/aws/aws-sdk-go-v2/service/mq/types" 10 | ) 11 | 12 | type MockedMQClient struct { 13 | } 14 | 15 | func (m *MockedMQClient) ListBrokers(ctx context.Context, input *mq.ListBrokersInput, options ...func(*mq.Options)) (*mq.ListBrokersOutput, error) { 16 | return &mq.ListBrokersOutput{ 17 | BrokerSummaries: []mqTypes.BrokerSummary{ 18 | { 19 | BrokerArn: aws.String("broker1"), 20 | BrokerId: aws.String("broker1"), 21 | BrokerName: aws.String("broker1"), 22 | BrokerState: mqTypes.BrokerStateRunning, 23 | Created: aws.Time(time.Now()), 24 | DeploymentMode: mqTypes.DeploymentModeSingleInstance, 25 | EngineType: mqTypes.EngineTypeRabbitmq, 26 | HostInstanceType: aws.String("host1"), 27 | }, 28 | { 29 | BrokerArn: aws.String("broker2"), 30 | BrokerId: aws.String("broker2"), 31 | BrokerName: aws.String("broker2"), 32 | BrokerState: mqTypes.BrokerStateRunning, 33 | Created: aws.Time(time.Now()), 34 | DeploymentMode: mqTypes.DeploymentModeSingleInstance, 35 | EngineType: mqTypes.EngineTypeActivemq, 36 | HostInstanceType: aws.String("host2"), 37 | }, 38 | }, 39 | }, nil 40 | } 41 | -------------------------------------------------------------------------------- /aws/sdk/opensearch_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/opensearch" 8 | openSearchTypes "github.com/aws/aws-sdk-go-v2/service/opensearch/types" 9 | ) 10 | 11 | type MockedOpenSearchClient struct { 12 | } 13 | 14 | func (m *MockedOpenSearchClient) ListDomainNames(ctx context.Context, input *opensearch.ListDomainNamesInput, options ...func(*opensearch.Options)) (*opensearch.ListDomainNamesOutput, error) { 15 | return &opensearch.ListDomainNamesOutput{ 16 | DomainNames: []openSearchTypes.DomainInfo{ 17 | { 18 | DomainName: aws.String("domain1"), 19 | EngineType: openSearchTypes.EngineTypeOpenSearch, 20 | }, 21 | { 22 | DomainName: aws.String("domain2"), 23 | EngineType: openSearchTypes.EngineTypeElasticsearch, 24 | }, 25 | }, 26 | }, nil 27 | } 28 | 29 | func (m *MockedOpenSearchClient) DescribeDomainConfig(ctx context.Context, input *opensearch.DescribeDomainConfigInput, options ...func(*opensearch.Options)) (*opensearch.DescribeDomainConfigOutput, error) { 30 | return &opensearch.DescribeDomainConfigOutput{ 31 | DomainConfig: &openSearchTypes.DomainConfig{ 32 | EngineVersion: &openSearchTypes.VersionStatus{ 33 | Options: aws.String("OpenSearch-1.1"), 34 | Status: &openSearchTypes.OptionStatus{ 35 | PendingDeletion: aws.Bool(false), 36 | }, 37 | }, 38 | ClusterConfig: &openSearchTypes.ClusterConfigStatus{ 39 | Options: &openSearchTypes.ClusterConfig{ 40 | DedicatedMasterCount: aws.Int32(3), 41 | DedicatedMasterEnabled: aws.Bool(true), 42 | InstanceCount: aws.Int32(3), 43 | WarmCount: aws.Int32(3), 44 | WarmEnabled: aws.Bool(true), 45 | }, 46 | }, 47 | DomainEndpointOptions: &openSearchTypes.DomainEndpointOptionsStatus{ 48 | Options: &openSearchTypes.DomainEndpointOptions{ 49 | EnforceHTTPS: aws.Bool(true), 50 | }, 51 | }, 52 | }, 53 | }, nil 54 | } 55 | 56 | func (m *MockedOpenSearchClient) DescribeDomain(ctx context.Context, input *opensearch.DescribeDomainInput, options ...func(*opensearch.Options)) (*opensearch.DescribeDomainOutput, error) { 57 | return &opensearch.DescribeDomainOutput{ 58 | DomainStatus: &openSearchTypes.DomainStatus{ 59 | DomainName: aws.String("domain1"), 60 | Endpoint: aws.String("https://domain1.us-east-1.es.amazonaws.com"), 61 | }, 62 | }, nil 63 | } 64 | -------------------------------------------------------------------------------- /aws/sdk/org.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/service/organizations" 10 | orgTypes "github.com/aws/aws-sdk-go-v2/service/organizations/types" 11 | "github.com/patrickmn/go-cache" 12 | ) 13 | 14 | type OrganizationsClientInterface interface { 15 | ListAccounts(ctx context.Context, params *organizations.ListAccountsInput, optFns ...func(*organizations.Options)) (*organizations.ListAccountsOutput, error) 16 | DescribeOrganization(ctx context.Context, params *organizations.DescribeOrganizationInput, optFns ...func(*organizations.Options)) (*organizations.DescribeOrganizationOutput, error) 17 | } 18 | 19 | func init() { 20 | gob.Register([]orgTypes.Account{}) 21 | //gob.Register(orgTypes.Organization{}) 22 | } 23 | 24 | // create a CachedOrganizationsListAccounts function that uses go-cache and pagination and returns a list of accounts 25 | func CachedOrganizationsListAccounts(client OrganizationsClientInterface, accountID string) ([]orgTypes.Account, error) { 26 | var PaginationControl *string 27 | var accounts []orgTypes.Account 28 | cacheKey := fmt.Sprintf("%s-organizations-ListAccounts", accountID) 29 | cached, found := internal.Cache.Get(cacheKey) 30 | if found { 31 | return cached.([]orgTypes.Account), nil 32 | } 33 | 34 | for { 35 | ListAccounts, err := client.ListAccounts( 36 | context.TODO(), 37 | &organizations.ListAccountsInput{ 38 | NextToken: PaginationControl, 39 | }, 40 | ) 41 | 42 | if err != nil { 43 | return accounts, err 44 | } 45 | 46 | accounts = append(accounts, ListAccounts.Accounts...) 47 | 48 | //pagination 49 | if ListAccounts.NextToken == nil { 50 | break 51 | } 52 | PaginationControl = ListAccounts.NextToken 53 | } 54 | internal.Cache.Set(cacheKey, accounts, cache.DefaultExpiration) 55 | 56 | return accounts, nil 57 | } 58 | 59 | // create a CachedOrganizationsDescribeOrganization function that uses go-cache and returns an organization 60 | func CachedOrganizationsDescribeOrganization(client OrganizationsClientInterface, accountID string) (*orgTypes.Organization, error) { 61 | var organization *orgTypes.Organization 62 | cacheKey := fmt.Sprintf("%s-organizations-DescribeOrganization", accountID) 63 | cached, found := internal.Cache.Get(cacheKey) 64 | if found { 65 | return cached.(*orgTypes.Organization), nil 66 | } 67 | 68 | DescribeOrganization, err := client.DescribeOrganization( 69 | context.TODO(), 70 | &organizations.DescribeOrganizationInput{}, 71 | ) 72 | 73 | if err != nil { 74 | return organization, err 75 | } 76 | 77 | organization = DescribeOrganization.Organization 78 | internal.Cache.Set(cacheKey, organization, cache.DefaultExpiration) 79 | 80 | return organization, nil 81 | } 82 | -------------------------------------------------------------------------------- /aws/sdk/org_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/aws/aws-sdk-go-v2/aws" 8 | "github.com/aws/aws-sdk-go-v2/service/organizations" 9 | organizationsTypes "github.com/aws/aws-sdk-go-v2/service/organizations/types" 10 | ) 11 | 12 | type MockedOrgClient struct { 13 | } 14 | 15 | func (m *MockedOrgClient) ListAccounts(ctx context.Context, input *organizations.ListAccountsInput, options ...func(*organizations.Options)) (*organizations.ListAccountsOutput, error) { 16 | return &organizations.ListAccountsOutput{ 17 | Accounts: []organizationsTypes.Account{ 18 | { 19 | Arn: aws.String("arn:aws:organizations::123456789012:account/o-exampleorgid/111111111111"), 20 | Email: aws.String("unittesting@bishopfox.com"), 21 | Id: aws.String("111111111111"), 22 | JoinedMethod: organizationsTypes.AccountJoinedMethodInvited, 23 | JoinedTimestamp: aws.Time(time.Now()), 24 | Name: aws.String("account1"), 25 | Status: organizationsTypes.AccountStatusActive, 26 | }, 27 | { 28 | Arn: aws.String("arn:aws:organizations::123456789012:account/o-exampleorgid/222222222222"), 29 | Email: aws.String("unittesting1@bishopfox.com"), 30 | Id: aws.String("222222222222"), 31 | JoinedMethod: organizationsTypes.AccountJoinedMethodCreated, 32 | JoinedTimestamp: aws.Time(time.Now()), 33 | Name: aws.String("account2"), 34 | Status: organizationsTypes.AccountStatusActive, 35 | }, 36 | }, 37 | }, nil 38 | 39 | } 40 | 41 | func (m *MockedOrgClient) DescribeOrganization(ctx context.Context, input *organizations.DescribeOrganizationInput, options ...func(*organizations.Options)) (*organizations.DescribeOrganizationOutput, error) { 42 | return &organizations.DescribeOrganizationOutput{ 43 | Organization: &organizationsTypes.Organization{ 44 | Arn: aws.String("arn:aws:organizations::123456789012:organization/o-exampleorgid"), 45 | FeatureSet: organizationsTypes.OrganizationFeatureSetAll, 46 | Id: aws.String("o-exampleorgid"), 47 | MasterAccountArn: aws.String("arn:aws:organizations::123456789012:account/o-exampleorgid/111111111111"), 48 | MasterAccountEmail: aws.String("unittesting1@bishopfox.com"), 49 | MasterAccountId: aws.String("111111111111"), 50 | }, 51 | }, nil 52 | } 53 | -------------------------------------------------------------------------------- /aws/sdk/rds.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/service/rds" 10 | rdsTypes "github.com/aws/aws-sdk-go-v2/service/rds/types" 11 | 12 | "github.com/patrickmn/go-cache" 13 | ) 14 | 15 | type RDSClientInterface interface { 16 | DescribeDBInstances(context.Context, *rds.DescribeDBInstancesInput, ...func(*rds.Options)) (*rds.DescribeDBInstancesOutput, error) 17 | DescribeDBClusters(context.Context, *rds.DescribeDBClustersInput, ...func(*rds.Options)) (*rds.DescribeDBClustersOutput, error) 18 | } 19 | 20 | func init() { 21 | gob.Register([]rdsTypes.DBInstance{}) 22 | gob.Register([]rdsTypes.DBCluster{}) 23 | 24 | } 25 | 26 | func CachedRDSDescribeDBInstances(client RDSClientInterface, accountID string, region string) ([]rdsTypes.DBInstance, error) { 27 | var PaginationControl *string 28 | var instances []rdsTypes.DBInstance 29 | cacheKey := fmt.Sprintf("%s-rds-DescribeDBInstances-%s", accountID, region) 30 | cached, found := internal.Cache.Get(cacheKey) 31 | if found { 32 | return cached.([]rdsTypes.DBInstance), nil 33 | } 34 | for { 35 | DescribeDBInstances, err := client.DescribeDBInstances( 36 | context.TODO(), 37 | &rds.DescribeDBInstancesInput{ 38 | Marker: PaginationControl, 39 | }, 40 | func(o *rds.Options) { 41 | o.Region = region 42 | }, 43 | ) 44 | 45 | if err != nil { 46 | return instances, err 47 | } 48 | 49 | instances = append(instances, DescribeDBInstances.DBInstances...) 50 | 51 | //pagination 52 | if DescribeDBInstances.Marker == nil { 53 | break 54 | } 55 | PaginationControl = DescribeDBInstances.Marker 56 | } 57 | 58 | internal.Cache.Set(cacheKey, instances, cache.DefaultExpiration) 59 | return instances, nil 60 | } 61 | 62 | func CachedRDSDescribeDBClusters(client RDSClientInterface, accountID string, region string) ([]rdsTypes.DBCluster, error) { 63 | var PaginationControl *string 64 | var clusters []rdsTypes.DBCluster 65 | cacheKey := fmt.Sprintf("%s-rds-DescribeDBClusters-%s", accountID, region) 66 | cached, found := internal.Cache.Get(cacheKey) 67 | if found { 68 | return cached.([]rdsTypes.DBCluster), nil 69 | } 70 | for { 71 | DescribeDBClusters, err := client.DescribeDBClusters( 72 | context.TODO(), 73 | &rds.DescribeDBClustersInput{ 74 | Marker: PaginationControl, 75 | }, 76 | func(o *rds.Options) { 77 | o.Region = region 78 | }, 79 | ) 80 | 81 | if err != nil { 82 | return clusters, err 83 | } 84 | 85 | clusters = append(clusters, DescribeDBClusters.DBClusters...) 86 | 87 | //pagination 88 | if DescribeDBClusters.Marker == nil { 89 | break 90 | } 91 | PaginationControl = DescribeDBClusters.Marker 92 | } 93 | 94 | internal.Cache.Set(cacheKey, clusters, cache.DefaultExpiration) 95 | return clusters, nil 96 | } 97 | -------------------------------------------------------------------------------- /aws/sdk/redshift.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/service/redshift" 10 | redshiftTypes "github.com/aws/aws-sdk-go-v2/service/redshift/types" 11 | "github.com/patrickmn/go-cache" 12 | ) 13 | 14 | type AWSRedShiftClientInterface interface { 15 | DescribeClusters(context.Context, *redshift.DescribeClustersInput, ...func(*redshift.Options)) (*redshift.DescribeClustersOutput, error) 16 | } 17 | 18 | func init() { 19 | gob.Register([]redshiftTypes.Cluster{}) 20 | 21 | } 22 | 23 | func CachedRedShiftDescribeClusters(client AWSRedShiftClientInterface, accountID string, region string) ([]redshiftTypes.Cluster, error) { 24 | var PaginationControl *string 25 | var clusters []redshiftTypes.Cluster 26 | cacheKey := fmt.Sprintf("%s-redshift-DescribeClusters-%s", accountID, region) 27 | cached, found := internal.Cache.Get(cacheKey) 28 | if found { 29 | return cached.([]redshiftTypes.Cluster), nil 30 | } 31 | for { 32 | DescribeClusters, err := client.DescribeClusters( 33 | context.TODO(), 34 | &redshift.DescribeClustersInput{ 35 | Marker: PaginationControl, 36 | }, 37 | func(o *redshift.Options) { 38 | o.Region = region 39 | }, 40 | ) 41 | 42 | if err != nil { 43 | return clusters, err 44 | } 45 | 46 | clusters = append(clusters, DescribeClusters.Clusters...) 47 | 48 | //pagination 49 | if DescribeClusters.Marker == nil { 50 | break 51 | } 52 | PaginationControl = DescribeClusters.Marker 53 | } 54 | 55 | internal.Cache.Set(cacheKey, clusters, cache.DefaultExpiration) 56 | return clusters, nil 57 | } 58 | -------------------------------------------------------------------------------- /aws/sdk/redshift_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/redshift" 8 | redshiftTypes "github.com/aws/aws-sdk-go-v2/service/redshift/types" 9 | ) 10 | 11 | type MockedRedshiftClient struct { 12 | } 13 | 14 | func (m *MockedRedshiftClient) DescribeClusters(ctx context.Context, input *redshift.DescribeClustersInput, options ...func(*redshift.Options)) (*redshift.DescribeClustersOutput, error) { 15 | return &redshift.DescribeClustersOutput{ 16 | Clusters: []redshiftTypes.Cluster{ 17 | { 18 | ClusterIdentifier: aws.String("cluster1"), 19 | ClusterNamespaceArn: aws.String("arn:aws:redshift:us-east-1:123456789012:cluster:cluster1"), 20 | DBName: aws.String("db1"), 21 | Endpoint: &redshiftTypes.Endpoint{ 22 | Address: aws.String("cluster1.us-east-1.redshift.amazonaws.com"), 23 | Port: aws.Int32(5439), 24 | }, 25 | }, 26 | { 27 | ClusterIdentifier: aws.String("cluster2"), 28 | ClusterNamespaceArn: aws.String("arn:aws:redshift:us-east-1:123456789012:cluster:cluster2"), 29 | DBName: aws.String("db2"), 30 | Endpoint: &redshiftTypes.Endpoint{ 31 | Address: aws.String("cluster2.us-east-1.redshift.amazonaws.com"), 32 | Port: aws.Int32(5439), 33 | }, 34 | }, 35 | }, 36 | }, nil 37 | } 38 | -------------------------------------------------------------------------------- /aws/sdk/route53.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/service/route53" 10 | route53types "github.com/aws/aws-sdk-go-v2/service/route53/types" 11 | "github.com/patrickmn/go-cache" 12 | ) 13 | 14 | type AWSRoute53ClientInterface interface { 15 | ListHostedZones(context.Context, *route53.ListHostedZonesInput, ...func(*route53.Options)) (*route53.ListHostedZonesOutput, error) 16 | ListResourceRecordSets(context.Context, *route53.ListResourceRecordSetsInput, ...func(*route53.Options)) (*route53.ListResourceRecordSetsOutput, error) 17 | } 18 | 19 | func init() { 20 | gob.Register([]route53types.HostedZone{}) 21 | gob.Register([]route53types.ResourceRecordSet{}) 22 | 23 | } 24 | 25 | func CachedRoute53ListHostedZones(client AWSRoute53ClientInterface, accountID string) ([]route53types.HostedZone, error) { 26 | var PaginationControl *string 27 | var hostedZones []route53types.HostedZone 28 | cacheKey := fmt.Sprintf("%s-route53-ListHostedZones", accountID) 29 | cached, found := internal.Cache.Get(cacheKey) 30 | if found { 31 | return cached.([]route53types.HostedZone), nil 32 | } 33 | 34 | for { 35 | ListHostedZones, err := client.ListHostedZones( 36 | context.TODO(), 37 | &route53.ListHostedZonesInput{ 38 | Marker: PaginationControl, 39 | }, 40 | ) 41 | 42 | if err != nil { 43 | return hostedZones, err 44 | } 45 | 46 | hostedZones = append(hostedZones, ListHostedZones.HostedZones...) 47 | 48 | //pagination 49 | if ListHostedZones.Marker == nil { 50 | break 51 | } 52 | PaginationControl = ListHostedZones.Marker 53 | } 54 | internal.Cache.Set(cacheKey, hostedZones, cache.DefaultExpiration) 55 | return hostedZones, nil 56 | } 57 | 58 | func CachedRoute53ListResourceRecordSets(client AWSRoute53ClientInterface, accountID string, hostedZoneID string) ([]route53types.ResourceRecordSet, error) { 59 | var PaginationControl *string 60 | var resourceRecordSets []route53types.ResourceRecordSet 61 | // remove the /hostedzone/ prefix 62 | hostedZoneID = hostedZoneID[12:] 63 | cacheKey := fmt.Sprintf("%s-route53-ListResourceRecordSets-%s", accountID, hostedZoneID) 64 | cached, found := internal.Cache.Get(cacheKey) 65 | if found { 66 | return cached.([]route53types.ResourceRecordSet), nil 67 | } 68 | 69 | for { 70 | ListResourceRecordSets, err := client.ListResourceRecordSets( 71 | context.TODO(), 72 | &route53.ListResourceRecordSetsInput{ 73 | HostedZoneId: &hostedZoneID, 74 | StartRecordName: PaginationControl, 75 | }, 76 | ) 77 | 78 | if err != nil { 79 | return resourceRecordSets, err 80 | } 81 | 82 | resourceRecordSets = append(resourceRecordSets, ListResourceRecordSets.ResourceRecordSets...) 83 | 84 | //pagination 85 | if !ListResourceRecordSets.IsTruncated { 86 | break 87 | } 88 | PaginationControl = ListResourceRecordSets.NextRecordName 89 | } 90 | internal.Cache.Set(cacheKey, resourceRecordSets, cache.DefaultExpiration) 91 | return resourceRecordSets, nil 92 | } 93 | -------------------------------------------------------------------------------- /aws/sdk/route53_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/route53" 8 | route53Types "github.com/aws/aws-sdk-go-v2/service/route53/types" 9 | ) 10 | 11 | type MockedRoute53Client struct { 12 | } 13 | 14 | func (m *MockedRoute53Client) ListHostedZones(ctx context.Context, input *route53.ListHostedZonesInput, options ...func(*route53.Options)) (*route53.ListHostedZonesOutput, error) { 15 | return &route53.ListHostedZonesOutput{ 16 | HostedZones: []route53Types.HostedZone{ 17 | { 18 | Id: aws.String("/hostedzone/zone1"), 19 | Name: aws.String("zone1"), 20 | ResourceRecordSetCount: aws.Int64(3), 21 | }, 22 | { 23 | Id: aws.String("/hostedzone/zone2"), 24 | Name: aws.String("zone2"), 25 | ResourceRecordSetCount: aws.Int64(3), 26 | }, 27 | }, 28 | }, nil 29 | } 30 | 31 | func (m *MockedRoute53Client) ListResourceRecordSets(ctx context.Context, input *route53.ListResourceRecordSetsInput, options ...func(*route53.Options)) (*route53.ListResourceRecordSetsOutput, error) { 32 | return &route53.ListResourceRecordSetsOutput{ 33 | ResourceRecordSets: []route53Types.ResourceRecordSet{ 34 | { 35 | Name: aws.String("zone1"), 36 | Type: route53Types.RRTypeSoa, 37 | }, 38 | { 39 | Name: aws.String("zone1"), 40 | Type: route53Types.RRTypeNs, 41 | }, 42 | { 43 | Name: aws.String("zone1"), 44 | Type: route53Types.RRTypeA, 45 | ResourceRecords: []route53Types.ResourceRecord{ 46 | { 47 | Value: aws.String("unit-test"), 48 | }, 49 | }, 50 | }, 51 | { 52 | Name: aws.String("zone2"), 53 | Type: route53Types.RRTypeSoa, 54 | }, 55 | { 56 | Name: aws.String("zone2"), 57 | Type: route53Types.RRTypeNs, 58 | }, 59 | { 60 | Name: aws.String("zone2"), 61 | Type: route53Types.RRTypeA, 62 | ResourceRecords: []route53Types.ResourceRecord{ 63 | { 64 | Value: aws.String("unit-test"), 65 | }, 66 | }, 67 | }, 68 | }, 69 | }, nil 70 | } 71 | -------------------------------------------------------------------------------- /aws/sdk/s3_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/s3" 8 | s3Types "github.com/aws/aws-sdk-go-v2/service/s3/types" 9 | ) 10 | 11 | type MockedS3Client struct { 12 | } 13 | 14 | func (m *MockedS3Client) ListBuckets(ctx context.Context, input *s3.ListBucketsInput, options ...func(*s3.Options)) (*s3.ListBucketsOutput, error) { 15 | return &s3.ListBucketsOutput{ 16 | Buckets: []s3Types.Bucket{ 17 | { 18 | Name: aws.String("bucket1"), 19 | }, 20 | { 21 | Name: aws.String("bucket2"), 22 | }, 23 | }, 24 | }, nil 25 | } 26 | 27 | func (m *MockedS3Client) GetBucketLocation(ctx context.Context, input *s3.GetBucketLocationInput, options ...func(*s3.Options)) (*s3.GetBucketLocationOutput, error) { 28 | return &s3.GetBucketLocationOutput{ 29 | LocationConstraint: s3Types.BucketLocationConstraintUsWest1, 30 | }, nil 31 | } 32 | 33 | func (m *MockedS3Client) GetBucketPolicy(ctx context.Context, input *s3.GetBucketPolicyInput, options ...func(*s3.Options)) (*s3.GetBucketPolicyOutput, error) { 34 | return &s3.GetBucketPolicyOutput{ 35 | Policy: aws.String(`{ 36 | "Version": "2012-10-17", 37 | "Statement": [ 38 | { 39 | "Sid": "AWSCloudTrailAclCheck20150319", 40 | "Effect": "Allow", 41 | "Principal": { 42 | "Service": "cloudtrail.amazonaws.com" 43 | }, 44 | "Action": "s3:GetBucketAcl", 45 | "Resource": "arn:aws:s3:::bucket1" 46 | }, 47 | { 48 | "Sid": "AWSCloudTrailWrite20150319", 49 | "Effect": "Allow", 50 | "Principal": { 51 | "Service": "cloudtrail.amazonaws.com" 52 | }, 53 | "Action": "s3:PutObject", 54 | "Resource": "arn:aws:s3:::bucket1/AWSLogs/123456789012/*", 55 | "Condition": { 56 | "StringEquals": { 57 | "s3:x-amz-acl": "bucket-owner-full-control" 58 | } 59 | } 60 | } 61 | ] 62 | }`), 63 | }, nil 64 | } 65 | 66 | func (m *MockedS3Client) GetPublicAccessBlock(ctx context.Context, input *s3.GetPublicAccessBlockInput, options ...func(*s3.Options)) (*s3.GetPublicAccessBlockOutput, error) { 67 | return &s3.GetPublicAccessBlockOutput{ 68 | PublicAccessBlockConfiguration: &s3Types.PublicAccessBlockConfiguration{ 69 | BlockPublicAcls: aws.Bool(true), 70 | BlockPublicPolicy: aws.Bool(true), 71 | IgnorePublicAcls: aws.Bool(true), 72 | RestrictPublicBuckets: aws.Bool(true), 73 | }, 74 | }, nil 75 | } 76 | -------------------------------------------------------------------------------- /aws/sdk/secretsmanager.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/BishopFox/cloudfox/internal/aws/policy" 10 | "github.com/aws/aws-sdk-go-v2/aws" 11 | "github.com/aws/aws-sdk-go-v2/service/secretsmanager" 12 | secretsmanagerTypes "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types" 13 | "github.com/patrickmn/go-cache" 14 | ) 15 | 16 | type SecretsManagerClientInterface interface { 17 | ListSecrets(context.Context, *secretsmanager.ListSecretsInput, ...func(*secretsmanager.Options)) (*secretsmanager.ListSecretsOutput, error) 18 | GetResourcePolicy(context.Context, *secretsmanager.GetResourcePolicyInput, ...func(*secretsmanager.Options)) (*secretsmanager.GetResourcePolicyOutput, error) 19 | } 20 | 21 | func init() { 22 | gob.Register([]secretsmanagerTypes.SecretListEntry{}) 23 | gob.Register(policy.Policy{}) 24 | 25 | } 26 | 27 | func CachedSecretsManagerListSecrets(client SecretsManagerClientInterface, accountID string, region string) ([]secretsmanagerTypes.SecretListEntry, error) { 28 | var PaginationControl *string 29 | var secrets []secretsmanagerTypes.SecretListEntry 30 | cacheKey := fmt.Sprintf("%s-secretsmanager-ListSecrets-%s", accountID, region) 31 | cached, found := internal.Cache.Get(cacheKey) 32 | if found { 33 | return cached.([]secretsmanagerTypes.SecretListEntry), nil 34 | } 35 | for { 36 | ListSecrets, err := client.ListSecrets( 37 | context.TODO(), 38 | &secretsmanager.ListSecretsInput{ 39 | NextToken: PaginationControl, 40 | }, 41 | func(o *secretsmanager.Options) { 42 | o.Region = region 43 | }, 44 | ) 45 | 46 | if err != nil { 47 | return secrets, err 48 | } 49 | 50 | secrets = append(secrets, ListSecrets.SecretList...) 51 | 52 | //pagination 53 | if ListSecrets.NextToken == nil { 54 | break 55 | } 56 | PaginationControl = ListSecrets.NextToken 57 | } 58 | 59 | internal.Cache.Set(cacheKey, secrets, cache.DefaultExpiration) 60 | return secrets, nil 61 | } 62 | 63 | func CachedSecretsManagerGetResourcePolicy(client SecretsManagerClientInterface, secretId string, region string, accountID string) (policy.Policy, error) { 64 | var secretPolicy policy.Policy 65 | var policyJSON string 66 | cacheKey := fmt.Sprintf("%s-secretsmanager-GetResourcePolicy-%s-%s", accountID, region, secretId) 67 | cached, found := internal.Cache.Get(cacheKey) 68 | if found { 69 | return cached.(policy.Policy), nil 70 | } 71 | GetResourcePolicy, err := client.GetResourcePolicy( 72 | context.TODO(), 73 | &secretsmanager.GetResourcePolicyInput{ 74 | SecretId: &secretId, 75 | }, 76 | func(o *secretsmanager.Options) { 77 | o.Region = region 78 | }, 79 | ) 80 | if err != nil { 81 | return secretPolicy, err 82 | } 83 | 84 | policyJSON = aws.ToString(GetResourcePolicy.ResourcePolicy) 85 | secretPolicy, err = policy.ParseJSONPolicy([]byte(policyJSON)) 86 | if err != nil { 87 | return secretPolicy, fmt.Errorf("parsing policy (%s) as JSON: %s", secretId, err) 88 | } 89 | internal.Cache.Set(cacheKey, secretPolicy, cache.DefaultExpiration) 90 | return secretPolicy, nil 91 | } 92 | -------------------------------------------------------------------------------- /aws/sdk/secretsmanager_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/secretsmanager" 8 | secretsmanagerTypes "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types" 9 | ) 10 | 11 | type MockedSecretsManagerClient struct { 12 | } 13 | 14 | func (m *MockedSecretsManagerClient) ListSecrets(ctx context.Context, input *secretsmanager.ListSecretsInput, options ...func(*secretsmanager.Options)) (*secretsmanager.ListSecretsOutput, error) { 15 | return &secretsmanager.ListSecretsOutput{ 16 | SecretList: []secretsmanagerTypes.SecretListEntry{ 17 | { 18 | Name: aws.String("secret1"), 19 | }, 20 | { 21 | Name: aws.String("secret2"), 22 | }, 23 | }, 24 | }, nil 25 | } 26 | 27 | func (m *MockedSecretsManagerClient) GetResourcePolicy(ctx context.Context, input *secretsmanager.GetResourcePolicyInput, options ...func(*secretsmanager.Options)) (*secretsmanager.GetResourcePolicyOutput, error) { 28 | return &secretsmanager.GetResourcePolicyOutput{ 29 | ResourcePolicy: aws.String(`{ 30 | "Version": "2012-10-17", 31 | "Statement": [ 32 | { 33 | "Sid": "RetrieveSecret", 34 | "Effect": "Allow", 35 | "Principal": { 36 | "AWS": "arn:aws:iam::123456789012:root" 37 | }, 38 | "Action": [ 39 | "secretsmanager:GetSecretValue", 40 | "secretsmanager:DescribeSecret", 41 | "secretsmanager:ListSecretVersionIds" 42 | ], 43 | "Resource": "*" 44 | }, 45 | { 46 | "Sid": "RetrieveSecret", 47 | "Effect": "Allow", 48 | "Principal": { 49 | "AWS": "arn:aws:iam::123456789012:root" 50 | }, 51 | "Action": [ 52 | "secretsmanager:GetSecretValue", 53 | "secretsmanager:DescribeSecret", 54 | ], 55 | "Resource": "*" 56 | } 57 | ] 58 | }`), 59 | }, nil 60 | } 61 | -------------------------------------------------------------------------------- /aws/sdk/shared.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/BishopFox/cloudfox/internal" 8 | ) 9 | 10 | var sharedLogger = internal.TxtLogger() 11 | 12 | func readTestFile(testFile string) []byte { 13 | file, err := os.ReadFile(testFile) 14 | if err != nil { 15 | log.Fatalf("can't read file %s", testFile) 16 | } 17 | return file 18 | } 19 | -------------------------------------------------------------------------------- /aws/sdk/sns_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/sns" 8 | snsTypes "github.com/aws/aws-sdk-go-v2/service/sns/types" 9 | ) 10 | 11 | type MockedSNSClient struct { 12 | } 13 | 14 | func (m *MockedSNSClient) ListTopics(ctx context.Context, input *sns.ListTopicsInput, options ...func(*sns.Options)) (*sns.ListTopicsOutput, error) { 15 | return &sns.ListTopicsOutput{ 16 | Topics: []snsTypes.Topic{ 17 | { 18 | TopicArn: aws.String("arn:aws:sns:us-east-1:123456789012:topic1"), 19 | }, 20 | { 21 | TopicArn: aws.String("arn:aws:sns:us-east-1:123456789012:topic2"), 22 | }, 23 | }, 24 | }, nil 25 | } 26 | 27 | func (m *MockedSNSClient) ListSubscriptionsByTopic(ctx context.Context, input *sns.ListSubscriptionsByTopicInput, options ...func(*sns.Options)) (*sns.ListSubscriptionsByTopicOutput, error) { 28 | return &sns.ListSubscriptionsByTopicOutput{ 29 | Subscriptions: []snsTypes.Subscription{ 30 | { 31 | SubscriptionArn: aws.String("arn:aws:sns:us-east-1:123456789012:topic1:sub1"), 32 | }, 33 | { 34 | SubscriptionArn: aws.String("arn:aws:sns:us-east-1:123456789012:topic1:sub2"), 35 | }, 36 | }, 37 | }, nil 38 | } 39 | 40 | func (m *MockedSNSClient) ListSubscriptions(ctx context.Context, input *sns.ListSubscriptionsInput, options ...func(*sns.Options)) (*sns.ListSubscriptionsOutput, error) { 41 | return &sns.ListSubscriptionsOutput{ 42 | Subscriptions: []snsTypes.Subscription{ 43 | { 44 | SubscriptionArn: aws.String("arn:aws:sns:us-east-1:123456789012:topic1:sub1"), 45 | }, 46 | { 47 | SubscriptionArn: aws.String("arn:aws:sns:us-east-1:123456789012:topic1:sub2"), 48 | }, 49 | }, 50 | }, nil 51 | } 52 | 53 | func (m *MockedSNSClient) GetTopicAttributes(ctx context.Context, input *sns.GetTopicAttributesInput, options ...func(*sns.Options)) (*sns.GetTopicAttributesOutput, error) { 54 | return &sns.GetTopicAttributesOutput{ 55 | Attributes: map[string]string{ 56 | "DisplayName": "topic1", 57 | "Policy": `{"Statement":[{"Effect":"Allow","Principal":"*","Action":"SNS:Publish","Resource":"arn:aws:sns:us-east-1:123456789012:topoic1","Condition":{"StringEquals":{"aws:sourceVpce":"vpce-1a2b3c4d"}}}]}`, 58 | }, 59 | }, nil 60 | } 61 | -------------------------------------------------------------------------------- /aws/sdk/sqs.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | 7 | "github.com/BishopFox/cloudfox/internal" 8 | "github.com/aws/aws-sdk-go-v2/service/sqs" 9 | "github.com/patrickmn/go-cache" 10 | ) 11 | 12 | type AWSSQSClientInterface interface { 13 | ListQueues(ctx context.Context, params *sqs.ListQueuesInput, optFns ...func(*sqs.Options)) (*sqs.ListQueuesOutput, error) 14 | } 15 | 16 | func init() { 17 | gob.Register([]string{}) 18 | } 19 | 20 | func CachedSQSListQueues(SQSClient AWSSQSClientInterface, accountID string, region string) ([]string, error) { 21 | var PaginationControl *string 22 | var queues []string 23 | cacheKey := "sqs-ListQueues-" + accountID + "-" + region 24 | cached, found := internal.Cache.Get(cacheKey) 25 | if found { 26 | sharedLogger.Debug("Using cached SQS queues data") 27 | return cached.([]string), nil 28 | } 29 | 30 | for { 31 | ListQueues, err := SQSClient.ListQueues( 32 | context.TODO(), 33 | &sqs.ListQueuesInput{ 34 | NextToken: PaginationControl, 35 | }, 36 | func(o *sqs.Options) { 37 | o.Region = region 38 | }, 39 | ) 40 | if err != nil { 41 | sharedLogger.Error(err.Error()) 42 | break 43 | } 44 | 45 | queues = append(queues, ListQueues.QueueUrls...) 46 | 47 | // Pagination control. 48 | if ListQueues.NextToken != nil { 49 | PaginationControl = ListQueues.NextToken 50 | } else { 51 | PaginationControl = nil 52 | break 53 | } 54 | } 55 | 56 | internal.Cache.Set(cacheKey, queues, cache.DefaultExpiration) 57 | return queues, nil 58 | } 59 | -------------------------------------------------------------------------------- /aws/sdk/sqs_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/service/sqs" 7 | ) 8 | 9 | type MockedSQSClient struct { 10 | } 11 | 12 | func (m *MockedSQSClient) ListQueues(ctx context.Context, input *sqs.ListQueuesInput, options ...func(*sqs.Options)) (*sqs.ListQueuesOutput, error) { 13 | return &sqs.ListQueuesOutput{ 14 | QueueUrls: []string{ 15 | "https://sqs.us-east-1.amazonaws.com/123456789012/queue1", 16 | "https://sqs.us-east-1.amazonaws.com/123456789012/queue2", 17 | }, 18 | }, nil 19 | } 20 | 21 | func (m *MockedSQSClient) GetQueueAttributes(ctx context.Context, input *sqs.GetQueueAttributesInput, options ...func(*sqs.Options)) (*sqs.GetQueueAttributesOutput, error) { 22 | return &sqs.GetQueueAttributesOutput{ 23 | Attributes: map[string]string{ 24 | "QueueArn": "arn:aws:sqs:us-east-1:123456789012:queue1", 25 | "Policy": `{"Version": "2012-10-17","Id": "anyID","Statement": [{"Sid":"unconditionally_public","Effect": "Allow","Principal": {"AWS": "*"},"Action": "sqs:*","Resource": "arn:aws:sqs:*:123456789012:some-queue"}]}`, 26 | }, 27 | }, nil 28 | } 29 | -------------------------------------------------------------------------------- /aws/sdk/ssm.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/service/ssm" 10 | "github.com/aws/aws-sdk-go-v2/service/ssm/types" 11 | "github.com/patrickmn/go-cache" 12 | ) 13 | 14 | type AWSSSMClientInterface interface { 15 | DescribeParameters(ctx context.Context, params *ssm.DescribeParametersInput, optFns ...func(*ssm.Options)) (*ssm.DescribeParametersOutput, error) 16 | } 17 | 18 | func init() { 19 | gob.Register([]types.ParameterMetadata{}) 20 | } 21 | 22 | // create a CachedSSMDescribeParameters function that uses go-cache line the other Cached* functions. It should accept a ssm client, account id, and region. Make sure it handles the region option and pagination if needed 23 | func CachedSSMDescribeParameters(SSMClient AWSSSMClientInterface, accountID string, region string) ([]types.ParameterMetadata, error) { 24 | var PaginationControl *string 25 | var parameters []types.ParameterMetadata 26 | cacheKey := fmt.Sprintf("%s-ssm-DescribeParameters-%s", accountID, region) 27 | cached, found := internal.Cache.Get(cacheKey) 28 | if found { 29 | sharedLogger.Debug("Using cached SSM parameters data") 30 | return cached.([]types.ParameterMetadata), nil 31 | } 32 | 33 | for { 34 | DescribeParameters, err := SSMClient.DescribeParameters( 35 | context.TODO(), 36 | &ssm.DescribeParametersInput{ 37 | NextToken: PaginationControl, 38 | }, 39 | func(o *ssm.Options) { 40 | o.Region = region 41 | }, 42 | ) 43 | if err != nil { 44 | sharedLogger.Error(err.Error()) 45 | break 46 | } 47 | 48 | parameters = append(parameters, DescribeParameters.Parameters...) 49 | 50 | // Pagination control. 51 | if DescribeParameters.NextToken != nil { 52 | PaginationControl = DescribeParameters.NextToken 53 | } else { 54 | PaginationControl = nil 55 | break 56 | } 57 | } 58 | 59 | internal.Cache.Set(cacheKey, parameters, cache.DefaultExpiration) 60 | return parameters, nil 61 | } 62 | -------------------------------------------------------------------------------- /aws/sdk/ssm_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/ssm" 8 | ssmTypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" 9 | ) 10 | 11 | type MockedSSMClient struct { 12 | } 13 | 14 | func (m *MockedSSMClient) DescribeParameters(ctx context.Context, input *ssm.DescribeParametersInput, options ...func(*ssm.Options)) (*ssm.DescribeParametersOutput, error) { 15 | return &ssm.DescribeParametersOutput{ 16 | Parameters: []ssmTypes.ParameterMetadata{ 17 | { 18 | Name: aws.String("/parameter/param1"), 19 | Type: ssmTypes.ParameterTypeString, 20 | }, 21 | { 22 | Name: aws.String("/parameter/param2"), 23 | Type: ssmTypes.ParameterTypeString, 24 | }, 25 | }, 26 | }, nil 27 | } 28 | -------------------------------------------------------------------------------- /aws/sdk/stepfunctions.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/aws-sdk-go-v2/service/sfn" 10 | sfnTypes "github.com/aws/aws-sdk-go-v2/service/sfn/types" 11 | "github.com/patrickmn/go-cache" 12 | ) 13 | 14 | type StepFunctionsClientInterface interface { 15 | ListStateMachines(context.Context, *sfn.ListStateMachinesInput, ...func(*sfn.Options)) (*sfn.ListStateMachinesOutput, error) 16 | } 17 | 18 | func init() { 19 | gob.Register([]sfnTypes.StateMachineListItem{}) 20 | } 21 | 22 | func CachedStepFunctionsListStateMachines(client StepFunctionsClientInterface, accountID string, region string) ([]sfnTypes.StateMachineListItem, error) { 23 | var PaginationControl *string 24 | var stateMachines []sfnTypes.StateMachineListItem 25 | cacheKey := fmt.Sprintf("%s-stepfunctions-ListStateMachines-%s", accountID, region) 26 | cached, found := internal.Cache.Get(cacheKey) 27 | if found { 28 | return cached.([]sfnTypes.StateMachineListItem), nil 29 | } 30 | for { 31 | ListStateMachines, err := client.ListStateMachines( 32 | context.TODO(), 33 | &sfn.ListStateMachinesInput{ 34 | NextToken: PaginationControl, 35 | }, 36 | func(o *sfn.Options) { 37 | o.Region = region 38 | }, 39 | ) 40 | 41 | if err != nil { 42 | return stateMachines, err 43 | } 44 | 45 | stateMachines = append(stateMachines, ListStateMachines.StateMachines...) 46 | 47 | //pagination 48 | if ListStateMachines.NextToken == nil { 49 | break 50 | } 51 | PaginationControl = ListStateMachines.NextToken 52 | } 53 | 54 | internal.Cache.Set(cacheKey, stateMachines, cache.DefaultExpiration) 55 | return stateMachines, nil 56 | } 57 | -------------------------------------------------------------------------------- /aws/sdk/stepfunctions_mocks.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/aws/aws-sdk-go-v2/aws" 7 | "github.com/aws/aws-sdk-go-v2/service/sfn" 8 | sfnTypes "github.com/aws/aws-sdk-go-v2/service/sfn/types" 9 | ) 10 | 11 | type MockedStepFunctionsClient struct { 12 | } 13 | 14 | func (m *MockedStepFunctionsClient) ListStateMachines(ctx context.Context, input *sfn.ListStateMachinesInput, options ...func(*sfn.Options)) (*sfn.ListStateMachinesOutput, error) { 15 | return &sfn.ListStateMachinesOutput{ 16 | StateMachines: []sfnTypes.StateMachineListItem{ 17 | { 18 | Name: aws.String("state_machine1"), 19 | StateMachineArn: aws.String("arn:aws:states:us-east-1:123456789012:stateMachine:state_machine1"), 20 | }, 21 | { 22 | Name: aws.String("state_machine2"), 23 | StateMachineArn: aws.String("arn:aws:states:us-east-1:123456789012:stateMachine:state_machine2"), 24 | }, 25 | }, 26 | }, nil 27 | } 28 | -------------------------------------------------------------------------------- /aws/tags_test.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/BishopFox/cloudfox/internal" 8 | "github.com/aws/aws-sdk-go-v2/aws" 9 | "github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi" 10 | "github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi/types" 11 | "github.com/aws/aws-sdk-go-v2/service/sts" 12 | ) 13 | 14 | type MockedTagsGetResources struct { 15 | } 16 | 17 | func (m *MockedTagsGetResources) GetResources(ctx context.Context, params *resourcegroupstaggingapi.GetResourcesInput, optFns ...func(*resourcegroupstaggingapi.Options)) (*resourcegroupstaggingapi.GetResourcesOutput, error) { 18 | testResources := []types.ResourceTagMapping{} 19 | testResources = append(testResources, types.ResourceTagMapping{ 20 | ResourceARN: aws.String("arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0"), 21 | Tags: []types.Tag{ 22 | { 23 | Key: aws.String("Name"), 24 | Value: aws.String("test"), 25 | }, 26 | }, 27 | }) 28 | 29 | testGetResourcesOutput := &resourcegroupstaggingapi.GetResourcesOutput{ 30 | ResourceTagMappingList: testResources, 31 | } 32 | 33 | return testGetResourcesOutput, nil 34 | } 35 | 36 | func TestTags(t *testing.T) { 37 | subtests := []struct { 38 | name string 39 | outputDirectory string 40 | verbosity int 41 | testModule TagsModule 42 | expectedResult []Tag 43 | }{ 44 | { 45 | name: "TestTags", 46 | outputDirectory: ".", 47 | verbosity: 2, 48 | testModule: TagsModule{ 49 | ResourceGroupsTaggingApiInterface: &MockedTagsGetResources{}, 50 | Caller: sts.GetCallerIdentityOutput{ 51 | Account: aws.String("123456789012"), 52 | Arn: aws.String("arn:aws:iam::123456789012:user/test"), 53 | UserId: aws.String("AIDAJDPLRKLG7UEXAMPLE"), 54 | }, 55 | AWSRegions: []string{"us-east-1"}, 56 | Goroutines: 10, 57 | AWSProfile: "test", 58 | WrapTable: false, 59 | MaxResourcesPerRegion: 100, 60 | }, 61 | expectedResult: []Tag{ 62 | { 63 | AWSService: "ec2", 64 | Region: "us-east-1", 65 | Type: "instance", 66 | Key: "Name", 67 | Value: "test", 68 | }, 69 | }, 70 | }, 71 | } 72 | internal.MockFileSystem(true) 73 | for _, subtest := range subtests { 74 | t.Run(subtest.name, func(t *testing.T) { 75 | subtest.testModule.PrintTags(subtest.outputDirectory, subtest.verbosity) 76 | if len(subtest.testModule.Tags) != len(subtest.expectedResult) { 77 | t.Errorf("Expected %d results, got %d", len(subtest.expectedResult), len(subtest.testModule.Tags)) 78 | } 79 | for i, ExpectedTag := range subtest.expectedResult { 80 | 81 | if ExpectedTag.Type != subtest.testModule.Tags[i].Type { 82 | t.Errorf("Expected Type = %s, got %s", subtest.testModule.Tags[i].Type, ExpectedTag.Type) 83 | } 84 | if ExpectedTag.Key != subtest.testModule.Tags[i].Key { 85 | t.Errorf("Expected Key = %s, got %s", subtest.testModule.Tags[i].Key, ExpectedTag.Key) 86 | } 87 | if ExpectedTag.Value != subtest.testModule.Tags[i].Value { 88 | t.Errorf("Expected Value = %s, got %s", subtest.testModule.Tags[i].Value, ExpectedTag.Value) 89 | } 90 | } 91 | }) 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /aws/test-data/cloudformation-describestacks.json: -------------------------------------------------------------------------------- 1 | { 2 | "Stacks": [ 3 | { 4 | "StackId": "arn:aws:cloudformation:us-east-1:123456789012:stack/myteststack/466df9e0-0dff-08e3-8e2f-5088487c4896", 5 | "Description": "AWS CloudFormation Sample Template S3_Bucket: Sample template showing how to create a publicly accessible S3 bucket. **WARNING** This template creates an S3 bucket. You will be billed for the AWS resources used if you create a stack from this template.", 6 | "Tags": [], 7 | "Outputs": [ 8 | { 9 | "Description": "Name of S3 bucket to hold website content", 10 | "OutputKey": "BucketName", 11 | "OutputValue": "myteststack-s3bucket-jssofi1zie2w" 12 | } 13 | ], 14 | "StackStatusReason": null, 15 | "CreationTime": "2013-08-23T01:02:15.422Z", 16 | "Capabilities": [], 17 | "StackName": "myteststack", 18 | "RoleArn": "role123", 19 | "StackStatus": "CREATE_COMPLETE", 20 | "DisableRollback": false 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /aws/test-data/cloudformation-getTemplate.json: -------------------------------------------------------------------------------- 1 | { 2 | "TemplateBody": { 3 | "AWSTemplateFormatVersion": "2010-09-09", 4 | "Outputs": { 5 | "BucketName": { 6 | "Description": "Name of S3 bucket to hold website content", 7 | "Value": { 8 | "Ref": "S3Bucket" 9 | } 10 | } 11 | }, 12 | "Description": "AWS CloudFormation Sample Template S3_Bucket: Sample template showing how to create a publicly accessible S3 bucket. **WARNING** This template creates an S3 bucket. You will be billed for the AWS resources used if you create a stack from this template.", 13 | "Resources": { 14 | "S3Bucket": { 15 | "Type": "AWS::S3::Bucket", 16 | "Properties": { 17 | "AccessControl": "PublicRead" 18 | } 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /azure/inventory_test.go: -------------------------------------------------------------------------------- 1 | package azure 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sort" 7 | "testing" 8 | 9 | "github.com/Azure/azure-sdk-for-go/sdk/azidentity" 10 | "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" 11 | "github.com/BishopFox/cloudfox/internal" 12 | "github.com/aws/smithy-go/ptr" 13 | ) 14 | 15 | func TestAzInventoryPoC(t *testing.T) { 16 | // Using the new version implementation 17 | // https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/MIGRATION_GUIDE.md 18 | 19 | tenantID := "" 20 | subscriptionID := "" 21 | 22 | cred, err := azidentity.NewAzureCLICredential(&azidentity.AzureCLICredentialOptions{TenantID: tenantID}) 23 | if err != nil { 24 | return 25 | } 26 | 27 | client, err := armresources.NewClient(subscriptionID, cred, nil) 28 | if err != nil { 29 | return 30 | } 31 | 32 | var resources []*armresources.GenericResourceExpanded 33 | 34 | pager := client.NewListPager(nil) 35 | for pager.More() { 36 | nextResult, err := pager.NextPage(context.TODO()) 37 | if err != nil { 38 | return 39 | } 40 | resources = append(resources, nextResult.Value...) 41 | } 42 | 43 | inventory := make(map[string]map[string]int) 44 | resourceTypes := make(map[string]bool) 45 | resourceLocations := make(map[string]bool) 46 | 47 | for _, resource := range resources { 48 | resourceType := ptr.ToString(resource.Type) 49 | resourceLocation := ptr.ToString(resource.Location) 50 | 51 | _, ok := inventory[resourceType] 52 | if !ok { 53 | inventory[resourceType] = make(map[string]int) 54 | } 55 | inventory[resourceType][resourceLocation]++ 56 | resourceTypes[resourceType] = true 57 | resourceLocations[resourceLocation] = true 58 | } 59 | 60 | header := []string{"Resource Type"} 61 | var body [][]string 62 | for location := range resourceLocations { 63 | header = append(header, location) 64 | } 65 | 66 | for t := range resourceTypes { 67 | row := []string{t} 68 | for location := range resourceLocations { 69 | count, ok := inventory[t][location] 70 | if ok { 71 | row = append(row, fmt.Sprintf("%d", count)) 72 | } else { 73 | row = append(row, "-") 74 | } 75 | } 76 | body = append(body, row) 77 | } 78 | 79 | sort.Slice(body, func(i, j int) bool { 80 | return body[i][0] < body[j][0] 81 | }) 82 | 83 | internal.MockFileSystem(true) 84 | internal.OutputSelector(2, "table", header, body, ".", "test.txt", "inventory", true, "sub-11111111-1111-11111-1111-1111111111111111") 85 | } 86 | -------------------------------------------------------------------------------- /azure/storage_test.go: -------------------------------------------------------------------------------- 1 | package azure 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "testing" 7 | 8 | "github.com/BishopFox/cloudfox/globals" 9 | "github.com/BishopFox/cloudfox/internal" 10 | ) 11 | 12 | // TO-DO: add blob URL enumeration to this table test. 13 | // This test won't work anymore until blob URL enumeration is added. 14 | func TestAzStorageCommand(t *testing.T) { 15 | fmt.Println() 16 | fmt.Println("[test case] Azure Storage Accounts") 17 | 18 | // Test case parameters 19 | subtests := []struct { 20 | name string 21 | AzTenantID string 22 | AzSubscriptionID string 23 | AzOutputFormat string 24 | azOutputDirectory string 25 | AzVerbosity int 26 | resourcesTestFile string 27 | storageAccountsTestFile string 28 | version string 29 | wrapTableOutput bool 30 | azMergedTable bool 31 | }{ 32 | { 33 | name: "./cloudfox az storage --tenant 11111111-1111-1111-1111-11111111", 34 | AzTenantID: "11111111-1111-1111-1111-11111111", 35 | AzSubscriptionID: "", 36 | AzOutputFormat: "all", 37 | azOutputDirectory: "~/.cloudfox", 38 | AzVerbosity: 2, 39 | resourcesTestFile: "./test-data/resources.json", 40 | storageAccountsTestFile: "./test-data/storage-accounts.json", 41 | version: "DEV", 42 | wrapTableOutput: false, 43 | azMergedTable: false, 44 | }, 45 | { 46 | name: "./cloudfox az storage --subscription BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBB", 47 | AzTenantID: "", 48 | AzSubscriptionID: "BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBB", 49 | AzOutputFormat: "all", 50 | azOutputDirectory: "~/.cloudfox", 51 | AzVerbosity: 2, 52 | resourcesTestFile: "./test-data/resources.json", 53 | storageAccountsTestFile: "./test-data/storage-accounts.json", 54 | version: "DEV", 55 | wrapTableOutput: false, 56 | azMergedTable: false, 57 | }, 58 | { 59 | name: "./cloudfox az storage", 60 | AzOutputFormat: "all", 61 | azOutputDirectory: "~/.cloudfox", 62 | AzVerbosity: 2, 63 | resourcesTestFile: "./test-data/resources.json", 64 | storageAccountsTestFile: "./test-data/storage-accounts.json", 65 | version: "DEV", 66 | wrapTableOutput: false, 67 | azMergedTable: false, 68 | }, 69 | } 70 | internal.MockFileSystem(true) 71 | // Mocked functions to simulate Azure calls and responses 72 | getTenants = mockedGetTenants 73 | GetSubscriptions = mockedGetSubscriptions 74 | getResourceGroups = mockedGetResourceGroups 75 | getStorageAccounts = mockedGetStorageAccounts 76 | 77 | for _, s := range subtests { 78 | fmt.Println() 79 | fmt.Printf("[subtest] %s\n", s.name) 80 | globals.RESOURCES_TEST_FILE = s.resourcesTestFile 81 | globals.STORAGE_ACCOUNTS_TEST_FILE = s.storageAccountsTestFile 82 | 83 | err := AzStorageCommand(s.AzTenantID, s.AzSubscriptionID, s.AzOutputFormat, s.azOutputDirectory, s.version, s.AzVerbosity, s.wrapTableOutput, s.azMergedTable) 84 | if err != nil { 85 | log.Fatal(err) 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /azure/test-data/nics.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA1/providers/Microsoft.Network/networkInterfaces/NetworkInterface1", 4 | "properties": { 5 | "ipConfigurations": [ 6 | { 7 | "properties": { 8 | "privateIPAddress": "192.168.0.1", 9 | "publicIPAddress": { 10 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA1/providers/Microsoft.Network/publicIPAddresses/PublicIpAddress1A" 11 | } 12 | } 13 | }, 14 | { 15 | "properties": { 16 | "privateIPAddress": "192.168.0.2", 17 | "publicIPAddress": { 18 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA1/providers/Microsoft.Network/publicIPAddresses/PublicIpAddress1B" 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | }, 25 | { 26 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA2/providers/Microsoft.Network/networkInterfaces/NetworkInterface2", 27 | "properties": { 28 | "ipConfigurations": [ 29 | { 30 | "properties": { 31 | "privateIPAddress": "192.168.0.3", 32 | "publicIPAddress": { 33 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupB1/providers/Microsoft.Network/publicIPAddresses/PublicIpAddress2A" 34 | } 35 | } 36 | } 37 | ] 38 | } 39 | }, 40 | { 41 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA2/providers/Microsoft.Network/networkInterfaces/NetworkInterface3", 42 | "properties": { 43 | "ipConfigurations": [ 44 | { 45 | "properties": { 46 | "privateIPAddress": "192.168.0.4", 47 | "publicIPAddress": { 48 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA2/providers/Microsoft.Network/publicIPAddresses/PublicIpAddress3A" 49 | } 50 | } 51 | } 52 | ] 53 | } 54 | }, 55 | { 56 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA1/providers/Microsoft.Network/networkInterfaces/NetworkInterface4", 57 | "properties": { 58 | "ipConfigurations": [ 59 | { 60 | "properties": { 61 | "privateIPAddress": "192.168.0.5", 62 | "publicIPAddress": { 63 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA1/providers/Microsoft.Network/publicIPAddresses/PublicIpAddress4A" 64 | } 65 | } 66 | } 67 | ] 68 | } 69 | } 70 | ] -------------------------------------------------------------------------------- /azure/test-data/public-ips.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA1/providers/Microsoft.Network/publicIPAddresses/PublicIpAddress1A", 4 | "properties": { 5 | "ipAddress": "72.88.100.1" 6 | } 7 | }, 8 | { 9 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA1/providers/Microsoft.Network/publicIPAddresses/PublicIpAddress1B", 10 | "properties": { 11 | "ipAddress": "72.88.100.2" 12 | } 13 | }, 14 | { 15 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupB1/providers/Microsoft.Network/publicIPAddresses/PublicIpAddress2A", 16 | "properties": { 17 | "ipAddress": "72.88.100.3" 18 | } 19 | }, 20 | { 21 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA2/providers/Microsoft.Network/publicIPAddresses/PublicIpAddress3A", 22 | "properties": { 23 | "ipAddress": "72.88.100.4" 24 | } 25 | }, 26 | { 27 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA1/providers/Microsoft.Network/publicIPAddresses/PublicIpAddress4A", 28 | "properties": { 29 | "ipAddress": "72.88.100.5" 30 | } 31 | } 32 | ] -------------------------------------------------------------------------------- /azure/test-data/resources.json: -------------------------------------------------------------------------------- 1 | { 2 | "Tenants":[ 3 | { 4 | "displayName": "CloudFoxTestTenant1", 5 | "tenantId": "11111111-1111-1111-1111-11111111", 6 | "defaultDomain": "cloudfox1.local", 7 | "Subscriptions": [ 8 | { 9 | "tenantId" : "11111111-1111-1111-1111-11111111", 10 | "displayName": "SubscriptionA", 11 | "subscriptionId": "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA", 12 | "ResourceGroups": [ 13 | { 14 | "Name": "ResourceGroupA1", 15 | "id": "subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA1", 16 | "location": "eastus" 17 | }, 18 | { 19 | "Name": "ResourceGroupA2", 20 | "id": "subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA2", 21 | "location": "eastus" 22 | } 23 | ] 24 | }, 25 | { 26 | "tenantId" : "11111111-1111-1111-1111-11111111", 27 | "displayName": "SubscriptionB", 28 | "subscriptionId": "BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBB", 29 | "ResourceGroups": [ 30 | { 31 | "Name": "ResourceGroupB1", 32 | "id": "subscriptions/BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBB/resourceGroups/ResourceGroupB1", 33 | "location": "eastus" 34 | }, 35 | { 36 | "Name": "ResourceGroupB2", 37 | "id": "subscriptions/BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBB/resourceGroups/ResourceGroupB2", 38 | "location": "eastus" 39 | } 40 | ] 41 | } 42 | ] 43 | }, 44 | { 45 | "displayName": "CloudFoxTestTenant2", 46 | "tenantId": "22222222-2222-2222-2222-22222222", 47 | "defaultDomain": "cloudfox2.local", 48 | "Subscriptions": [ 49 | { 50 | "tenantId" : "22222222-2222-2222-2222-22222222", 51 | "displayName": "SubscriptionC", 52 | "subscriptionId": "CCCCCCCC-CCCC-CCCC-CCCC-CCCCCCCC", 53 | "ResourceGroups": [ 54 | { 55 | "Name": "ResourceGroupC1", 56 | "id": "subscriptions/CCCCCCCC-CCCC-CCCC-CCCCCCCC/resourceGroups/ResourceGroupC1", 57 | "location": "eastus" 58 | }, 59 | { 60 | "Name": "ResourceGroupC2", 61 | "id": "subscriptions/CCCCCCCC-CCCC-CCCC-CCCCCCCC/resourceGroups/ResourceGroupC2", 62 | "location": "eastus" 63 | } 64 | ] 65 | } 66 | ] 67 | } 68 | ] 69 | } -------------------------------------------------------------------------------- /azure/test-data/role-assignments.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "properties": { 4 | "scope": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA", 5 | "roleDefinitionId": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7", 6 | "principalId": "8da340e3-5e2e-4f55-a3f7-4ea20d6755be" 7 | } 8 | }, 9 | { 10 | "properties": { 11 | "scope": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA", 12 | "roleDefinitionId": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c", 13 | "principalId": "d994d51b-d939-469e-ac73-bd81030cecf0" 14 | } 15 | }, 16 | { 17 | "properties": { 18 | "scope": "/subscriptions/BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBB", 19 | "roleDefinitionId": "/subscriptions/BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBB/providers/Microsoft.Authorization/roleDefinitions/c6decf44-fd0a-444c-a844-d653c394e7ab", 20 | "principalId": "8da340e3-5e2e-4f55-a3f7-4ea20d6755be" 21 | } 22 | }, 23 | { 24 | "properties": { 25 | "scope": "/subscriptions/BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBB", 26 | "roleDefinitionId": "/subscriptions/BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBB/providers/Microsoft.Authorization/roleDefinitions/c6decf44-fd0a-444c-a844-d653c394e7ab", 27 | "principalId": "4ad03365-28cd-40cd-a46f-6e9d1149803d" 28 | } 29 | }, 30 | { 31 | "properties": { 32 | "scope": "/subscriptions/CCCCCCCC-CCCC-CCCC-CCCCCCCC", 33 | "roleDefinitionId": "/subscriptions/CCCCCCCC-CCCC-CCCC-CCCCCCCC/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635", 34 | "principalId": "73d5b926-b258-47a2-891c-b14bf9da5dde" 35 | } 36 | } 37 | ] -------------------------------------------------------------------------------- /azure/test-data/storage-accounts.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA1/providers/resourceProviderNamespace/resourceType/resourceName", 4 | "name": "Storage-12345", 5 | "kind": "KindBlobStorage", 6 | "location": "eastus", 7 | "properties": { 8 | "allowBlobPublicAccess": true, 9 | "allowCrossTenantReplication": true, 10 | "allowSharedKeyAccess": true, 11 | "minimumTlsVersion": "TLS1_2", 12 | "supportsHttpsTrafficOnly": true 13 | }, 14 | "tags": {} 15 | }, 16 | { 17 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA1/providers/resourceProviderNamespace/resourceType/resourceName", 18 | "name": "Storage-678910", 19 | "location": "eastus", 20 | "kind": "KindStorageV2", 21 | "properties": { 22 | "allowBlobPublicAccess": false, 23 | "allowCrossTenantReplication": true, 24 | "allowSharedKeyAccess": true, 25 | "minimumTlsVersion": "TLS1_2", 26 | "supportsHttpsTrafficOnly": true 27 | }, 28 | "tags": {} 29 | }, 30 | { 31 | "id": "/subscriptions/BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBB/resourceGroups/ResourceGroupB1/providers/resourceProviderNamespace/resourceType/resourceName", 32 | "name": "Storage-1112131415", 33 | "location": "eastus", 34 | "kind": "KindStorageV2", 35 | "properties": { 36 | "allowBlobPublicAccess": true, 37 | "allowCrossTenantReplication": true, 38 | "allowSharedKeyAccess": true, 39 | "minimumTlsVersion": "TLS1_2", 40 | "supportsHttpsTrafficOnly": true 41 | }, 42 | "tags": {} 43 | }, 44 | { 45 | "id": "/subscriptions/BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBB/resourceGroups/ResourceGroupB1/providers/resourceProviderNamespace/resourceType/resourceName", 46 | "name": "Storage-16171819", 47 | "location": "eastus", 48 | "kind": "KindStorageV2", 49 | "properties": { 50 | "allowBlobPublicAccess": false, 51 | "allowCrossTenantReplication": true, 52 | "allowSharedKeyAccess": true, 53 | "minimumTlsVersion": "TLS1_2", 54 | "supportsHttpsTrafficOnly": true 55 | }, 56 | "tags": {} 57 | }, 58 | { 59 | "id": "/subscriptions/CCCCCCCC-CCCC-CCCC-CCCCCCCC/resourceGroups/ResourceGroupC1/providers/resourceProviderNamespace/resourceType/resourceName", 60 | "name": "Storage-20212223", 61 | "location": "eastus", 62 | "kind": "KindStorageV2", 63 | "properties": { 64 | "allowBlobPublicAccess": false, 65 | "allowCrossTenantReplication": true, 66 | "allowSharedKeyAccess": true, 67 | "minimumTlsVersion": "TLS1_2", 68 | "supportsHttpsTrafficOnly": true 69 | }, 70 | "tags": {} 71 | }, 72 | { 73 | "id": "/subscriptions/CCCCCCCC-CCCC-CCCC-CCCCCCCC/resourceGroups/ResourceGroupC2/providers/resourceProviderNamespace/resourceType/resourceName", 74 | "name": "Storage-24252627", 75 | "location": "eastus", 76 | "kind": "KindStorageV2", 77 | "properties": { 78 | "allowBlobPublicAccess": false, 79 | "allowCrossTenantReplication": true, 80 | "allowSharedKeyAccess": true, 81 | "minimumTlsVersion": "TLS1_2", 82 | "supportsHttpsTrafficOnly": true 83 | }, 84 | "tags": {} 85 | } 86 | ] -------------------------------------------------------------------------------- /azure/test-data/storage-blobs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "storage_account_name": "storagedo89vl86ey", 4 | "blob_urls": [ 5 | "https://storagedo89vl86ey.blob.core.windows.net/containercfuyyey5nm/test1.txt", 6 | "https://storagedo89vl86ey.blob.core.windows.net/containercfuyyey5nm/test2.txt", 7 | "https://storagedo89vl86ey.blob.core.windows.net/containercfuyyey5nm/test3.txt", 8 | "https://storagedo89vl86ey.blob.core.windows.net/containercfuyyey5nm/test4.txt" 9 | ] 10 | }, 11 | { 12 | "storage_account_name": "storagedo89vl86ey", 13 | "blob_urls": [ 14 | "https://storagedo89vl86ey.blob.core.windows.net/containercfuyyey5nm/test1.txt", 15 | "https://storagedo89vl86ey.blob.core.windows.net/containercfuyyey5nm/test2.txt", 16 | "https://storagedo89vl86ey.blob.core.windows.net/containercfuyyey5nm/test3.txt", 17 | "https://storagedo89vl86ey.blob.core.windows.net/containercfuyyey5nm/test4.txt" 18 | ] 19 | } 20 | ] -------------------------------------------------------------------------------- /azure/test-data/vms.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "TestVM-1", 4 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA1/providers/Microsoft.Compute/virtualMachines/TestVM-1", 5 | "location": "us-east-1", 6 | "properties": { 7 | "networkProfile": { 8 | "networkInterfaces": [ 9 | { 10 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA1/providers/Microsoft.Network/networkInterfaces/NetworkInterface1" 11 | } 12 | ] 13 | }, 14 | "osProfile": { 15 | "adminUsername": "admin" 16 | } 17 | } 18 | }, 19 | { 20 | "name": "TestVM-2", 21 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA2/providers/Microsoft.Compute/virtualMachines/TestVM-2", 22 | "location": "us-west-2", 23 | "properties": { 24 | "networkProfile": { 25 | "networkInterfaces": [ 26 | { 27 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA2/providers/Microsoft.Network/networkInterfaces/NetworkInterface2" 28 | }, 29 | { 30 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA2/providers/Microsoft.Network/networkInterfaces/NetworkInterface3" 31 | } 32 | ] 33 | }, 34 | "osProfile": { 35 | "adminUsername": "admin" 36 | } 37 | } 38 | }, 39 | { 40 | "name": "TestVM-3", 41 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA1/providers/Microsoft.Compute/virtualMachines/TestVM-3", 42 | "location": "us-east-2", 43 | "properties": { 44 | "networkProfile": { 45 | "networkInterfaces": [ 46 | { 47 | "id": "/subscriptions/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA/resourceGroups/ResourceGroupA1/providers/Microsoft.Network/networkInterfaces/NetworkInterface4" 48 | } 49 | ] 50 | }, 51 | "osProfile": { 52 | "adminUsername": "admin" 53 | } 54 | } 55 | } 56 | ] -------------------------------------------------------------------------------- /azure/vms_test.go: -------------------------------------------------------------------------------- 1 | package azure 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "testing" 7 | 8 | "github.com/BishopFox/cloudfox/globals" 9 | "github.com/BishopFox/cloudfox/internal" 10 | ) 11 | 12 | func TestAzVMsCommand(t *testing.T) { 13 | fmt.Println() 14 | fmt.Println("[test case] Azure vms Command") 15 | 16 | // Test case parameters 17 | internal.MockFileSystem(true) 18 | subtests := []struct { 19 | name string 20 | azTenantID string 21 | azSubscriptionID string 22 | azVerbosity int 23 | azOutputFormat string 24 | azOutputDirectory string 25 | version string 26 | resourcesTestFile string 27 | vmsTestFile string 28 | nicsTestFile string 29 | publicIPsTestFile string 30 | wrapTableOutput bool 31 | azMergedTable bool 32 | }{ 33 | { 34 | name: "./cloudfox azure vms --tenant 11111111-1111-1111-1111-11111111", 35 | azTenantID: "11111111-1111-1111-1111-11111111", 36 | azSubscriptionID: "", 37 | azVerbosity: 2, 38 | azOutputDirectory: "~/.cloudfox", 39 | azOutputFormat: "all", 40 | version: "DEV", 41 | resourcesTestFile: "./test-data/resources.json", 42 | vmsTestFile: "./test-data/vms.json", 43 | nicsTestFile: "./test-data/nics.json", 44 | publicIPsTestFile: "./test-data/public-ips.json", 45 | wrapTableOutput: false, 46 | azMergedTable: false, 47 | }, 48 | { 49 | name: "./cloudfox azure vms --subscription AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA", 50 | azTenantID: "", 51 | azSubscriptionID: "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA", 52 | azVerbosity: 2, 53 | azOutputDirectory: "~/.cloudfox", 54 | azOutputFormat: "all", 55 | version: "DEV", 56 | resourcesTestFile: "./test-data/resources.json", 57 | vmsTestFile: "./test-data/vms.json", 58 | nicsTestFile: "./test-data/nics.json", 59 | publicIPsTestFile: "./test-data/public-ips.json", 60 | wrapTableOutput: false, 61 | azMergedTable: false, 62 | }, 63 | { 64 | name: "./cloudfox azure vms", 65 | azVerbosity: 2, 66 | azOutputFormat: "all", 67 | azOutputDirectory: "~/.cloudfox", 68 | version: "DEV", 69 | resourcesTestFile: "./test-data/resources.json", 70 | vmsTestFile: "./test-data/vms.json", 71 | nicsTestFile: "./test-data/nics.json", 72 | publicIPsTestFile: "./test-data/public-ips.json", 73 | wrapTableOutput: false, 74 | azMergedTable: false, 75 | }, 76 | } 77 | 78 | // Mocked functions to simulate Azure calls and responses 79 | GetSubscriptions = mockedGetSubscriptions 80 | getResourceGroups = mockedGetResourceGroups 81 | getComputeVMsPerResourceGroup = mockedGetComputeVMsPerResourceGroup 82 | getNICdetails = mockedGetNICdetails 83 | getPublicIP = mockedGetPublicIP 84 | 85 | for _, s := range subtests { 86 | fmt.Println() 87 | fmt.Printf("[subtest] %s\n", s.name) 88 | globals.RESOURCES_TEST_FILE = s.resourcesTestFile 89 | globals.VMS_TEST_FILE = s.vmsTestFile 90 | globals.NICS_TEST_FILE = s.nicsTestFile 91 | globals.PUBLIC_IPS_TEST_FILE = s.publicIPsTestFile 92 | 93 | err := AzVMsCommand(s.azTenantID, s.azSubscriptionID, s.azOutputFormat, s.azOutputDirectory, s.version, 2, s.wrapTableOutput, s.azMergedTable) 94 | if err != nil { 95 | log.Fatalf(err.Error()) 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /azure/whoami_test.go: -------------------------------------------------------------------------------- 1 | package azure 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/BishopFox/cloudfox/globals" 8 | "github.com/BishopFox/cloudfox/internal" 9 | "github.com/aws/smithy-go/ptr" 10 | ) 11 | 12 | func TestAzWhoamiCommand(t *testing.T) { 13 | fmt.Println() 14 | fmt.Println("[test case] Azure Whoami Command") 15 | 16 | // Mocked functions to simulate Azure calls and responses 17 | getTenants = mockedGetTenants 18 | GetSubscriptions = mockedGetSubscriptions 19 | getResourceGroups = mockedGetResourceGroups 20 | 21 | // Test case parameters 22 | internal.MockFileSystem(true) 23 | subtests := []struct { 24 | name string 25 | resourcesTestFile string 26 | azExtendedFilter bool 27 | version string 28 | wrapTableOutput bool 29 | azOutputDirectory string 30 | }{ 31 | { 32 | name: "./cloudfox azure whoami", 33 | resourcesTestFile: "./test-data/resources.json", 34 | azExtendedFilter: false, 35 | version: "DEV", 36 | wrapTableOutput: false, 37 | azOutputDirectory: "~/.cloudfox", 38 | }, 39 | { 40 | name: "./cloudfox azure whoami --extended", 41 | resourcesTestFile: "./test-data/resources.json", 42 | azExtendedFilter: true, 43 | version: "DEV", 44 | wrapTableOutput: true, 45 | azOutputDirectory: "~/.cloudfox", 46 | }, 47 | } 48 | for _, s := range subtests { 49 | globals.RESOURCES_TEST_FILE = s.resourcesTestFile 50 | fmt.Println() 51 | fmt.Printf("[subtest] %s\n", s.name) 52 | AzWhoamiCommand(s.azOutputDirectory, s.version, s.wrapTableOutput, 1, false) 53 | } 54 | } 55 | 56 | func TestTest(t *testing.T) { 57 | fmt.Println(ptr.ToString(GetTenantIDPerSubscription("4cedc5dd-e3ad-468d-bf66-32e31bdb9148"))) 58 | } 59 | -------------------------------------------------------------------------------- /gcp/commands/whoami.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | 6 | OAuthService "github.com/BishopFox/cloudfox/gcp/services/oauthService" 7 | "github.com/BishopFox/cloudfox/globals" 8 | 9 | "github.com/BishopFox/cloudfox/internal" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var GCPWhoAmICommand = &cobra.Command{ 14 | Use: globals.GCP_WHOAMI_MODULE_NAME, 15 | Short: "Display the email address of the GCP authenticated user", 16 | Args: cobra.NoArgs, 17 | Run: runGCPWhoAmICommand, 18 | } 19 | 20 | func runGCPWhoAmICommand(cmd *cobra.Command, args []string) { 21 | logger := internal.NewLogger() 22 | 23 | // Initialize the OAuthService 24 | oauthService := OAuthService.NewOAuthService() 25 | 26 | // Call the WhoAmI function 27 | principal, err := oauthService.WhoAmI() 28 | if err != nil { 29 | logger.ErrorM(fmt.Sprintf("Error retrieving token info: %v", err), globals.GCP_WHOAMI_MODULE_NAME) 30 | return 31 | } 32 | 33 | logger.InfoM(fmt.Sprintf("authenticated user email: %s", principal.Email), globals.GCP_WHOAMI_MODULE_NAME) 34 | } 35 | -------------------------------------------------------------------------------- /gcp/services/bigqueryService/bigqueryService_test.go: -------------------------------------------------------------------------------- 1 | package bigqueryservice_test 2 | -------------------------------------------------------------------------------- /gcp/services/cloudStorageService/cloudStorageService_test.go: -------------------------------------------------------------------------------- 1 | package cloudstorageservice_test 2 | -------------------------------------------------------------------------------- /gcp/services/computeEngineService/computeEngineService.go: -------------------------------------------------------------------------------- 1 | package computeengineservice 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "google.golang.org/api/compute/v1" 9 | ) 10 | 11 | type ComputeEngineService struct { 12 | // DataStoreService datastoreservice.DataStoreService 13 | } 14 | 15 | func New() *ComputeEngineService { 16 | return &ComputeEngineService{} 17 | } 18 | 19 | type ComputeEngineInfo struct { 20 | Name string 21 | ID string 22 | Zone string 23 | State string 24 | ExternalIP string 25 | InternalIP string 26 | ServiceAccounts []*compute.ServiceAccount // Assuming role is derived from service accounts 27 | NetworkInterfaces []*compute.NetworkInterface 28 | Tags *compute.Tags 29 | ProjectID string 30 | } 31 | 32 | // Retrieves instances from all regions and zones for a project without using concurrency. 33 | func (ces *ComputeEngineService) Instances(projectID string) ([]ComputeEngineInfo, error) { 34 | ctx := context.Background() 35 | computeService, err := compute.NewService(ctx) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | regions, err := computeService.Regions.List(projectID).Do() 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | var instanceInfos []ComputeEngineInfo 46 | for _, region := range regions.Items { 47 | for _, zoneURL := range region.Zones { 48 | zone := getZoneNameFromURL(zoneURL) 49 | instanceList, err := computeService.Instances.List(projectID, zone).Do() 50 | if err != nil { 51 | return nil, fmt.Errorf("error retrieving instances from zone %s: %v", zone, err) 52 | } 53 | for _, instance := range instanceList.Items { 54 | info := ComputeEngineInfo{ 55 | Name: instance.Name, 56 | ID: fmt.Sprintf("%v", instance.Id), 57 | Zone: zoneURL, 58 | State: instance.Status, 59 | ExternalIP: getExternalIP(instance), 60 | InternalIP: getInternalIP(instance), 61 | ServiceAccounts: instance.ServiceAccounts, 62 | NetworkInterfaces: instance.NetworkInterfaces, 63 | Tags: instance.Tags, 64 | ProjectID: projectID, 65 | } 66 | instanceInfos = append(instanceInfos, info) 67 | } 68 | } 69 | } 70 | return instanceInfos, nil 71 | } 72 | 73 | // Returns the zone from a GCP URL string with the zone in it 74 | func getZoneNameFromURL(zoneURL string) string { 75 | splits := strings.Split(zoneURL, "/") 76 | return splits[len(splits)-1] 77 | } 78 | 79 | // getExternalIP extracts the external IP address from an instance if available. 80 | func getExternalIP(instance *compute.Instance) string { 81 | for _, iface := range instance.NetworkInterfaces { 82 | for _, accessConfig := range iface.AccessConfigs { 83 | if accessConfig.NatIP != "" { 84 | return accessConfig.NatIP 85 | } 86 | } 87 | } 88 | return "" 89 | } 90 | 91 | // getInternalIP extracts the internal IP address from an instance. 92 | func getInternalIP(instance *compute.Instance) string { 93 | if len(instance.NetworkInterfaces) > 0 { 94 | return instance.NetworkInterfaces[0].NetworkIP 95 | } 96 | return "" 97 | } 98 | 99 | // TODO consider just getting the emails of the service account and returning a []string 100 | -------------------------------------------------------------------------------- /gcp/services/computeEngineService/computeEngineService_test.go: -------------------------------------------------------------------------------- 1 | package computeengineservice 2 | -------------------------------------------------------------------------------- /gcp/services/iamService/access-tokens.go: -------------------------------------------------------------------------------- 1 | package iamservice 2 | 3 | // import ( 4 | // "fmt" 5 | // "strings" 6 | // "time" 7 | // "github.com/fatih/color" 8 | // "github.com/BishopFox/cloudfox/internal/gcp" 9 | // "github.com/BishopFox/cloudfox/internal" 10 | // "github.com/BishopFox/cloudfox/globals" 11 | // ) 12 | 13 | // type AccessTokensModule struct { 14 | // // Filtering data 15 | // Organizations []string 16 | // Folders []string 17 | // Projects []string 18 | // } 19 | 20 | // // Be incorporated in iam.go 21 | // func (m *AccessTokensModule) PrintAccessTokens(outputFormat string, outputDirectory string, verbosity int) error { 22 | // GCPLogger.InfoM(fmt.Sprintf("Enumerating GCP local access tokens (%s, %s)...", color.CyanString("default user token"), color.RedString("application-default token")), globals.GCP_ACCESSTOKENS_MODULE_NAME) 23 | // tokens := gcp.ReadRefreshTokens() 24 | // accessTokens := gcp.ReadAccessTokens() 25 | // applicationdefaulthash := gcp.GetDefaultApplicationHash() 26 | // activeAccount := gcp.GetActiveAccount() 27 | 28 | // var tableHead = []string{"Account", "Credential Type", "Validity", "Refresh Token", "Refresh Token needs password"} 29 | // var tableBody [][]string 30 | // for _, accessToken := range accessTokens { 31 | // // prepare token's associated ID (email or hash) 32 | // var emailinfo string 33 | // if (accessToken.AccountID == activeAccount) { 34 | // emailinfo = color.BlueString(accessToken.AccountID) 35 | // } else { 36 | // emailinfo = accessToken.AccountID 37 | // } 38 | 39 | // // format expiration time 40 | // Exp, _ := time.Parse(time.RFC3339, accessToken.TokenExpiry) 41 | // var timeinfo string 42 | // if Exp.After(time.Now()) { 43 | // timeinfo = Exp.Format(time.RFC1123) 44 | // } else { 45 | // timeinfo = "EXPIRED" 46 | // } 47 | 48 | // // display token type, user or application credential 49 | // var tokentype string 50 | // if (strings.Contains(accessToken.AccountID, "@")) { 51 | // tokentype = "User" 52 | // } else { 53 | // tokentype = "Application" 54 | // } 55 | // // is there an associated refres htoken ? 56 | // var refreshtokeninfo string 57 | // refreshtokeninfo = "No" 58 | // for _, refreshtoken := range tokens { 59 | // if (accessToken.AccountID == refreshtoken.Email) { 60 | // refreshtokeninfo = "Yes" 61 | // } else if (accessToken.AccountID == applicationdefaulthash) { 62 | // refreshtokeninfo = "Yes" 63 | // emailinfo = color.RedString(emailinfo) 64 | // } 65 | // } 66 | 67 | // // is there a proof of reauthentication to use the refresh 68 | // // token ? If not, password is needed 69 | // var raptinfo string 70 | // if refreshtokeninfo == "No" { 71 | // raptinfo = "-" 72 | // } else if accessToken.RaptToken.Valid { 73 | // raptinfo = "No" 74 | // } else { 75 | // if (tokentype == "Application") { 76 | // raptinfo = "TBD" 77 | // } else { 78 | // raptinfo = "Yes" 79 | // } 80 | // } 81 | // tableBody = append( 82 | // tableBody, 83 | // []string{ 84 | // emailinfo, 85 | // tokentype, 86 | // timeinfo, 87 | // refreshtokeninfo, 88 | // raptinfo, 89 | // }) 90 | // } 91 | // internal.PrintTableToScreen(tableHead, tableBody, false) 92 | // return nil 93 | // } 94 | -------------------------------------------------------------------------------- /gcp/services/iamService/iamService_test.go: -------------------------------------------------------------------------------- 1 | package iamservice_test 2 | -------------------------------------------------------------------------------- /gcp/services/models/models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type GenericIterator[T any] interface { 4 | Next() (*T, error) 5 | } 6 | -------------------------------------------------------------------------------- /gcp/services/networkService/networkService_test.go: -------------------------------------------------------------------------------- 1 | package networkservice_test 2 | 3 | import ( 4 | "testing" 5 | 6 | networkservice "github.com/BishopFox/cloudfox/gcp/services/networkService" 7 | ) 8 | 9 | func TestIsInternalIP(t *testing.T) { 10 | tests := []struct { 11 | input string 12 | expect bool 13 | }{ 14 | // IPv4 tests 15 | {"10.1.2.3", true}, // falls within 10.0.0.0/8 16 | {"172.17.0.1", true}, // falls within 172.16.0.0/12 17 | {"192.168.10.1", true}, // falls within 192.168.0.0/16 18 | {"11.1.2.3", false}, // public IP 19 | {"172.32.0.1", false}, // public IP 20 | {"192.169.0.1", false}, // public IP 21 | {"8.8.8.8", false}, // public IP 22 | // IPv6 tests 23 | {"fc00::1", true}, // falls within fc00::/7 24 | {"fd00::1", true}, // falls within fd00::/8 25 | {"fe80::1", false}, // link-local address, not ULA 26 | {"2001:0db8:85a3::8a2e", false}, // public IP 27 | } 28 | 29 | for _, test := range tests { 30 | result := networkservice.IsInternalIP(test.input) 31 | if result != test.expect { 32 | t.Errorf("IsInternalIP(%s) = %v; want %v", test.input, result, test.expect) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /gcp/services/oauthService/oauthService.go: -------------------------------------------------------------------------------- 1 | package oauthservice 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "strings" 10 | 11 | "golang.org/x/oauth2/google" 12 | ) 13 | 14 | // Principal struct modified to map JSON response fields. 15 | type Principal struct { 16 | Email string `json:"email"` 17 | Scope []string 18 | } 19 | 20 | // Temporary struct to match the JSON structure. 21 | type tokenInfoResponse struct { 22 | Email string `json:"email"` 23 | Scope string `json:"scope"` // Keep as string to match JSON. 24 | } 25 | 26 | // OAuthService struct definition (unchanged) 27 | type OAuthService struct { 28 | // This struct can be expanded in the future if necessary. 29 | } 30 | 31 | // NewOAuthService creates a new instance of OAuthService. 32 | func NewOAuthService() *OAuthService { 33 | return &OAuthService{} 34 | } 35 | 36 | // WhoAmI function returns the Principal struct showing the email and scope of the currently authenticated user by generating an oauth token 37 | func (s *OAuthService) WhoAmI() (*Principal, error) { 38 | ctx := context.Background() 39 | ts, err := google.DefaultTokenSource(ctx, "") 40 | if err != nil { 41 | return nil, fmt.Errorf("failed to obtain default token source: %v", err) 42 | } 43 | 44 | token, err := ts.Token() 45 | if err != nil { 46 | return nil, fmt.Errorf("failed to get token from token source: %v", err) 47 | } 48 | 49 | tokenInfo, err := queryTokenInfo(token.AccessToken) 50 | if err != nil { 51 | return nil, fmt.Errorf(fmt.Sprintf("failed to retrieve metada of the token with error: %s", err.Error())) 52 | } 53 | // Split the scope string into a slice of strings. 54 | scopes := strings.Split(tokenInfo.Scope, " ") 55 | 56 | return &Principal{ 57 | Email: tokenInfo.Email, 58 | Scope: scopes, 59 | }, nil 60 | } 61 | 62 | // queryTokenInfo function modified to return (*Principal, error). 63 | func queryTokenInfo(accessToken string) (*tokenInfoResponse, error) { 64 | url := fmt.Sprintf("https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=%s", accessToken) 65 | 66 | resp, err := http.Get(url) 67 | if err != nil { 68 | return nil, fmt.Errorf("error making request to tokeninfo endpoint: %v", err) 69 | } 70 | defer resp.Body.Close() 71 | 72 | body, err := io.ReadAll(resp.Body) 73 | if err != nil { 74 | return nil, fmt.Errorf("error reading tokeninfo response body: %v", err) 75 | } 76 | 77 | var response tokenInfoResponse 78 | // Unmarshal the JSON response body into the Principal struct. 79 | if err := json.Unmarshal(body, &response); err != nil { 80 | return nil, fmt.Errorf("error unmarshalling tokeninfo response body: %v", err) 81 | } 82 | 83 | return &response, nil 84 | } 85 | -------------------------------------------------------------------------------- /gcp/services/secretsService/secretsService.go: -------------------------------------------------------------------------------- 1 | package secretservice 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | secretmanager "cloud.google.com/go/secretmanager/apiv1" 8 | secretmanagerpb "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb" 9 | "github.com/googleapis/gax-go/v2" 10 | "google.golang.org/api/iterator" 11 | ) 12 | 13 | // Wrappers and abstracting types to facilitate mocking the client responses 14 | type Iterator interface { 15 | Next() (*secretmanagerpb.Secret, error) 16 | } 17 | 18 | type SecretsManagerClientWrapper struct { 19 | Closer func() error 20 | SecretLister func(ctx context.Context, req *secretmanagerpb.ListSecretsRequest, opts ...gax.CallOption) Iterator 21 | } 22 | 23 | func (w *SecretsManagerClientWrapper) Close() error { 24 | return w.Closer() 25 | } 26 | 27 | func (w *SecretsManagerClientWrapper) ListSecrets(ctx context.Context, req *secretmanagerpb.ListSecretsRequest, opts ...gax.CallOption) Iterator { 28 | return w.SecretLister(ctx, req, opts...) 29 | 30 | } 31 | 32 | type SecretsService struct { 33 | Client *SecretsManagerClientWrapper 34 | } 35 | 36 | // New function to facilitate using the ss client 37 | func New(client *secretmanager.Client) SecretsService { 38 | ss := SecretsService{ 39 | Client: &SecretsManagerClientWrapper{ 40 | Closer: client.Close, 41 | SecretLister: func(ctx context.Context, req *secretmanagerpb.ListSecretsRequest, opts ...gax.CallOption) Iterator { 42 | return client.ListSecrets(ctx, req, opts...) 43 | }, 44 | }, 45 | } 46 | return ss 47 | } 48 | 49 | type SecretInfo struct { 50 | Name string `json:"name"` 51 | ProjectID string `json:"projectID"` 52 | CreationTime string `json:"creationTime"` 53 | Labels map[string]string `json:"labels"` 54 | Rotation string `json:"rotation,omitempty"` 55 | } 56 | 57 | func (ss *SecretsService) Secrets(projectID string) ([]SecretInfo, error) { 58 | var secrets []SecretInfo 59 | req := &secretmanagerpb.ListSecretsRequest{ 60 | Parent: fmt.Sprintf("projects/%s", projectID), 61 | } 62 | 63 | ctx := context.Background() 64 | it := ss.Client.ListSecrets(ctx, req) 65 | for { 66 | resp, err := it.Next() //Here it errors out 67 | if err == iterator.Done { 68 | break 69 | } 70 | if err != nil { 71 | return nil, fmt.Errorf("failed to list secrets: %v", err) 72 | } 73 | 74 | secrets = append(secrets, SecretInfo{ 75 | Name: resp.Name, 76 | ProjectID: projectID, 77 | CreationTime: resp.CreateTime.AsTime().String(), 78 | Labels: resp.Labels, 79 | Rotation: resp.Rotation.String(), 80 | }) 81 | } 82 | return secrets, nil 83 | } 84 | -------------------------------------------------------------------------------- /globals/azure.go: -------------------------------------------------------------------------------- 1 | package globals 2 | 3 | // Module directory tree 4 | const AZ_DIR_BASE = "azure" 5 | const AZ_DIR_TEN = "tenants" 6 | const AZ_DIR_SUB = "subscriptions" 7 | 8 | // Test file full names and paths 9 | var STORAGE_ACCOUNTS_TEST_FILE string 10 | var VMS_TEST_FILE string 11 | var NICS_TEST_FILE string 12 | var PUBLIC_IPS_TEST_FILE string 13 | var RESOURCES_TEST_FILE string 14 | var ROLE_DEFINITIONS_TEST_FILE string 15 | var ROLE_ASSIGNMENTS_TEST_FILE string 16 | var AAD_USERS_TEST_FILE string 17 | 18 | // Module names 19 | const AZ_WHOAMI_MODULE_NAME = "whoami" 20 | const AZ_INVENTORY_MODULE_NAME = "inventory" 21 | const AZ_VMS_MODULE_NAME = "vms" 22 | const AZ_RBAC_MODULE_NAME = "rbac" 23 | const AZ_STORAGE_MODULE_NAME = "storage" 24 | 25 | // Microsoft endpoints 26 | const AZ_RESOURCE_MANAGER_ENDPOINT = "https://management.azure.com/" 27 | const AZ_GRAPH_ENDPOINT = "https://graph.windows.net/" 28 | -------------------------------------------------------------------------------- /globals/gcp.go: -------------------------------------------------------------------------------- 1 | package globals 2 | 3 | // Module names 4 | // const GCP_WHOAMI_MODULE_NAME = "whoami" 5 | const GCP_ARTIFACT_RESGISTRY_MODULE_NAME string = "artifact-registry" 6 | const GCP_BIGQUERY_MODULE_NAME string = "bigquery" 7 | const GCP_BUCKETS_MODULE_NAME string = "buckets" 8 | const GCP_INSTANCES_MODULE_NAME string = "instances" 9 | const GCP_IAM_MODULE_NAME string = "iam" 10 | const GCP_SECRETS_MODULE_NAME string = "secrets" 11 | const GCP_WHOAMI_MODULE_NAME string = "whoami" 12 | 13 | // const GCP_INVENTORY_MODULE_NAME string = "inventory" 14 | // const GCP_GCLOUD_REFRESH_TOKENS_DB_PATH = ".config/gcloud/credentials.db" 15 | // const GCP_GCLOUD_ACCESS_TOKENS_DB_PATH = ".config/gcloud/access_tokens.db" 16 | // const GCP_GCLOUD_DEFAULT_CONFIG_PATH = ".config/gcloud/configurations/config_default" 17 | // const GCP_GCLOUD_APPLICATION_DEFAULT_PATH = ".config/gcloud/application_default_credentials.json" 18 | -------------------------------------------------------------------------------- /globals/utils.go: -------------------------------------------------------------------------------- 1 | package globals 2 | 3 | const CLOUDFOX_USER_AGENT = "cloudfox" 4 | const CLOUDFOX_LOG_FILE_DIR_NAME = ".cloudfox" 5 | const CLOUDFOX_BASE_DIRECTORY = "cloudfox-output" 6 | const LOOT_DIRECTORY_NAME = "loot" 7 | const CLOUDFOX_VERSION = "1.15.0" 8 | -------------------------------------------------------------------------------- /internal/array.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "bufio" 5 | "os" 6 | ) 7 | 8 | func LoadFileLinesIntoArray(input string) []string { 9 | file, err := os.Open(input) 10 | if err != nil { 11 | return []string{input} 12 | } 13 | defer file.Close() 14 | 15 | scanner := bufio.NewScanner(file) 16 | scanner.Split(bufio.ScanLines) 17 | 18 | var text []string 19 | for scanner.Scan() { 20 | text = append(text, scanner.Text()) 21 | } 22 | 23 | return text 24 | } 25 | 26 | // Checks if element is part of array. 27 | func Contains(element string, array []string) bool { 28 | for _, v := range array { 29 | if v == element { 30 | return true 31 | } 32 | } 33 | return false 34 | } 35 | -------------------------------------------------------------------------------- /internal/aws/policy/condition.go: -------------------------------------------------------------------------------- 1 | package policy 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html 9 | // Conditions have the following general structure: 10 | // "Condition" : { "{condition-operator}" : { "{condition-key}" : "{condition-value}" }} 11 | type PolicyStatementCondition map[string]map[string]ListOrString 12 | 13 | func (psc *PolicyStatementCondition) IsEmpty() bool { 14 | if psc == nil { 15 | return true 16 | } 17 | 18 | return len(*psc) == 0 19 | } 20 | 21 | // IsScopedOnAccountOrOrganization returns true if the policy condition ensures access only for specific 22 | // AWS accounts or organizations. If may return false even if access is restricted in such a way. 23 | // Such policies should be reported to the user and analyzed case by case to judge if conditions are sufficiently restrictive. 24 | func (psc *PolicyStatementCondition) IsScopedOnAccountOrOrganization() bool { 25 | for condition, kv := range *psc { 26 | for k, v := range kv { 27 | if strings.ToLower(condition) == "stringequals" && contains([]string{ 28 | "aws:sourceowner", // https://docs.aws.amazon.com/sns/latest/dg/sns-access-policy-use-cases.html#source-account-versus-source-owner 29 | "aws:sourceaccount", // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-sourceaccount 30 | "aws:principalaccount", // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-principalaccount 31 | "aws:principalorgid", // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-principalorgid 32 | "sns:endpoint", // https://docs.aws.amazon.com/sns/latest/dg/sns-using-identity-based-policies.html#sns-policy-keys 33 | }, strings.ToLower(k)) { 34 | return len(v) > 0 35 | } 36 | 37 | if strings.ToLower(condition) == "stringlike" && contains([]string{ 38 | "sns:endpoint", // https://docs.aws.amazon.com/sns/latest/dg/sns-using-identity-based-policies.html#sns-policy-keys 39 | }, strings.ToLower(k)) { 40 | return isAccountIDInWildcardPattern(v) 41 | } 42 | 43 | // handling for aws:SourceArn 44 | // https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-sourcearn 45 | if contains([]string{ 46 | "arnequals", 47 | "stringequals", 48 | }, strings.ToLower(condition)) && strings.ToLower(k) == "aws:sourcearn" { 49 | return len(v) > 0 // this means there is an exact AWS resource 50 | } 51 | 52 | if contains([]string{ 53 | "arnlike", 54 | "stringlike", 55 | }, strings.ToLower(condition)) && strings.ToLower(k) == "aws:sourcearn" { 56 | return isAccountIDInWildcardPattern(v) 57 | } 58 | } 59 | } 60 | 61 | return false 62 | } 63 | 64 | // isAccountIDInWildcardPattern returns true iff at least one entry in v 65 | // contains an ARN pattern with a full account ID. 66 | func isAccountIDInWildcardPattern(v []string) bool { 67 | for _, e := range v { 68 | s := getAccountIDInARN(e) 69 | if s != "" { 70 | return true 71 | } 72 | } 73 | 74 | return false 75 | } 76 | 77 | // Format: arn:aws:::: 78 | var reAccountIDInARN = regexp.MustCompile(`arn:aws:[a-zA-Z0-9]+:[a-zA-Z0-9-]*:([0-9]+):`) 79 | 80 | func getAccountIDInARN(arn string) string { 81 | match := reAccountIDInARN.FindStringSubmatch(arn) 82 | if len(match) != 2 { 83 | return "" 84 | } 85 | 86 | return match[1] 87 | } 88 | -------------------------------------------------------------------------------- /internal/aws/policy/condition_test.go: -------------------------------------------------------------------------------- 1 | package policy 2 | 3 | import "testing" 4 | 5 | func TestGetAccountIDInARN(t *testing.T) { 6 | tests := []struct { 7 | S string 8 | want string 9 | }{ 10 | {S: "abc123", want: ""}, 11 | {S: "arn:aws:iam::123456789012:root", want: "123456789012"}, 12 | {S: "arn:aws:iam::123456789012:role/any-role-name", want: "123456789012"}, 13 | {S: "arn:aws:sts::123456789012:assumed-role/any-role-name/session-name", want: "123456789012"}, 14 | {S: "arn:aws:iam::123456789012:user/any-user-name", want: "123456789012"}, 15 | {S: "arn:aws:sts::123456789012:federated-user/any-user-name", want: "123456789012"}, 16 | {S: "arn:aws:cloudwatch:us-east-2:123456789012:alarm:*", want: "123456789012"}, 17 | } 18 | 19 | for _, tt := range tests { 20 | 21 | actual := getAccountIDInARN(tt.S) 22 | if tt.want != actual { 23 | t.Errorf("getAccountIDInARN(%s) wrong: want %s but got %s", tt.S, tt.want, actual) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/aws/policy/principal.go: -------------------------------------------------------------------------------- 1 | package policy 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "strings" 7 | ) 8 | 9 | type PolicyStatementPrincipal struct { 10 | S string 11 | O PolicyStatementPrincipalObject 12 | } 13 | 14 | func (psp *PolicyStatementPrincipal) UnmarshalJSON(b []byte) error { 15 | var s string 16 | if err := json.Unmarshal(b, &s); err == nil { 17 | psp.S = s 18 | return nil 19 | } 20 | 21 | var obj PolicyStatementPrincipalObject 22 | if err := json.Unmarshal(b, &obj); err == nil { 23 | psp.O = obj 24 | return nil 25 | } 26 | 27 | return errors.New("principal is not a string or complex object") 28 | } 29 | 30 | func (psp *PolicyStatementPrincipal) MarshalJSON() ([]byte, error) { 31 | if psp.S != "" { 32 | return json.Marshal(psp.S) 33 | } 34 | 35 | return json.Marshal(psp.O) 36 | } 37 | 38 | func (psp *PolicyStatementPrincipal) IsEmpty() bool { 39 | return psp.S == "" && psp.O.IsEmpty() 40 | } 41 | 42 | func (psp *PolicyStatementPrincipal) IsPublic() bool { 43 | return psp.S == "*" || psp.O.IsPublic() 44 | } 45 | 46 | type PolicyStatementPrincipalObject struct { 47 | AWS ListOrString `json:"AWS,omitempty"` 48 | CanonicalUser ListOrString `json:"CanonicalUser,omitempty"` 49 | Federated ListOrString `json:"Federated,omitempty"` 50 | Service ListOrString `json:"Service,omitempty"` 51 | } 52 | 53 | func (pspo *PolicyStatementPrincipalObject) IsEmpty() bool { 54 | return len(pspo.AWS) == 0 && 55 | len(pspo.CanonicalUser) == 0 && 56 | len(pspo.Federated) == 0 && 57 | len(pspo.Service) == 0 58 | } 59 | 60 | func (pspo *PolicyStatementPrincipalObject) IsPublic() bool { 61 | for _, s := range pspo.AWS { 62 | if s == "*" { 63 | return true 64 | } 65 | } 66 | 67 | return false 68 | } 69 | 70 | func (pspo *PolicyStatementPrincipalObject) GetListOfPrincipals() []string { 71 | var allPrincipals []string 72 | allPrincipals = append(allPrincipals, pspo.AWS...) 73 | allPrincipals = append(allPrincipals, pspo.CanonicalUser...) 74 | allPrincipals = append(allPrincipals, pspo.Federated...) 75 | allPrincipals = append(allPrincipals, pspo.Service...) 76 | return allPrincipals 77 | } 78 | 79 | type ListOrString []string 80 | 81 | func (ls *ListOrString) UnmarshalJSON(b []byte) error { 82 | var s string 83 | if err := json.Unmarshal(b, &s); err == nil { 84 | *ls = append(*ls, s) 85 | return nil 86 | } 87 | 88 | var ss []string 89 | if err := json.Unmarshal(b, &ss); err == nil { 90 | *ls = ss 91 | return nil 92 | } 93 | 94 | return errors.New("not a string or list of strings") 95 | } 96 | 97 | // create a method on *PolicyStatementPrincipalObject that will determine if trusted principal is from the same account as the resource or a different account 98 | func (pspo *PolicyStatementPrincipalObject) IsTrustedPrincipalSameAccount(accountID string) bool { 99 | for _, principal := range pspo.CanonicalUser { 100 | if strings.Contains(principal, ":") { 101 | principalAccount := strings.Split(principal, ":")[4] 102 | if principalAccount == accountID { 103 | return true 104 | } 105 | } 106 | } 107 | return false 108 | } 109 | -------------------------------------------------------------------------------- /internal/aws/policy/testdata/amazon-ec2-full-access.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Action": "ec2:*", 6 | "Effect": "Allow", 7 | "Resource": "*" 8 | }, 9 | { 10 | "Effect": "Allow", 11 | "Action": "elasticloadbalancing:*", 12 | "Resource": "*" 13 | }, 14 | { 15 | "Effect": "Allow", 16 | "Action": "cloudwatch:*", 17 | "Resource": "*" 18 | }, 19 | { 20 | "Effect": "Allow", 21 | "Action": "autoscaling:*", 22 | "Resource": "*" 23 | }, 24 | { 25 | "Effect": "Allow", 26 | "Action": "iam:CreateServiceLinkedRole", 27 | "Resource": "*", 28 | "Condition": { 29 | "StringEquals": { 30 | "iam:AWSServiceName": [ 31 | "autoscaling.amazonaws.com", 32 | "ec2scheduled.amazonaws.com", 33 | "elasticloadbalancing.amazonaws.com", 34 | "spot.amazonaws.com", 35 | "spotfleet.amazonaws.com", 36 | "transitgateway.amazonaws.com" 37 | ] 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /internal/aws/policy/testdata/sns-conditionally-public.json: -------------------------------------------------------------------------------- 1 | {"Statement":[{"Effect":"Allow","Principal":"*","Action":"SNS:Publish","Resource":"arn:aws:sns:us-east-1:123456789012:MyFirstTopic","Condition":{"StringEquals":{"aws:sourceVpce":"vpce-1a2b3c4d"}}}]} 2 | -------------------------------------------------------------------------------- /internal/aws/policy/testdata/sns-shared-via-condition.json: -------------------------------------------------------------------------------- 1 | { 2 | "Statement": [ 3 | { 4 | "Effect": "Allow", 5 | "Principal": { 6 | "AWS": "*" 7 | }, 8 | "Action": "SNS:Publish", 9 | "Resource": "arn:aws:sns:us-east-2:444455556666:MyTopic", 10 | "Condition": { 11 | "ArnLike": { 12 | "aws:SourceArn": "arn:aws:cloudwatch:us-east-2:111122223333:alarm:*" 13 | } 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /internal/aws/policy/testdata/sns-shared-with-org.json: -------------------------------------------------------------------------------- 1 | { 2 | "Statement": [ 3 | { 4 | "Effect": "Allow", 5 | "Principal": { 6 | "AWS": "*" 7 | }, 8 | "Action": "SNS:Publish", 9 | "Resource": "arn:aws:sns:us-east-2:444455556666:MyTopic", 10 | "Condition": { 11 | "StringEquals": { 12 | "aws:PrincipalOrgID": "o-yj4fwt1bwm" 13 | } 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /internal/aws/policy/testdata/sqs-conditionally-public.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Id": "anyID", 4 | "Statement": [{ 5 | "Sid":"conditionally_public", 6 | "Effect": "Allow", 7 | "Principal": "*", 8 | "Action": "sqs:*", 9 | "Resource": "arn:aws:sqs:*:111122223333:queue1", 10 | "Condition" : { 11 | "IpAddress" : { 12 | "aws:SourceIp":"192.0.2.0/24" 13 | } 14 | } 15 | }] 16 | } 17 | -------------------------------------------------------------------------------- /internal/aws/policy/testdata/sqs-public.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Id": "anyID", 4 | "Statement": [{ 5 | "Sid":"unconditionally_public", 6 | "Effect": "Allow", 7 | "Principal": { 8 | "AWS": "*" 9 | }, 10 | "Action": "sqs:*", 11 | "Resource": "arn:aws:sqs:*:111122223333:queue1" 12 | }] 13 | } 14 | -------------------------------------------------------------------------------- /internal/aws/policy/testdata/sqs-shared-with-condition.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Id": "anyID", 4 | "Statement": [{ 5 | "Sid":"conditionally_shared", 6 | "Effect": "Allow", 7 | "Principal": { 8 | "AWS": ["123456789012"] 9 | }, 10 | "Action": "sqs:*", 11 | "Resource": "arn:aws:sqs:*:111122223333:queue1", 12 | "Condition" : { 13 | "IpAddress" : { 14 | "aws:SourceIp":"192.0.2.0/24" 15 | } 16 | } 17 | }] 18 | } 19 | -------------------------------------------------------------------------------- /internal/aws/policy/testdata/sqs-shared.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Id": "anyID", 4 | "Statement": [{ 5 | "Sid":"unconditionally_shared", 6 | "Effect": "Allow", 7 | "Principal": { 8 | "AWS": [ 9 | "111122223333", 10 | "arn:aws:iam::123456789012:root" 11 | ] 12 | }, 13 | "Action": "sqs:*", 14 | "Resource": "arn:aws:sqs:*:111122223333:queue1" 15 | }] 16 | } 17 | -------------------------------------------------------------------------------- /internal/aws_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/aws/aws-sdk-go-v2/config" 8 | "github.com/spf13/afero" 9 | ) 10 | 11 | func compareSlice(a []string, b []string) bool { 12 | if len(a) != len(b) { 13 | return false 14 | } 15 | for idx, elem := range a { 16 | if elem != b[idx] { 17 | return false 18 | } 19 | } 20 | return true 21 | } 22 | 23 | // Case empty file. 24 | // Case expected format. 25 | func TestGetAllAWSProfiles(t *testing.T) { 26 | var tests = []struct { 27 | fileData []byte 28 | expectedOutput []string 29 | AWSConfirm bool 30 | caseName string 31 | }{ 32 | {[]byte(""), []string{}, true, "empty file"}, 33 | {[]byte("[default]\naws_access_key=abc\naws_secret_access_key=123\n[123]\naws_access_key=abc\naws_secret_access_key=123"), []string{"default", "123"}, true, "expected format"}, 34 | } 35 | credentialsFile := config.DefaultSharedCredentialsFilename() 36 | 37 | for _, test := range tests { 38 | UtilsFs = afero.NewMemMapFs() 39 | fmt.Printf("[*] Testing %s\n", test.caseName) 40 | UtilsFs.Create(credentialsFile) 41 | afero.WriteFile(UtilsFs, credentialsFile, test.fileData, 0755) 42 | output := GetAllAWSProfiles(test.AWSConfirm) 43 | if !compareSlice(output, test.expectedOutput) { 44 | t.Errorf("Test Failed: %v inputted, %v expected, received: %v", test.fileData, test.expectedOutput, output) 45 | } 46 | } 47 | 48 | } 49 | 50 | // Case empty file. 51 | // Case special characters \r \n \t. 52 | // Case with extra new lines at the end. 53 | // Case expected format. 54 | func TestGetSelectedAWSProfiles(t *testing.T) { 55 | var tests = []struct { 56 | fileData []byte 57 | expectedOutput []string 58 | AWSConfirm bool 59 | caseName string 60 | }{ 61 | {[]byte(""), []string{}, true, "empty file"}, 62 | {[]byte("abcd\r\nxyz\t\n123\r\t"), []string{"abcd", "xyz", "123"}, true, "special characters \\r \\n \\t"}, 63 | {[]byte("qwerty\nxyz\n456\n\n\n"), []string{"qwerty", "xyz", "456"}, true, "extra new lines at the end"}, 64 | {[]byte("nmhj\nyuioy\n098"), []string{"nmhj", "yuioy", "098"}, true, "expected format"}, 65 | } 66 | 67 | for _, test := range tests { 68 | UtilsFs = afero.NewMemMapFs() 69 | fmt.Printf("[*] Testing %s\n", test.caseName) 70 | UtilsFs.Create("/tmp/myfile.txt") 71 | afero.WriteFile(UtilsFs, "/tmp/myfile.txt", test.fileData, 0755) 72 | output := GetSelectedAWSProfiles("/tmp/myfile.txt") 73 | if !compareSlice(output, test.expectedOutput) { 74 | t.Errorf("Test Failed: %v inputted, %v expected, received: %v", test.fileData, test.expectedOutput, output) 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /internal/azure_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "testing" 7 | 8 | "github.com/BishopFox/cloudfox/globals" 9 | ) 10 | 11 | // Requires Az CLI Authentication to pass 12 | func TestGetAuthorizer(t *testing.T) { 13 | t.Skip() 14 | subtests := []struct { 15 | name string 16 | endpoint string 17 | }{ 18 | { 19 | name: "Resource Manager Authorizer", 20 | endpoint: globals.AZ_RESOURCE_MANAGER_ENDPOINT, 21 | }, 22 | { 23 | name: "Graph API Authorizer", 24 | endpoint: globals.AZ_GRAPH_ENDPOINT, 25 | }, 26 | } 27 | for _, subtest := range subtests { 28 | t.Run(subtest.name, func(t *testing.T) { 29 | log.Printf("Test case: %s", subtest.name) 30 | authorizer, err := getAuthorizer(subtest.endpoint) 31 | if err != nil { 32 | log.Print(err) 33 | } else { 34 | log.Print(authorizer) 35 | } 36 | fmt.Println() 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /internal/common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | type PermissionsRow struct { 4 | AWSService string 5 | Type string 6 | Name string 7 | Arn string 8 | PolicyType string 9 | PolicyName string 10 | PolicyArn string 11 | Effect string 12 | Action string 13 | Resource string 14 | Condition string 15 | } 16 | 17 | var PermissionRowsFromAllProfiles []PermissionsRow 18 | -------------------------------------------------------------------------------- /internal/log.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "os/user" 8 | "path/filepath" 9 | 10 | "github.com/BishopFox/cloudfox/globals" 11 | "github.com/aws/smithy-go/ptr" 12 | "github.com/fatih/color" 13 | "github.com/jedib0t/go-pretty/text" 14 | "github.com/kyokomi/emoji" 15 | "github.com/sirupsen/logrus" 16 | ) 17 | 18 | func init() { 19 | text.EnableColors() 20 | } 21 | 22 | // This function returns ~/.cloudfox. 23 | // If the folder does not exist the function creates it. 24 | func GetLogDirPath() *string { 25 | user, _ := user.Current() 26 | dir := filepath.Join(user.HomeDir, globals.CLOUDFOX_LOG_FILE_DIR_NAME) 27 | if _, err := os.Stat(dir); os.IsNotExist(err) { 28 | err = os.MkdirAll(dir, 0700) 29 | if err != nil { 30 | log.Printf("[-] Failed to read or create cloudfox directory") 31 | dir, err = os.Getwd() 32 | return ptr.String(dir) 33 | } 34 | } 35 | return ptr.String(dir) 36 | } 37 | 38 | type Logger struct { 39 | version string 40 | txtLog *logrus.Logger 41 | } 42 | 43 | func NewLogger() Logger { 44 | var logger = Logger{ 45 | version: globals.CLOUDFOX_VERSION, 46 | txtLog: TxtLog, 47 | } 48 | return logger 49 | } 50 | 51 | func (l *Logger) Info(text string) { 52 | l.InfoM(text, "config") 53 | } 54 | 55 | func (l *Logger) InfoM(text string, module string) { 56 | var cyan = color.New(color.FgCyan).SprintFunc() 57 | fmt.Printf("[%s][%s] %s\n", cyan(emoji.Sprintf(":fox:cloudfox %s :fox:", l.version)), cyan(module), text) 58 | } 59 | 60 | func (l *Logger) Success(text string) { 61 | l.SuccessM(text, "config") 62 | } 63 | func (l *Logger) SuccessM(text string, module string) { 64 | var green = color.New(color.FgGreen).SprintFunc() 65 | fmt.Printf("[%s][%s] %s\n", green(emoji.Sprintf(":fox:cloudfox %s :fox:", l.version)), green(module), text) 66 | } 67 | 68 | func (l *Logger) Error(text string) { 69 | l.ErrorM(text, "config") 70 | } 71 | 72 | func (l *Logger) ErrorM(text string, module string) { 73 | var red = color.New(color.FgRed).SprintFunc() 74 | fmt.Printf("[%s][%s] %s\n", red(emoji.Sprintf(":fox:cloudfox %s :fox:", l.version)), red(module), text) 75 | l.txtLog.Printf("[%s] %s", module, text) 76 | } 77 | 78 | func (l *Logger) Fatal(text string) { 79 | l.FatalM(text, "config") 80 | } 81 | 82 | func (l *Logger) FatalM(text string, module string) { 83 | var red = color.New(color.FgRed).SprintFunc() 84 | l.txtLog.Printf("[%s] %s", module, text) 85 | fmt.Printf("[%s][%s] %s\n", red(emoji.Sprintf(":fox:cloudfox %s :fox:", l.version)), red(module), text) 86 | os.Exit(1) 87 | } 88 | -------------------------------------------------------------------------------- /internal/log_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestLog(t *testing.T) { 8 | /* 9 | Rewrite better test cases for this later 10 | logFile := InitLogging() 11 | defer logFile.Close() 12 | 13 | log.Print("[+] Uncolored log message") 14 | log.Print(text.FgGreen.Sprint("[+] Green log message")) 15 | log.Print(text.FgYellow.Sprint("[+] Yellow log message")) 16 | log.Print(text.FgRed.Sprint("[+] Red log message")) 17 | */ 18 | } 19 | -------------------------------------------------------------------------------- /internal/output2_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "testing" 7 | 8 | "github.com/BishopFox/cloudfox/globals" 9 | ) 10 | 11 | func TestOutput2(t *testing.T) { 12 | 13 | tables := []TableFile{ 14 | { 15 | Name: "TableFile1", 16 | Header: []string{"Service", "Status"}, 17 | Body: [][]string{ 18 | {"IAM", "Active"}, 19 | {"EC2", "Not Active"}, 20 | {"Lambda", "Active"}, 21 | {"S3", "Not Active"}, 22 | }, 23 | }, 24 | { 25 | Name: "TableFile2", 26 | Header: []string{"Resource", "Condition"}, 27 | Body: [][]string{ 28 | {"ACR", "Good"}, 29 | {"AKS", "Bad"}, 30 | {"Storage Account", "Bad"}, 31 | {"Databricks", "Good"}, 32 | }, 33 | }, 34 | } 35 | 36 | lootFiles := []LootFile{ 37 | { 38 | Name: "loot1", 39 | Contents: "This is a loot file\nline1\nline2\n", 40 | }, 41 | { 42 | Name: "loot2", 43 | Contents: "This is a loot file\nline1\nline2\nline3\nline4\n", 44 | }, 45 | { 46 | Name: "loot3", 47 | Contents: "This is a loot file\nline1\nline2\nline3\n", 48 | }, 49 | } 50 | 51 | o := OutputClient{ 52 | Verbosity: 3, 53 | CallingModule: "testModule", 54 | PrefixIdentifier: "DEV", 55 | Table: TableClient{ 56 | Wrap: false, 57 | DirectoryName: filepath.Join(globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE), 58 | }, 59 | Loot: LootClient{ 60 | DirectoryName: filepath.Join(globals.CLOUDFOX_BASE_DIRECTORY, globals.AZ_DIR_BASE, globals.LOOT_DIRECTORY_NAME), 61 | LootFiles: nil, 62 | }, 63 | } 64 | 65 | MockFileSystem(false) 66 | fmt.Printf("Verbose level: %d\n", o.Verbosity) 67 | o.WriteFullOutput(tables, lootFiles) 68 | } 69 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/BishopFox/cloudfox/cli" 7 | "github.com/BishopFox/cloudfox/globals" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var ( 12 | rootCmd = &cobra.Command{ 13 | Use: os.Args[0], 14 | Version: globals.CLOUDFOX_VERSION, 15 | } 16 | ) 17 | 18 | func main() { 19 | rootCmd.AddCommand(cli.AWSCommands, cli.AzCommands, cli.GCPCommands) 20 | rootCmd.Execute() 21 | } 22 | --------------------------------------------------------------------------------