Skip to content

Commit

Permalink
Convert bucket policy to an indexable format
Browse files Browse the repository at this point in the history
  • Loading branch information
romulets committed Mar 21, 2024
1 parent c84c31d commit ec9cd70
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 30 deletions.
56 changes: 38 additions & 18 deletions internal/inventory/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,20 @@ const (

// AssetEvent holds the whole asset
type AssetEvent struct {
Asset Asset
Network *AssetNetwork
Cloud *AssetCloud
Host *AssetHost
IAM *AssetIAM
Asset Asset
Network *AssetNetwork
Cloud *AssetCloud
Host *AssetHost
IAM *AssetIAM
ResourcePolicies []AssetResourcePolicy
}

// AssetClassification holds the taxonomy of an asset
type AssetClassification struct {
Category assetCategory `json:"category"`
SubCategory assetSubCategory `json:"subCategory"`
SubCategory assetSubCategory `json:"sub_category"`
Type assetType `json:"type"`
SubStype assetSubType `json:"subStype"`
SubType assetSubType `json:"sub_type"`
}

// Asset contains the identifiers of the asset
Expand All @@ -87,13 +88,13 @@ type Asset struct {

// AssetNetwork contains network information
type AssetNetwork struct {
NetworkId *string `json:"networkId"`
SubnetId *string `json:"subnetId"`
Ipv6Address *string `json:"ipv6Address"`
PublicIpAddress *string `json:"publicIpAddress"`
PrivateIpAddress *string `json:"privateIpAddress"`
PublicDnsName *string `json:"publicDnsName"`
PrivateDnsName *string `json:"privateDnsName"`
NetworkId *string `json:"network_id"`
SubnetId *string `json:"subnet_id"`
Ipv6Address *string `json:"ipv6_address"`
PublicIpAddress *string `json:"public_ip_address"`
PrivateIpAddress *string `json:"private_ip_address"`
PublicDnsName *string `json:"public_dns_name"`
PrivateDnsName *string `json:"private_dns_name"`
}

// AssetCloud contains information about the cloud provider
Expand All @@ -119,7 +120,7 @@ type AssetCloudInstance struct {
}

type AssetCloudMachine struct {
MachineType string `json:"machineType,omitempty"`
MachineType string `json:"machine_type,omitempty"`
}

type AssetCloudProject struct {
Expand All @@ -135,16 +136,29 @@ type AssetCloudService struct {
type AssetHost struct {
Architecture string `json:"architecture"`
ImageId *string `json:"imageId"`
InstanceType string `json:"instanceType"`
InstanceType string `json:"instance_type"`
Platform string `json:"platform"`
PlatformDetails *string `json:"platformDetails"`
PlatformDetails *string `json:"platform_details"`
}

type AssetIAM struct {
Id *string `json:"id"`
Arn *string `json:"arn"`
}

// AssetResourcePolicy maps security policies applied directly on resources
type AssetResourcePolicy struct {
Version *string `json:"version,omitempty"`
Id *string `json:"id,omitempty"`
Effect string `json:"effect,omitempty"`
Principal map[string]any `json:"principal,omitempty"`
Action []string `json:"action,omitempty"`
NotAction []string `json:"notAction,omitempty"`
Resource []string `json:"resource,omitempty"`
NoResource []string `json:"noResource,omitempty"`
Condition map[string]any `json:"condition,omitempty"`
}

// AssetEnricher functional builder function
type AssetEnricher func(asset *AssetEvent)

Expand Down Expand Up @@ -201,13 +215,19 @@ func WithIAM(iam AssetIAM) AssetEnricher {
}
}

func WithResourcePolicies(policies ...AssetResourcePolicy) AssetEnricher {
return func(a *AssetEvent) {
a.ResourcePolicies = policies
}
}

func EmptyEnricher() AssetEnricher {
return func(_ *AssetEvent) {}
}

func generateUniqueId(c AssetClassification, resourceId string) string {
hasher := sha256.New()
toBeHashed := fmt.Sprintf("%s-%s-%s-%s-%s", resourceId, c.Category, c.SubCategory, c.Type, c.SubStype)
toBeHashed := fmt.Sprintf("%s-%s-%s-%s-%s", resourceId, c.Category, c.SubCategory, c.Type, c.SubType)
hasher.Write([]byte(toBeHashed)) //nolint:revive
hash := hasher.Sum(nil)
encoded := base64.StdEncoding.EncodeToString(hash)
Expand Down
2 changes: 1 addition & 1 deletion internal/inventory/aws/fetcher_ec2_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ var ec2InstanceClassification = inventory.AssetClassification{
Category: inventory.CategoryInfrastructure,
SubCategory: inventory.SubCategoryCompute,
Type: inventory.TypeVirtualMachine,
SubStype: inventory.SubTypeEC2,
SubType: inventory.SubTypeEC2,
}

func newEc2Fetcher(logger *logp.Logger, identity *cloud.Identity, cfg aws.Config) inventory.AssetFetcher {
Expand Down
96 changes: 95 additions & 1 deletion internal/inventory/aws/fetcher_s3_bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/elastic/cloudbeat/internal/inventory"
"github.com/elastic/cloudbeat/internal/resources/providers/awslib"
"github.com/elastic/cloudbeat/internal/resources/providers/awslib/s3"
"github.com/elastic/cloudbeat/internal/resources/utils/pointers"
)

type S3BucketFetcher struct {
Expand All @@ -41,7 +42,7 @@ var s3BucketClassification = inventory.AssetClassification{
Category: inventory.CategoryInfrastructure,
SubCategory: inventory.SubCategoryStorage,
Type: inventory.TypeObjectStorage,
SubStype: inventory.SubTypeS3,
SubType: inventory.SubTypeS3,
}

type s3BucketProvider interface {
Expand Down Expand Up @@ -89,6 +90,99 @@ func (s S3BucketFetcher) Fetch(ctx context.Context, assetChannel chan<- inventor
Name: "AWS S3",
},
}),
inventory.WithResourcePolicies(getBucketPolicies(bucket)...),
)
}
}

func getBucketPolicies(bucket s3.BucketDescription) []inventory.AssetResourcePolicy {
if len(bucket.BucketPolicy) == 0 {
return nil
}

version, hasVersion := bucket.BucketPolicy["Version"].(string)
if !hasVersion {
version = ""
}

switch statements := bucket.BucketPolicy["Statement"].(type) {
case []map[string]any:
return convertStatements(statements, version)
case []any:
return convertAnyStatements(statements, version)
case map[string]any:
return []inventory.AssetResourcePolicy{convertStatement(statements, &version)}
}
return nil
}

func convertAnyStatements(statements []any, version string) []inventory.AssetResourcePolicy {
policies := make([]inventory.AssetResourcePolicy, 0, len(statements))
for _, statement := range statements {
policies = append(policies, convertStatement(statement.(map[string]any), &version))
}
return policies
}

func convertStatements(statements []map[string]any, version string) []inventory.AssetResourcePolicy {
policies := make([]inventory.AssetResourcePolicy, 0, len(statements))
for _, statement := range statements {
policies = append(policies, convertStatement(statement, &version))
}
return policies
}

func convertStatement(statement map[string]any, version *string) inventory.AssetResourcePolicy {
p := inventory.AssetResourcePolicy{}
p.Version = version

if sid, ok := statement["Sid"]; ok {
p.Id = pointers.Ref(sid.(string))
}

if effect, ok := statement["Effect"]; ok {
p.Effect = effect.(string)
}

if anyPrincipal, ok := statement["Principal"]; ok {
switch principal := anyPrincipal.(type) {
case string:
p.Principal = map[string]any{principal: principal}
case map[string]any:
p.Principal = principal
}
}

if action, ok := statement["Action"]; ok {
p.Action = anyToSliceString(action)
}

if notAction, ok := statement["NotAction"]; ok {
p.NotAction = anyToSliceString(notAction)
}

if resource, ok := statement["Resource"]; ok {
p.Resource = anyToSliceString(resource)
}

if noResource, ok := statement["NoResource"]; ok {
p.NoResource = anyToSliceString(noResource)
}

if condition, ok := statement["Condition"]; ok {
p.Condition = condition.(map[string]any)
}

return p
}

func anyToSliceString(anyString any) []string {
switch s := anyString.(type) {
case string:
return []string{s}
case []string:
return s
}

return nil
}
62 changes: 60 additions & 2 deletions internal/inventory/aws/fetcher_s3_bucket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,30 @@ func TestS3BucketFetcher_Fetch(t *testing.T) {
bucket1 := s3.BucketDescription{
Name: "bucket-1",
SSEAlgorithm: nil,
BucketPolicy: nil,
BucketPolicy: map[string]any{
"Version": "2012-10-17",
"Statement": []map[string]any{
{
"Sid": "Test 1",
"Effect": "Allow",
"Principal": map[string]any{
"AWS": "dima",
"service": "aws.com",
},
"Action": []string{"read", "update", "delete"},
"Resource": []string{"s3/bucket", "s3/bucket/*"},
},
{
"Sid": "Test 2",
"Effect": "Deny",
"Principal": map[string]any{
"AWS": "romulo",
},
"Action": []string{"delete"},
"Resource": []string{"s3/bucket"},
},
},
},
BucketVersioning: &s3.BucketVersioning{
Enabled: true,
MfaDelete: true,
Expand All @@ -55,7 +78,16 @@ func TestS3BucketFetcher_Fetch(t *testing.T) {
bucket2 := s3.BucketDescription{
Name: "bucket-2",
SSEAlgorithm: nil,
BucketPolicy: nil,
BucketPolicy: map[string]any{
"Version": "2012-10-17",
"Statement": map[string]any{
"Sid": "Test 1",
"Effect": "Allow",
"Principal": "*",
"Action": "read",
"Resource": "s3/bucket",
},
},
BucketVersioning: &s3.BucketVersioning{
Enabled: false,
MfaDelete: false,
Expand Down Expand Up @@ -87,6 +119,24 @@ func TestS3BucketFetcher_Fetch(t *testing.T) {
Name: "AWS S3",
},
}),
inventory.WithResourcePolicies(inventory.AssetResourcePolicy{
Version: pointers.Ref("2012-10-17"),
Id: pointers.Ref("Test 1"),
Effect: "Allow",
Principal: map[string]any{
"AWS": "dima",
"service": "aws.com",
},
Action: []string{"read", "update", "delete"},
Resource: []string{"s3/bucket", "s3/bucket/*"},
}, inventory.AssetResourcePolicy{
Version: pointers.Ref("2012-10-17"),
Id: pointers.Ref("Test 2"),
Effect: "Deny",
Principal: map[string]any{"AWS": "romulo"},
Action: []string{"delete"},
Resource: []string{"s3/bucket"},
}),
),
inventory.NewAssetEvent(
s3BucketClassification,
Expand All @@ -104,6 +154,14 @@ func TestS3BucketFetcher_Fetch(t *testing.T) {
Name: "AWS S3",
},
}),
inventory.WithResourcePolicies(inventory.AssetResourcePolicy{
Version: pointers.Ref("2012-10-17"),
Id: pointers.Ref("Test 1"),
Effect: "Allow",
Principal: map[string]any{"*": "*"},
Action: []string{"read"},
Resource: []string{"s3/bucket"},
}),
),
}

Expand Down
13 changes: 7 additions & 6 deletions internal/inventory/inventory.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,12 @@ func (a *AssetInventory) publish(assets []AssetEvent) {
Meta: mapstr.M{libevents.FieldMetaIndex: generateIndex(e.Asset)},
Timestamp: a.now(),
Fields: mapstr.M{
"asset": e.Asset,
"cloud": e.Cloud,
"host": e.Host,
"network": e.Network,
"iam": e.IAM,
"asset": e.Asset,
"cloud": e.Cloud,
"host": e.Host,
"network": e.Network,
"iam": e.IAM,
"resource_policies": e.ResourcePolicies,
},
}
})
Expand All @@ -118,7 +119,7 @@ func (a *AssetInventory) publish(assets []AssetEvent) {
}

func generateIndex(a Asset) string {
return fmt.Sprintf("asset_inventory_%s_%s_%s_%s", a.Category, a.SubCategory, a.Type, a.SubStype)
return fmt.Sprintf("asset_inventory_%s_%s_%s_%s", a.Category, a.SubCategory, a.Type, a.SubType)
}

func (a *AssetInventory) Stop() {
Expand Down
Loading

0 comments on commit ec9cd70

Please sign in to comment.