Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions internal/inventory/awsfetcher/fetcher_ec2_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ func (e *ec2InstanceFetcher) buildDetails(i *ec2.Ec2Instance, tags map[string]st
if v := awslib.LookupTag(tags, "costcenter", "cost-center", "cost_center"); v != "" {
details["CostCenter"] = v
}
if v := awslib.LookupTag(tags, "role"); v != "" {
details["Role"] = v
}
return details
}

Expand Down
7 changes: 5 additions & 2 deletions internal/inventory/awsfetcher/fetcher_ec2_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func makeTestInstance(launchTime *time.Time) *ec2beat.Ec2Instance {
{Key: pointers.Ref("key"), Value: pointers.Ref("value")},
{Key: pointers.Ref("Owner"), Value: pointers.Ref("team-infra")},
{Key: pointers.Ref("CostCenter"), Value: pointers.Ref("cc-1234")},
{Key: pointers.Ref("Role"), Value: pointers.Ref("sre")},
},
InstanceId: pointers.Ref("234567890"),
Architecture: ec2types.ArchitectureValuesX8664,
Expand Down Expand Up @@ -104,7 +105,7 @@ func TestEC2InstanceFetcher_Fetch(t *testing.T) {
"private-dns",
inventory.WithRelatedAssetIds([]string{"234567890"}),
inventory.WithRawAsset(instance1),
inventory.WithLabels(map[string]string{"Name": "test-server", "key": "value", "Owner": "team-infra", "CostCenter": "cc-1234"}),
inventory.WithLabels(map[string]string{"Name": "test-server", "key": "value", "Owner": "team-infra", "CostCenter": "cc-1234", "Role": "sre"}),
inventory.WithCloud(inventory.Cloud{
Provider: inventory.AwsCloudProvider,
Region: "us-east",
Expand Down Expand Up @@ -134,6 +135,7 @@ func TestEC2InstanceFetcher_Fetch(t *testing.T) {
"RoleArn": testRoleArn,
"Owner": "team-infra",
"CostCenter": "cc-1234",
"Role": "sre",
}),
inventory.WithCreatedAt(&launchTime),
inventory.WithUser(inventory.User{
Expand Down Expand Up @@ -193,7 +195,7 @@ func TestEC2InstanceFetcher_Fetch_ResolverError(t *testing.T) {
"private-dns",
inventory.WithRelatedAssetIds([]string{"234567890"}),
inventory.WithRawAsset(instance),
inventory.WithLabels(map[string]string{"Name": "test-server", "key": "value", "Owner": "team-infra", "CostCenter": "cc-1234"}),
inventory.WithLabels(map[string]string{"Name": "test-server", "key": "value", "Owner": "team-infra", "CostCenter": "cc-1234", "Role": "sre"}),
inventory.WithCloud(inventory.Cloud{
Provider: inventory.AwsCloudProvider,
Region: "us-east",
Expand Down Expand Up @@ -223,6 +225,7 @@ func TestEC2InstanceFetcher_Fetch_ResolverError(t *testing.T) {
// RoleArn is absent because the resolver failed.
"Owner": "team-infra",
"CostCenter": "cc-1234",
"Role": "sre",
}),
inventory.WithCreatedAt(&launchTime),
// WithUser falls back to the profile ARN when role resolution fails.
Expand Down
12 changes: 9 additions & 3 deletions internal/inventory/awsfetcher/fetcher_elb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ import (
)

func TestELBv1Fetcher_Fetch(t *testing.T) {
asset := elb.ElasticLoadBalancerInfo{
LoadBalancer: types.LoadBalancerDescription{
asset := elb.NewElasticLoadBalancerInfo(
types.LoadBalancerDescription{
AvailabilityZones: []string{"us-east-1a"},
CanonicalHostedZoneName: pointers.Ref("HZ-NAME"),
CanonicalHostedZoneNameID: pointers.Ref("HZ-ID"),
Expand All @@ -64,7 +64,11 @@ func TestELBv1Fetcher_Fetch(t *testing.T) {
Subnets: []string{"subnet-123"},
VPCId: pointers.Ref("vpc-id"),
},
}
"", // awsAccount
"", // region
nil,
[]string{"203.0.113.1", "203.0.113.2"}, // DNS-resolved by the provider in real code
)
in := []awslib.AwsResource{asset}

expected := []inventory.AssetEvent{
Expand All @@ -84,6 +88,8 @@ func TestELBv1Fetcher_Fetch(t *testing.T) {
"PubliclyAccessible": false, // scheme is "internal"
"AccountID": "123",
"LoadBalancerType": "classic",
"State": "active",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it makes sense to add IPAddresses as well? Because we don't run the DNS resolver (like we do in the actual code to resolve the IPs).
Reference:
https://github.com/elastic/cloudbeat/pull/7277/changes#diff-45e6ce26ce27502d2d082db4502e0999a52d546b187833699c645fbfa297c146R88-R96

"IPAddresses": []string{"203.0.113.1", "203.0.113.2"},
}),
inventory.WithCreatedAt(asset.GetCreatedAt()),
),
Expand Down
3 changes: 3 additions & 0 deletions internal/inventory/awsfetcher/fetcher_rds.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ func (s *rdsFetcher) Fetch(ctx context.Context, assetChannel chan<- inventory.As
if item.EngineVersion != "" {
details["EngineVersion"] = item.EngineVersion
}
if item.Status != "" {
details["DBInstanceStatus"] = item.Status
}

assetChannel <- inventory.NewAssetEvent(
inventory.AssetClassificationAwsRds,
Expand Down
4 changes: 4 additions & 0 deletions internal/inventory/awsfetcher/fetcher_rds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func TestRDSInstanceFetcher_Fetch(t *testing.T) {
PubliclyAccessible: false,
Engine: "postgres",
EngineVersion: "15.4",
Status: "available",
CreatedAt: &createdAt,
Subnets: []rds.Subnet{
{
Expand All @@ -68,6 +69,7 @@ func TestRDSInstanceFetcher_Fetch(t *testing.T) {
PubliclyAccessible: true,
Engine: "mysql",
EngineVersion: "8.0.35",
Status: "available",
Subnets: []rds.Subnet{
{
ID: "subnet-aabbccdd",
Expand Down Expand Up @@ -107,6 +109,7 @@ func TestRDSInstanceFetcher_Fetch(t *testing.T) {
"PubliclyAccessible": false,
"Engine": "postgres",
"EngineVersion": "15.4",
"DBInstanceStatus": "available",
}),
inventory.WithCreatedAt(&createdAt),
),
Expand All @@ -126,6 +129,7 @@ func TestRDSInstanceFetcher_Fetch(t *testing.T) {
"PubliclyAccessible": true,
"Engine": "mysql",
"EngineVersion": "8.0.35",
"DBInstanceStatus": "available",
}),
),
}
Expand Down
9 changes: 9 additions & 0 deletions internal/resources/providers/awslib/elb/elb.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package elb

import (
"context"
"net"

"github.com/aws/aws-sdk-go-v2/aws"
elb "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing"
Expand All @@ -28,6 +29,12 @@ import (
"github.com/elastic/cloudbeat/internal/resources/providers/awslib"
)

// hostResolver abstracts DNS resolution so that tests can inject a fake without hitting the
// real network. *net.Resolver satisfies this interface.
type hostResolver interface {
LookupHost(ctx context.Context, host string) ([]string, error)
}

type Client interface {
elb.DescribeLoadBalancersAPIClient
DescribeTags(ctx context.Context, params *elb.DescribeTagsInput, optFns ...func(*elb.Options)) (*elb.DescribeTagsOutput, error)
Expand All @@ -43,6 +50,7 @@ type Provider struct {
client Client
clients map[string]Client
awsAccountID string
resolver hostResolver
}

func NewElbProvider(ctx context.Context, log *clog.Logger, awsAccountID string, cfg aws.Config, factory awslib.CrossRegionFactory[Client]) *Provider {
Expand All @@ -55,5 +63,6 @@ func NewElbProvider(ctx context.Context, log *clog.Logger, awsAccountID string,
client: elb.NewFromConfig(cfg),
clients: m.GetMultiRegionsClientMap(),
awsAccountID: awsAccountID,
resolver: net.DefaultResolver,
}
}
95 changes: 95 additions & 0 deletions internal/resources/providers/awslib/elb/elb_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 20 additions & 4 deletions internal/resources/providers/awslib/elb/load_balancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,19 @@ type ElasticLoadBalancerInfo struct {
awsAccount string
region string
tags map[string]string
ipAddresses []string
}

// NewElasticLoadBalancerInfo constructs a classic load balancer wrapper. ipAddresses are the
// addresses resolved from the load balancer's DNS name (classic ELBs expose no IPs via the API).
func NewElasticLoadBalancerInfo(lb types.LoadBalancerDescription, awsAccount, region string, tags map[string]string, ipAddresses []string) *ElasticLoadBalancerInfo {
return &ElasticLoadBalancerInfo{
LoadBalancer: lb,
awsAccount: awsAccount,
region: region,
tags: tags,
ipAddresses: ipAddresses,
}
}

func (v ElasticLoadBalancerInfo) GetResourceArn() string {
Expand Down Expand Up @@ -73,14 +86,17 @@ func (v ElasticLoadBalancerInfo) GetLoadBalancerType() string {
return "classic"
}

// GetState is not exposed for classic load balancers by the AWS SDK.
// GetState returns "active" for classic load balancers. The classic ELB API does not expose
// a state field, but any LB returned by DescribeLoadBalancers is live.
func (v ElasticLoadBalancerInfo) GetState() string {
return ""
return "active"
}

// GetIPAddresses is not exposed for classic load balancers (they are DNS-only).
// GetIPAddresses returns the IP addresses resolved from the load balancer's DNS name.
// Classic ELBs do not expose IPs directly via the AWS API; the addresses are resolved at
// fetch time (see Provider.DescribeAllLoadBalancers) and stored here.
func (v ElasticLoadBalancerInfo) GetIPAddresses() []string {
return nil
return v.ipAddresses
}

// GetOwnerTag returns the value of the "Owner" tag (case-insensitive), if present.
Expand Down
23 changes: 17 additions & 6 deletions internal/resources/providers/awslib/elb/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package elb
import (
"context"
"fmt"
"sort"

elb "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing"
"github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing/types"
Expand Down Expand Up @@ -78,12 +79,22 @@ func (p *Provider) DescribeAllLoadBalancers(ctx context.Context) ([]awslib.AwsRe

var result []awslib.AwsResource
for _, item := range all {
result = append(result, &ElasticLoadBalancerInfo{
LoadBalancer: item,
awsAccount: p.awsAccountID,
region: region,
tags: tagsByName[pointers.Deref(item.LoadBalancerName)],
})
var ipAddresses []string
if dnsName := pointers.Deref(item.DNSName); dnsName != "" {
if ips, err := p.resolver.LookupHost(ctx, dnsName); err != nil {
p.log.Debugf("Could not resolve IPs for classic ELB %q: %v", dnsName, err)
} else {
sort.Strings(ips)
ipAddresses = ips
}
}
result = append(result, NewElasticLoadBalancerInfo(
item,
p.awsAccountID,
region,
tagsByName[pointers.Deref(item.LoadBalancerName)],
ipAddresses,
))
}
return result, nil
})
Expand Down
Loading
Loading