diff --git a/providers/aws/resources/aws.lr b/providers/aws/resources/aws.lr index 73c063d806..adfd9c6ab3 100644 --- a/providers/aws/resources/aws.lr +++ b/providers/aws/resources/aws.lr @@ -35,6 +35,8 @@ aws.organization @defaults("arn masterAccountEmail") { masterAccountId string // Email owner of the organization's master account masterAccountEmail string + // List of accounts that belong to the organization, if available to the caller + accounts() []aws.account } // Amazon Virtual Private Cloud (VPC) diff --git a/providers/aws/resources/aws.lr.go b/providers/aws/resources/aws.lr.go index 19ae540621..8456161833 100644 --- a/providers/aws/resources/aws.lr.go +++ b/providers/aws/resources/aws.lr.go @@ -23,7 +23,7 @@ func init() { Create: createAws, }, "aws.account": { - // to override args, implement: initAwsAccount(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) + Init: initAwsAccount, Create: createAwsAccount, }, "aws.organization": { @@ -793,6 +793,9 @@ var getDataFields = map[string]func(r plugin.Resource) *plugin.DataRes{ "aws.organization.masterAccountEmail": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlAwsOrganization).GetMasterAccountEmail()).ToDataRes(types.String) }, + "aws.organization.accounts": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlAwsOrganization).GetAccounts()).ToDataRes(types.Array(types.Resource("aws.account"))) + }, "aws.vpc.arn": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlAwsVpc).GetArn()).ToDataRes(types.String) }, @@ -4150,6 +4153,10 @@ var setDataFields = map[string]func(r plugin.Resource, v *llx.RawData) bool { r.(*mqlAwsOrganization).MasterAccountEmail, ok = plugin.RawToTValue[string](v.Value, v.Error) return }, + "aws.organization.accounts": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlAwsOrganization).Accounts, ok = plugin.RawToTValue[[]interface{}](v.Value, v.Error) + return + }, "aws.vpc.__id": func(r plugin.Resource, v *llx.RawData) (ok bool) { r.(*mqlAwsVpc).__id, ok = v.Value.(string) return @@ -9383,6 +9390,7 @@ type mqlAwsOrganization struct { FeatureSet plugin.TValue[string] MasterAccountId plugin.TValue[string] MasterAccountEmail plugin.TValue[string] + Accounts plugin.TValue[[]interface{}] } // createAwsOrganization creates a new instance of this resource @@ -9433,6 +9441,22 @@ func (c *mqlAwsOrganization) GetMasterAccountEmail() *plugin.TValue[string] { return &c.MasterAccountEmail } +func (c *mqlAwsOrganization) GetAccounts() *plugin.TValue[[]interface{}] { + return plugin.GetOrCompute[[]interface{}](&c.Accounts, func() ([]interface{}, error) { + if c.MqlRuntime.HasRecording { + d, err := c.MqlRuntime.FieldResourceFromRecording("aws.organization", c.__id, "accounts") + if err != nil { + return nil, err + } + if d != nil { + return d.Value.([]interface{}), nil + } + } + + return c.accounts() + }) +} + // mqlAwsVpc for the aws.vpc resource type mqlAwsVpc struct { MqlRuntime *plugin.Runtime diff --git a/providers/aws/resources/aws.lr.manifest.yaml b/providers/aws/resources/aws.lr.manifest.yaml index 8f2b85c72a..6e4a245d7c 100755 --- a/providers/aws/resources/aws.lr.manifest.yaml +++ b/providers/aws/resources/aws.lr.manifest.yaml @@ -2090,6 +2090,8 @@ resources: - aws aws.organization: fields: + accounts: + min_mondoo_version: 9.0.0 arn: {} featureSet: {} masterAccountEmail: {} diff --git a/providers/aws/resources/aws_account.go b/providers/aws/resources/aws_account.go index 6ac91a6229..1207f5b42b 100644 --- a/providers/aws/resources/aws_account.go +++ b/providers/aws/resources/aws_account.go @@ -6,18 +6,17 @@ package resources import ( "context" "errors" + "strings" "github.com/aws/aws-sdk-go-v2/service/iam" "github.com/aws/aws-sdk-go-v2/service/organizations" "go.mondoo.com/cnquery/v11/llx" + "go.mondoo.com/cnquery/v11/providers-sdk/v1/plugin" "go.mondoo.com/cnquery/v11/providers/aws/connection" ) func (a *mqlAwsAccount) id() (string, error) { - if conn, ok := a.MqlRuntime.Connection.(*connection.AwsConnection); ok { - return "aws.account/" + conn.AccountId(), nil - } - return "", errors.New("wrong connection for aws account id call") + return "aws.account/" + a.Id.Data, nil } func (a *mqlAwsAccount) aliases() ([]interface{}, error) { @@ -52,3 +51,50 @@ func (a *mqlAwsAccount) organization() (*mqlAwsOrganization, error) { }) return res.(*mqlAwsOrganization), err } + +func (a *mqlAwsOrganization) accounts() ([]interface{}, error) { + conn := a.MqlRuntime.Connection.(*connection.AwsConnection) + client := conn.Organizations("") // no region for orgs, use configured region + + orgAccounts, err := client.ListAccounts(context.TODO(), &organizations.ListAccountsInput{}) + if err != nil { + return nil, err + } + accounts := []interface{}{} + for i := range orgAccounts.Accounts { + account := orgAccounts.Accounts[i] + res, err := CreateResource(a.MqlRuntime, "aws.account", + map[string]*llx.RawData{ + "id": llx.StringDataPtr(account.Id), + }) + if err != nil { + return nil, err + } + accounts = append(accounts, res.(*mqlAwsAccount)) + } + return accounts, nil +} + +func initAwsAccount(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) { + if len(args) >= 2 { + return args, nil, nil + } + if len(args) == 0 { + if ids := getAssetIdentifier(runtime); ids != nil { + id := strings.TrimPrefix(ids.arn, "arn:aws:sts::") + args["id"] = llx.StringData(id) + } + } + if args["id"] == nil { + return args, nil, errors.New("no account id specified") + } + id := args["id"].Value.(string) + res, err := CreateResource(runtime, "aws.account", + map[string]*llx.RawData{ + "id": llx.StringData(id), + }) + if err != nil { + return nil, nil, err + } + return args, res, nil +} diff --git a/providers/aws/resources/discovery.go b/providers/aws/resources/discovery.go index 283e2c61ca..3707d7f838 100644 --- a/providers/aws/resources/discovery.go +++ b/providers/aws/resources/discovery.go @@ -27,6 +27,7 @@ const ( // API scan DiscoveryAccounts = "accounts" + DiscoveryOrg = "organization" DiscoveryResources = "resources" // all the resources DiscoveryECSContainersAPI = "ecs-containers-api" // need dedup story DiscoveryECRImageAPI = "ecr-image-api" // need policy + dedup story @@ -247,9 +248,24 @@ func discover(runtime *plugin.Runtime, awsAccount *mqlAwsAccount, target string, accountId := trimAwsAccountIdToJustId(awsAccount.Id.Data) assetList := []*inventory.Asset{} switch target { + case DiscoveryOrg: + res, err := NewResource(runtime, "aws.organization", map[string]*llx.RawData{}) + if err != nil { + return nil, err + } + org := res.(*mqlAwsOrganization) + + accounts := org.GetAccounts() + if accounts == nil { + return assetList, nil + } + + for i := range accounts.Data { + awsAccount := accounts.Data[i].(*mqlAwsAccount) + assetList = append(assetList, accountAsset(conn, awsAccount)) + } case DiscoveryAccounts: assetList = append(assetList, accountAsset(conn, awsAccount)) - case DiscoveryInstances: res, err := NewResource(runtime, "aws.ec2", map[string]*llx.RawData{}) if err != nil { diff --git a/providers/aws/resources/discovery_conversion.go b/providers/aws/resources/discovery_conversion.go index cc4cde7eea..6a1c89a963 100644 --- a/providers/aws/resources/discovery_conversion.go +++ b/providers/aws/resources/discovery_conversion.go @@ -202,9 +202,9 @@ func accountAsset(conn *connection.AwsConnection, awsAccount *mqlAwsAccount) *in name := AssembleIntegrationName(alias, accountId) id := "//platformid.api.mondoo.app/runtime/aws/accounts/" + accountId - + accountArn := "arn:aws:sts::" + accountId return &inventory.Asset{ - PlatformIds: []string{id}, + PlatformIds: []string{id, accountArn}, Name: name, Platform: connection.GetPlatformForObject("", accountId), Connections: []*inventory.Config{conn.Conf.Clone(inventory.WithoutDiscovery(), inventory.WithParentConnectionId(conn.Conf.Id))},