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  }