diff --git a/aws/org.go b/aws/org.go new file mode 100644 index 0000000..ebf5704 --- /dev/null +++ b/aws/org.go @@ -0,0 +1,179 @@ +package aws + +import ( + "context" + "fmt" + "path/filepath" + "strconv" + + "github.com/BishopFox/cloudfox/internal" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/organizations" + "github.com/aws/aws-sdk-go-v2/service/organizations/types" + "github.com/aws/aws-sdk-go-v2/service/sts" + "github.com/sirupsen/logrus" +) + +type OrgModule struct { + OrganizationsClient *organizations.Client + Caller sts.GetCallerIdentityOutput + AWSRegions []string + OutputFormat string + Goroutines int + AWSProfile string + SkipAdminCheck bool + WrapTable bool + + // Main module data + Accounts []Account + CommandCounter internal.CommandCounter + // Used to store output data for pretty printing + output internal.OutputData2 + modLog *logrus.Entry +} + +type OrganizationsClientInterface interface { + ListAccounts(ctx context.Context, params *organizations.ListAccountsInput, optFns ...func(*organizations.Options)) (*organizations.ListAccountsOutput, error) +} + +type Account struct { + isManagementAccount bool + Name string + Id string + Email string + Arn string + Status string +} + +func (m *OrgModule) PrintOrgAccounts(outputFormat string, outputDirectory string, verbosity int) { + // These stuct values are used by the output module + m.output.Verbosity = verbosity + m.output.Directory = outputDirectory + m.output.CallingModule = "org" + + m.modLog = internal.TxtLog.WithFields(logrus.Fields{ + "module": m.output.CallingModule, + }) + if m.AWSProfile == "" { + m.AWSProfile = internal.BuildAWSPath(m.Caller) + } + + fmt.Printf("[%s][%s] Checking if account %s is the management account in an organization %s.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), aws.ToString(m.Caller.Account)) + + // wg := new(sync.WaitGroup) + // semaphore := make(chan struct{}, m.Goroutines) + + // // Create a channel to signal the spinner aka task status goroutine to finish + // spinnerDone := make(chan bool) + // //fire up the the task status spinner/updated + // go internal.SpinUntil(m.output.CallingModule, &m.CommandCounter, spinnerDone, "tasks") + + // //create a channel to receive the objects + // dataReceiver := make(chan Project) + + // // Create a channel to signal to stop + // receiverDone := make(chan bool) + + // go m.Receiver(dataReceiver, receiverDone) + + // // for _, region := range m.AWSRegions { + // // wg.Add(1) + // // m.CommandCounter.Pending++ + // // go m.executeChecks(region, wg, semaphore, dataReceiver) + + // // } + + // wg.Wait() + + // // Send a message to the spinner goroutine to close the channel and stop + // spinnerDone <- true + // <-spinnerDone + // receiverDone <- true + // <-receiverDone + + m.addOrgAccounts() + + m.output.Headers = []string{ + "Name", + "ID", + "isManagementAccount?", + "Status", + "Email", + } + + // Table rows + + for i := range m.Accounts { + m.output.Body = append( + m.output.Body, + []string{ + m.Accounts[i].Name, + m.Accounts[i].Id, + strconv.FormatBool(m.Accounts[i].isManagementAccount), + m.Accounts[i].Status, + m.Accounts[i].Email, + }, + ) + } + + if len(m.output.Body) > 0 { + m.output.FilePath = filepath.Join(outputDirectory, "cloudfox-output", "aws", m.AWSProfile) + internal.OutputSelector(verbosity, outputFormat, m.output.Headers, m.output.Body, m.output.FilePath, m.output.CallingModule, m.output.CallingModule, m.WrapTable, m.AWSProfile) + //m.writeLoot(m.output.FilePath, verbosity) + fmt.Printf("[%s][%s] %d accounts found.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), len(m.output.Body)) + fmt.Printf("[%s][%s] For context and next steps: https://github.com/BishopFox/cloudfox/wiki/AWS-Commands#%s\n", cyan(m.output.CallingModule), cyan(m.AWSProfile), m.output.CallingModule) + + } else { + fmt.Printf("[%s][%s] No accounts found, skipping the creation of an output file.\n", cyan(m.output.CallingModule), cyan(m.AWSProfile)) + } + +} + +func (m *OrgModule) addOrgAccounts() { + accounts, err := m.listAccounts() + if err != nil { + m.modLog.Errorf("Failed to list accounts: %s", err) + return + } + for _, account := range accounts { + m.Accounts = append(m.Accounts, Account{ + isManagementAccount: false, + Name: aws.ToString(account.Name), + Id: aws.ToString(account.Id), + Email: aws.ToString(account.Email), + Arn: aws.ToString(account.Arn), + Status: string(account.Status), + }) + } +} + +func (m *OrgModule) listAccounts() ([]types.Account, error) { + // "PaginationMarker" is a control variable used for output continuity, as AWS return the output in pages. + var PaginationControl *string + var accounts []types.Account + + for { + ListAccounts, err := m.OrganizationsClient.ListAccounts( + context.TODO(), + &organizations.ListAccountsInput{ + NextToken: PaginationControl, + }, + ) + if err != nil { + m.modLog.Error(err.Error()) + m.CommandCounter.Error++ + break + } + + accounts = append(accounts, ListAccounts.Accounts...) + + if ListAccounts.NextToken != nil { + PaginationControl = ListAccounts.NextToken + } else { + PaginationControl = nil + break + } + + } + return accounts, nil +} diff --git a/cli/aws.go b/cli/aws.go index 76a1ff7..481d6e4 100644 --- a/cli/aws.go +++ b/cli/aws.go @@ -31,6 +31,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/lightsail" "github.com/aws/aws-sdk-go-v2/service/mq" "github.com/aws/aws-sdk-go-v2/service/opensearch" + "github.com/aws/aws-sdk-go-v2/service/organizations" "github.com/aws/aws-sdk-go-v2/service/ram" "github.com/aws/aws-sdk-go-v2/service/rds" "github.com/aws/aws-sdk-go-v2/service/redshift" @@ -272,6 +273,16 @@ var ( Run: runOutboundAssumedRolesCommand, } + OrgsCommand = &cobra.Command{ + Use: "orgs", + Aliases: []string{"org", "organizations", "accounts", "account"}, + Short: "Enumerate accounts in an organization", + Long: "\nUse case examples:\n" + + os.Args[0] + " aws orgs --profile readonly_profile", + PreRun: awsPreRun, + Run: runOrgsCommand, + } + PermissionsPrincipal string PermissionsCommand = &cobra.Command{ Use: "permissions", @@ -457,6 +468,7 @@ func init() { NetworkPortsCommand, PmapperCommand, ResourceTrustsCommand, + OrgsCommand, ) } @@ -864,6 +876,23 @@ func runOutboundAssumedRolesCommand(cmd *cobra.Command, args []string) { } } +func runOrgsCommand(cmd *cobra.Command, args []string) { + for _, profile := range AWSProfiles { + var AWSConfig = internal.AWSConfigFileLoader(profile, cmd.Root().Version) + caller, err := internal.AWSWhoami(profile, cmd.Root().Version) + if err != nil { + continue + } + m := aws.OrgModule{ + OrganizationsClient: organizations.NewFromConfig(AWSConfig), + Caller: *caller, + AWSProfile: profile, + WrapTable: AWSWrapTable, + } + m.PrintOrgAccounts(AWSOutputFormat, AWSOutputDirectory, Verbosity) + } +} + func runPermissionsCommand(cmd *cobra.Command, args []string) { for _, profile := range AWSProfiles { var AWSConfig = internal.AWSConfigFileLoader(profile, cmd.Root().Version) diff --git a/go.mod b/go.mod index 8bd44cb..5622ccb 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/Azure/go-autorest/autorest v0.11.28 github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 github.com/aquasecurity/table v1.8.0 - github.com/aws/aws-sdk-go-v2 v1.17.5 + github.com/aws/aws-sdk-go-v2 v1.17.7 github.com/aws/aws-sdk-go-v2/config v1.18.3 github.com/aws/aws-sdk-go-v2/service/apigateway v1.15.25 github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.12.23 @@ -75,8 +75,8 @@ require ( github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.9 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.13.3 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.19 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.3.26 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.16 // indirect github.com/aws/aws-sdk-go-v2/service/codebuild v1.20.5 // indirect @@ -85,6 +85,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.19 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.19 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.19 // indirect + github.com/aws/aws-sdk-go-v2/service/organizations v1.19.2 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.11.25 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.8 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect diff --git a/go.sum b/go.sum index 603b0fd..74ea667 100644 --- a/go.sum +++ b/go.sum @@ -88,6 +88,8 @@ github.com/aws/aws-sdk-go-v2 v1.17.3 h1:shN7NlnVzvDUgPQ+1rLMSxY8OWRNDRYtiqe0p/Pg github.com/aws/aws-sdk-go-v2 v1.17.3/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= github.com/aws/aws-sdk-go-v2 v1.17.5 h1:TzCUW1Nq4H8Xscph5M/skINUitxM5UBAyvm2s7XBzL4= github.com/aws/aws-sdk-go-v2 v1.17.5/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= +github.com/aws/aws-sdk-go-v2 v1.17.7 h1:CLSjnhJSTSogvqUGhIC6LqFKATMRexcxLZ0i/Nzk9Eg= +github.com/aws/aws-sdk-go-v2 v1.17.7/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.9 h1:RKci2D7tMwpvGpDNZnGQw9wk6v7o/xSwFcUAuNPoB8k= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.9/go.mod h1:vCmV1q1VK8eoQJ5+aYE7PkK1K6v41qJ5pJdK3ggCDvg= github.com/aws/aws-sdk-go-v2/config v1.18.3 h1:3kfBKcX3votFX84dm00U8RGA1sCCh3eRMOGzg5dCWfU= @@ -101,11 +103,15 @@ github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 h1:I3cakv2Uy1vNmmhRQ github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27/go.mod h1:a1/UpzeyBBerajpnP5nGZa9mGzsBn5cOKxm6NWQsvoI= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29 h1:9/aKwwus0TQxppPXFmf010DFrE+ssSbzroLVYINA+xE= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.29/go.mod h1:Dip3sIGv485+xerzVv24emnjX5Sg88utCL8fwGmCeWg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31 h1:sJLYcS+eZn5EeNINGHSCRAwUJMFVqklwkH36Vbyai7M= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31/go.mod h1:QT0BqUvX1Bh2ABdTGnjqEjvjzrCfIniM9Sc8zn9Yndo= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.19/go.mod h1:6Q0546uHDp421okhmmGfbxzq2hBqbXFNpi4k+Q1JnQA= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 h1:5NbbMrIzmUn/TXFqAle6mgrH5m9cOvMLRGL7pnG8tRE= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21/go.mod h1:+Gxn8jYn5k9ebfHEqlhrMirFjSW0v0C9fI+KN5vk2kE= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23 h1:b/Vn141DBuLVgXbhRWIrl9g+ww7G+ScV5SzniWR13jQ= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.23/go.mod h1:mr6c4cHC+S/MMkrjtSlG4QA36kOznDep+0fga5L/fGQ= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25 h1:1mnRASEKnkqsntcxHaysxwgVoUUp5dkiB+l3llKnqyg= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25/go.mod h1:zBHOPwhBc3FlQjQJE/D3IfPWiWaQmT06Vq9aNukDo0k= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.26 h1:Mza+vlnZr+fPKFKRq/lKGVvM6B/8ZZmNdEopOwSQLms= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.26/go.mod h1:Y2OJ+P+MC1u1VKnavT+PshiEuGPyh/7DqxoDNij4/bg= github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.16 h1:2EXB7dtGwRYIN3XQ9qwIW504DVbKIw3r89xQnonGdsQ= @@ -168,6 +174,8 @@ github.com/aws/aws-sdk-go-v2/service/mq v1.13.15 h1:K0UbxNfaPHoIXo0T4L4jrbE+TKcV github.com/aws/aws-sdk-go-v2/service/mq v1.13.15/go.mod h1:ycghPMXYItx5Y74iehFgGwDNUMXdq0xCxLaYC5uYZO0= github.com/aws/aws-sdk-go-v2/service/opensearch v1.11.3 h1:ViGcWux+UbkS/JBLxPILq/Mevlf9RXSycGlPaVQON5Y= github.com/aws/aws-sdk-go-v2/service/opensearch v1.11.3/go.mod h1:7mh4ks7MsQUuhHFCr10LIF9gKVS+0yylPUmCEM/kzJE= +github.com/aws/aws-sdk-go-v2/service/organizations v1.19.2 h1:8tikveMqrEqTIyMcevyeFNASIkffCuwuV+DAkMiXm8g= +github.com/aws/aws-sdk-go-v2/service/organizations v1.19.2/go.mod h1:MTKfY5PMwGkG9+tK451UCUpDm5jo1uyJwUkkvn7G1p4= github.com/aws/aws-sdk-go-v2/service/ram v1.16.24 h1:pzB33ZEhd1szVKHkgYe4cXQ+kSLJEFGBLy+IpbAKY9Y= github.com/aws/aws-sdk-go-v2/service/ram v1.16.24/go.mod h1:el3+/AiwdwKk5qZEcCQqm+t3b1A5QLZI2nr/OkhM/gc= github.com/aws/aws-sdk-go-v2/service/rds v1.31.0 h1:lx8pflhN7oogISvAorwqPGFHN7eiVlGEwk21ThYcyoA= diff --git a/misc/aws/cloudfox-policy.json b/misc/aws/cloudfox-policy.json index da61aa9..48b3aa8 100644 --- a/misc/aws/cloudfox-policy.json +++ b/misc/aws/cloudfox-policy.json @@ -11,6 +11,9 @@ "cloudformation:ListStacks", "cloudtrail:LookupEvents", "cloudfront:ListDistributions", + "codebuild:ListProjects", + "codebuild:BatchGetProjects", + "codebuild:GetResourcePolicy", "dynamodb:ListTables", "ec2:DescribeInstanceAttribute", "ec2:DescribeInstances", @@ -21,7 +24,7 @@ "ecs:DescribeTaskDefinition", "ecs:ListClusters", "ecs:ListTaskDefinitions", - "ecs:ListTasks", + "ecs:ListTasks", "eks:DescribeCluster", "eks:DescribeNodegroup", "eks:ListClusters", @@ -47,11 +50,13 @@ "iam:ListRoles", "iam:SimulatePrincipalPolicy", "lambda:GetFunctionUrlConfig", - "lambda:ListFunctions", + "lambda:ListFunctions", + "lambda:GetPolicy", "lightsail:GetContainerServices", "lightsail:GetInstances", "mq:DescribeBroker", "mq:ListBrokers", + "organizations:ListAccounts", "es:DescribeDomain", "es:ListDomainNames", "ram:GetResourceShares", @@ -63,6 +68,9 @@ "s3:GetBucketPolicyStatus", "s3:GetBucketWebsite", "s3:ListAllMyBuckets", + "s3:GetAccountPublicAccessBlock", + "s3:GetBucketLocation", + "s3:GetBucketPolicy", "ssm:DescribeParameters", "sagemaker:ListProcessingJobs", "sagemaker:DescribeProcessingJob", @@ -74,7 +82,9 @@ "sagemaker:DescribeModel", "secretsmanager:ListSecrets", "sns:ListTopics", + "sns:GetTopicAttributes", "sqs:ListQueues", + "sqs:GetQueueAttributes", "tag:GetResources" ],