diff --git a/providers/aws/resources/aws.lr b/providers/aws/resources/aws.lr index 221b1ae6af..a16ab84bdf 100644 --- a/providers/aws/resources/aws.lr +++ b/providers/aws/resources/aws.lr @@ -1660,6 +1660,8 @@ private aws.ec2.instance @defaults("arn state") { privateDnsName string // Keypair associated with the instance keypair() aws.ec2.keypair + // Time when the last state transition occurred + stateTransitionTime time } // Amazon EC2 Key Pair diff --git a/providers/aws/resources/aws.lr.go b/providers/aws/resources/aws.lr.go index 830272a640..dba7cf286f 100644 --- a/providers/aws/resources/aws.lr.go +++ b/providers/aws/resources/aws.lr.go @@ -2376,6 +2376,9 @@ var getDataFields = map[string]func(r plugin.Resource) *plugin.DataRes{ "aws.ec2.instance.keypair": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlAwsEc2Instance).GetKeypair()).ToDataRes(types.Resource("aws.ec2.keypair")) }, + "aws.ec2.instance.stateTransitionTime": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlAwsEc2Instance).GetStateTransitionTime()).ToDataRes(types.Time) + }, "aws.ec2.keypair.arn": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlAwsEc2Keypair).GetArn()).ToDataRes(types.String) }, @@ -5418,6 +5421,10 @@ var setDataFields = map[string]func(r plugin.Resource, v *llx.RawData) bool { r.(*mqlAwsEc2Instance).Keypair, ok = plugin.RawToTValue[*mqlAwsEc2Keypair](v.Value, v.Error) return }, + "aws.ec2.instance.stateTransitionTime": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlAwsEc2Instance).StateTransitionTime, ok = plugin.RawToTValue[*time.Time](v.Value, v.Error) + return + }, "aws.ec2.keypair.__id": func(r plugin.Resource, v *llx.RawData) (ok bool) { r.(*mqlAwsEc2Keypair).__id, ok = v.Value.(string) return @@ -14599,6 +14606,7 @@ type mqlAwsEc2Instance struct { PrivateIp plugin.TValue[string] PrivateDnsName plugin.TValue[string] Keypair plugin.TValue[*mqlAwsEc2Keypair] + StateTransitionTime plugin.TValue[*time.Time] } // createAwsEc2Instance creates a new instance of this resource @@ -14768,6 +14776,10 @@ func (c *mqlAwsEc2Instance) GetKeypair() *plugin.TValue[*mqlAwsEc2Keypair] { }) } +func (c *mqlAwsEc2Instance) GetStateTransitionTime() *plugin.TValue[*time.Time] { + return &c.StateTransitionTime +} + // mqlAwsEc2Keypair for the aws.ec2.keypair resource type mqlAwsEc2Keypair struct { MqlRuntime *plugin.Runtime diff --git a/providers/aws/resources/aws.lr.manifest.yaml b/providers/aws/resources/aws.lr.manifest.yaml index 61c581fc30..4743fa5de0 100755 --- a/providers/aws/resources/aws.lr.manifest.yaml +++ b/providers/aws/resources/aws.lr.manifest.yaml @@ -860,6 +860,8 @@ resources: state: {} stateReason: {} stateTransitionReason: {} + stateTransitionTime: + min_mondoo_version: 9.0.0 tags: {} vpc: {} is_private: true diff --git a/providers/aws/resources/aws_ec2.go b/providers/aws/resources/aws_ec2.go index 50c20b3b9b..4e4e744e9b 100644 --- a/providers/aws/resources/aws_ec2.go +++ b/providers/aws/resources/aws_ec2.go @@ -6,8 +6,10 @@ package resources import ( "context" "fmt" + "regexp" "strconv" "strings" + "time" "github.com/aws/aws-sdk-go-v2/service/ec2" ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" @@ -694,7 +696,16 @@ func (a *mqlAwsEc2) gatherInstanceInfo(instances []ec2types.Reservation, imdsvVe if err != nil { return nil, err } - + var stateTransitionTime time.Time + reg := regexp.MustCompile(`.*\((\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}) GMT\)`) + timeString := reg.FindStringSubmatch(convert.ToString(instance.StateTransitionReason)) + if len(timeString) == 2 { + stateTransitionTime, err = time.Parse(time.DateTime, timeString[1]) + if err != nil { + log.Error().Err(err).Msg("cannot parse state transition time for ec2 instance") + stateTransitionTime = llx.NeverPastTime + } + } args := map[string]*llx.RawData{ "platformDetails": llx.StringData(convert.ToString(instance.PlatformDetails)), "arn": llx.StringData(fmt.Sprintf(ec2InstanceArnPattern, regionVal, conn.AccountId(), convert.ToString(instance.InstanceId))), @@ -715,6 +726,7 @@ func (a *mqlAwsEc2) gatherInstanceInfo(instances []ec2types.Reservation, imdsvVe "launchTime": llx.TimeData(toTime(instance.LaunchTime)), "privateIp": llx.StringData(convert.ToString(instance.PrivateIpAddress)), "privateDnsName": llx.StringData(convert.ToString(instance.PrivateDnsName)), + "stateTransitionTime": llx.TimeData(stateTransitionTime), } if instance.ImageId != nil {