go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/id/awsec2/metadata_cmd.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package awsec2 5 6 import ( 7 "context" 8 "encoding/json" 9 "fmt" 10 "io" 11 "strings" 12 13 "github.com/aws/aws-sdk-go-v2/aws" 14 "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" 15 "github.com/aws/aws-sdk-go-v2/service/ec2" 16 ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" 17 "github.com/cockroachdb/errors" 18 "go.mondoo.com/cnquery/providers-sdk/v1/inventory" 19 "go.mondoo.com/cnquery/providers/os/connection/shared" 20 "go.mondoo.com/cnquery/providers/os/resources/powershell" 21 ) 22 23 const ( 24 identityUrl = `-H "X-aws-ec2-metadata-token: %s" -v http://169.254.169.254/latest/dynamic/instance-identity/document` 25 tokenUrl = `-H "X-aws-ec2-metadata-token-ttl-seconds: 21600" -X PUT "http://169.254.169.254/latest/api/token"` 26 tagNameUrl = `-H "X-aws-ec2-metadata-token: %s" -v http://169.254.169.254/latest/meta-data/tags/instance/Name` 27 28 identityUrlWindows = ` 29 $Headers = @{ 30 "X-aws-ec2-metadata-token" = %s 31 } 32 Invoke-RestMethod -TimeoutSec 1 -Headers $Headers -URI http://169.254.169.254/latest/dynamic/instance-identity/document -UseBasicParsing | ConvertTo-Json 33 ` 34 35 tokenUrlWindows = ` 36 $Headers = @{ 37 "X-aws-ec2-metadata-token-ttl-seconds" = "21600" 38 } 39 Invoke-RestMethod -Method Put -Uri "http://169.254.169.254/latest/api/token" -Headers $Headers -TimeoutSec 1 -UseBasicParsing 40 ` 41 tagNameUrlWindows = ` 42 $Headers = @{ 43 "X-aws-ec2-metadata-token" = %s 44 } 45 Invoke-RestMethod -Method Put -Uri "http://169.254.169.254/latest/meta-data/tags/instance/Name" -Headers $Headers -TimeoutSec 1 -UseBasicParsing 46 ` 47 ) 48 49 func NewCommandInstanceMetadata(conn shared.Connection, pf *inventory.Platform, config *aws.Config) *CommandInstanceMetadata { 50 return &CommandInstanceMetadata{ 51 conn: conn, 52 platform: pf, 53 config: config, 54 } 55 } 56 57 type CommandInstanceMetadata struct { 58 conn shared.Connection 59 platform *inventory.Platform 60 config *aws.Config 61 } 62 63 func (m *CommandInstanceMetadata) Identify() (Identity, error) { 64 instanceDocument, err := m.instanceIdentityDocument() 65 if err != nil { 66 return Identity{}, err 67 } 68 // parse into struct 69 doc := imds.InstanceIdentityDocument{} 70 if err := json.NewDecoder(strings.NewReader(instanceDocument)).Decode(&doc); err != nil { 71 return Identity{}, errors.Wrap(err, "failed to decode EC2 instance identity document") 72 } 73 74 name := "" 75 // Note that the tags metadata service has to be enabled for this to work. If not, we fallback to trying to get the name 76 // via the aws API (if there's a config provided). 77 taggedName, err := m.instanceNameTag() 78 if err == nil { 79 name = taggedName 80 } else if m.config != nil { 81 ec2svc := ec2.NewFromConfig(*m.config) 82 ctx := context.Background() 83 filters := []ec2types.Filter{ 84 { 85 Name: aws.String("resource-id"), 86 Values: []string{doc.InstanceID}, 87 }, 88 } 89 tags, err := ec2svc.DescribeTags(ctx, &ec2.DescribeTagsInput{Filters: filters}) 90 if err == nil { 91 for _, t := range tags.Tags { 92 if t.Key != nil && *t.Key == "Name" && t.Value != nil { 93 name = *t.Value 94 } 95 } 96 } 97 } 98 return Identity{ 99 InstanceName: name, 100 InstanceID: MondooInstanceID(doc.AccountID, doc.Region, doc.InstanceID), 101 AccountID: "//platformid.api.mondoo.app/runtime/aws/accounts/" + doc.AccountID, 102 }, nil 103 } 104 105 type metadataType int 106 107 const ( 108 document metadataType = iota 109 instanceNameTag 110 ) 111 112 func (m *CommandInstanceMetadata) curlDocument(metadataType metadataType) (string, error) { 113 switch { 114 case m.platform.IsFamily(inventory.FAMILY_UNIX): 115 cmd, err := m.conn.RunCommand("curl " + tokenUrl) 116 if err != nil { 117 return "", err 118 } 119 data, err := io.ReadAll(cmd.Stdout) 120 if err != nil { 121 return "", err 122 } 123 tokenString := strings.TrimSpace(string(data)) 124 125 commandScript := "" 126 switch metadataType { 127 case document: 128 commandScript = "curl " + fmt.Sprintf(identityUrl, tokenString) 129 case instanceNameTag: 130 commandScript = "curl " + fmt.Sprintf(tagNameUrl, tokenString) 131 } 132 133 cmd, err = m.conn.RunCommand(commandScript) 134 if err != nil { 135 return "", err 136 } 137 data, err = io.ReadAll(cmd.Stdout) 138 if err != nil { 139 return "", err 140 } 141 142 return strings.TrimSpace(string(data)), nil 143 case m.platform.IsFamily(inventory.FAMILY_WINDOWS): 144 tokenPwshEncoded := powershell.Encode(tokenUrlWindows) 145 cmd, err := m.conn.RunCommand(tokenPwshEncoded) 146 if err != nil { 147 return "", err 148 } 149 data, err := io.ReadAll(cmd.Stdout) 150 if err != nil { 151 return "", err 152 } 153 tokenString := strings.TrimSpace(string(data)) 154 155 commandScript := "" 156 switch metadataType { 157 case document: 158 commandScript = powershell.Encode(fmt.Sprintf(identityUrlWindows, tokenString)) 159 case instanceNameTag: 160 commandScript = powershell.Encode(fmt.Sprintf(tagNameUrlWindows, tokenString)) 161 } 162 163 cmd, err = m.conn.RunCommand(commandScript) 164 if err != nil { 165 return "", err 166 } 167 data, err = io.ReadAll(cmd.Stdout) 168 if err != nil { 169 return "", err 170 } 171 172 return strings.TrimSpace(string(data)), nil 173 default: 174 return "", errors.New("your platform is not supported by aws metadata identifier resource") 175 } 176 } 177 178 func (m *CommandInstanceMetadata) instanceNameTag() (string, error) { 179 res, err := m.curlDocument(instanceNameTag) 180 if err != nil { 181 return "", err 182 } 183 if strings.Contains(res, "Not Found") { 184 return "", errors.New("metadata tags not enabled") 185 } 186 return res, nil 187 } 188 189 func (m *CommandInstanceMetadata) instanceIdentityDocument() (string, error) { 190 return m.curlDocument(document) 191 }