go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/_motor/providers/awsec2ebs/provider.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package awsec2ebs
     5  
     6  import (
     7  	"context"
     8  	"math/rand"
     9  	"time"
    10  
    11  	"github.com/aws/aws-sdk-go-v2/aws"
    12  	"github.com/aws/aws-sdk-go-v2/config"
    13  	"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
    14  	"github.com/aws/aws-sdk-go-v2/service/ec2"
    15  	"github.com/cockroachdb/errors"
    16  	"github.com/rs/zerolog/log"
    17  	"github.com/spf13/afero"
    18  	"go.mondoo.com/cnquery/motor/providers"
    19  	"go.mondoo.com/cnquery/motor/providers/fs"
    20  	"go.mondoo.com/cnquery/motor/providers/os"
    21  	"go.mondoo.com/cnquery/motor/providers/os/snapshot"
    22  )
    23  
    24  var (
    25  	_ providers.Instance           = (*Provider)(nil)
    26  	_ providers.PlatformIdentifier = (*Provider)(nil)
    27  	_ os.OperatingSystemProvider   = (*Provider)(nil)
    28  )
    29  
    30  // New creates a new aws-ec2-ebs provider
    31  // It expects to be running on an ec2 instance with ssm iam role and
    32  // permissions for copy snapshot, create snapshot, create volume, attach volume, detach volume
    33  // TODO: validate the expected permissions here
    34  func New(pCfg *providers.Config) (*Provider, error) {
    35  	rand.Seed(time.Now().UnixNano())
    36  
    37  	// 1. validate; load
    38  	// TODO allow custom aws config
    39  	cfg, err := config.LoadDefaultConfig(context.Background())
    40  	if err != nil {
    41  		return nil, errors.Wrap(err, "could not load aws configuration")
    42  	}
    43  	i, err := RawInstanceInfo(cfg)
    44  	if err != nil {
    45  		return nil, errors.Wrap(err, "could not load instance info: aws-ec2-ebs provider only valid on ec2 instances")
    46  	}
    47  
    48  	// ec2 client for the scanner region
    49  	cfg.Region = i.Region
    50  	scannerSvc := ec2.NewFromConfig(cfg)
    51  
    52  	// ec2 client for the target region
    53  	cfgCopy := cfg.Copy()
    54  	cfgCopy.Region = pCfg.Options["region"]
    55  	targetSvc := ec2.NewFromConfig(cfgCopy)
    56  
    57  	shell := []string{"sh", "-c"}
    58  	volumeMounter := snapshot.NewVolumeMounter(shell)
    59  
    60  	// 2. create provider instance
    61  	p := &Provider{
    62  		config: cfg,
    63  		opts:   pCfg.Options,
    64  		target: TargetInfo{
    65  			PlatformId: pCfg.PlatformId,
    66  			AccountId:  pCfg.Options["account"],
    67  			Region:     pCfg.Options["region"],
    68  			Id:         pCfg.Options["id"],
    69  		},
    70  		targetType: pCfg.Options["type"],
    71  		scannerInstance: &InstanceId{
    72  			Id:      i.InstanceID,
    73  			Region:  i.Region,
    74  			Account: i.AccountID,
    75  			Zone:    i.AvailabilityZone,
    76  		},
    77  		targetRegionEc2svc:  targetSvc,
    78  		scannerRegionEc2svc: scannerSvc,
    79  		volumeMounter:       volumeMounter,
    80  	}
    81  	log.Debug().Interface("info", p.target).Str("type", p.targetType).Msg("target")
    82  
    83  	ctx := context.Background()
    84  
    85  	// 3. validate
    86  	instanceinfo, volumeid, snapshotid, err := p.Validate(ctx)
    87  	if err != nil {
    88  		return p, errors.Wrap(err, "unable to validate")
    89  	}
    90  
    91  	// 4. setup the volume for scanning
    92  	// check if we got the no setup override option. this implies the target volume is already attached to the instance
    93  	// this is used in cases where we need to test a snapshot created from a public marketplace image. the volume gets attached to a brand
    94  	// new instance, and then that instance is started and we scan the attached fs
    95  	if pCfg.Options[snapshot.NoSetup] == "true" {
    96  		log.Info().Msg("skipping setup step")
    97  	} else {
    98  		var ok bool
    99  		var err error
   100  		switch p.targetType {
   101  		case EBSTargetInstance:
   102  			ok, err = p.SetupForTargetInstance(ctx, instanceinfo)
   103  		case EBSTargetVolume:
   104  			ok, err = p.SetupForTargetVolume(ctx, *volumeid)
   105  		case EBSTargetSnapshot:
   106  			ok, err = p.SetupForTargetSnapshot(ctx, *snapshotid)
   107  		default:
   108  			return p, errors.New("invalid target type")
   109  		}
   110  		if err != nil {
   111  			log.Error().Err(err).Msg("unable to complete setup step")
   112  			p.Close()
   113  			return p, err
   114  		}
   115  		if !ok {
   116  			return p, errors.New("something went wrong; unable to complete setup for ebs volume scan")
   117  		}
   118  	}
   119  
   120  	// Mount Volume
   121  	err = p.volumeMounter.Mount()
   122  	if err != nil {
   123  		log.Error().Err(err).Msg("unable to complete mount step")
   124  		p.Close()
   125  		return p, err
   126  	}
   127  
   128  	// Create and initialize fs provider
   129  	fsProvider, err := fs.NewWithClose(&providers.Config{
   130  		Path:       p.volumeMounter.ScanDir,
   131  		Backend:    providers.ProviderType_FS,
   132  		PlatformId: pCfg.PlatformId,
   133  		Options:    pCfg.Options,
   134  	}, p.Close)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  	p.FsProvider = fsProvider
   139  	return p, nil
   140  }
   141  
   142  type Provider struct {
   143  	FsProvider          *fs.Provider
   144  	scannerRegionEc2svc *ec2.Client
   145  	targetRegionEc2svc  *ec2.Client
   146  	config              aws.Config
   147  	opts                map[string]string
   148  	scannerInstance     *InstanceId // the instance the transport is running on
   149  	scanVolumeInfo      *VolumeInfo // the info of the volume we attached to the instance
   150  	target              TargetInfo  // info about the target
   151  	targetType          string      // the type of object we're targeting (instance, volume, snapshot)
   152  	volumeMounter       *snapshot.VolumeMounter
   153  }
   154  
   155  type TargetInfo struct {
   156  	PlatformId string
   157  	AccountId  string
   158  	Region     string
   159  	Id         string
   160  }
   161  
   162  func (p *Provider) RunCommand(command string) (*os.Command, error) {
   163  	return nil, errors.New("RunCommand not implemented")
   164  }
   165  
   166  func (p *Provider) FileInfo(path string) (os.FileInfoDetails, error) {
   167  	return os.FileInfoDetails{}, errors.New("FileInfo not implemented")
   168  }
   169  
   170  func (p *Provider) FS() afero.Fs {
   171  	return p.FsProvider.FS()
   172  }
   173  
   174  func (p *Provider) Close() {
   175  	if p.opts != nil {
   176  		if p.opts[snapshot.NoSetup] == "true" {
   177  			return
   178  		}
   179  	}
   180  	ctx := context.Background()
   181  	err := p.volumeMounter.UnmountVolumeFromInstance()
   182  	if err != nil {
   183  		log.Error().Err(err).Msg("unable to unmount volume")
   184  	}
   185  	err = p.DetachVolumeFromInstance(ctx, p.scanVolumeInfo)
   186  	if err != nil {
   187  		log.Error().Err(err).Msg("unable to detach volume")
   188  	}
   189  	// only delete the volume if we created it, e.g., if we're scanning a snapshot
   190  	if val, ok := p.scanVolumeInfo.Tags["createdBy"]; ok {
   191  		if val == "Mondoo" {
   192  			err = p.DeleteCreatedVolume(ctx, p.scanVolumeInfo)
   193  			if err != nil {
   194  				log.Error().Err(err).Msg("unable to delete volume")
   195  			}
   196  			log.Info().Str("vol-id", p.scanVolumeInfo.Id).Msg("deleted temporary volume created by Mondoo")
   197  		}
   198  	} else {
   199  		log.Debug().Str("vol-id", p.scanVolumeInfo.Id).Msg("skipping volume deletion, not created by Mondoo")
   200  	}
   201  	err = p.volumeMounter.RemoveTempScanDir()
   202  	if err != nil {
   203  		log.Error().Err(err).Msg("unable to remove dir")
   204  	}
   205  }
   206  
   207  func (p *Provider) Capabilities() providers.Capabilities {
   208  	return providers.Capabilities{
   209  		providers.Capability_Aws_Ebs,
   210  	}
   211  }
   212  
   213  func (p *Provider) Kind() providers.Kind {
   214  	return providers.Kind_KIND_API
   215  }
   216  
   217  func (p *Provider) Runtime() string {
   218  	return providers.RUNTIME_AWS_EC2_EBS
   219  }
   220  
   221  func (p *Provider) PlatformIdDetectors() []providers.PlatformIdDetector {
   222  	return []providers.PlatformIdDetector{
   223  		providers.TransportPlatformIdentifierDetector,
   224  	}
   225  }
   226  
   227  func RawInstanceInfo(cfg aws.Config) (*imds.InstanceIdentityDocument, error) {
   228  	metadata := imds.NewFromConfig(cfg)
   229  	ctx := context.Background()
   230  	doc, err := metadata.GetInstanceIdentityDocument(ctx, &imds.GetInstanceIdentityDocumentInput{})
   231  	if err != nil {
   232  		return nil, err
   233  	}
   234  	return &doc.InstanceIdentityDocument, nil
   235  }
   236  
   237  func (p *Provider) Identifier() (string, error) {
   238  	return p.target.PlatformId, nil
   239  }
   240  
   241  func GetRawInstanceInfo(profile string) (*imds.InstanceIdentityDocument, error) {
   242  	ctx := context.Background()
   243  	var cfg aws.Config
   244  	var err error
   245  	if profile == "" {
   246  		cfg, err = config.LoadDefaultConfig(ctx)
   247  	} else {
   248  		cfg, err = config.LoadDefaultConfig(ctx, config.WithSharedConfigProfile(profile))
   249  	}
   250  	if err != nil {
   251  		return nil, errors.Wrap(err, "could not load aws configuration")
   252  	}
   253  	i, err := RawInstanceInfo(cfg)
   254  	if err != nil {
   255  		return nil, errors.Wrap(err, "could not load instance info: aws-ec2-ebs provider is only valid on ec2 instances")
   256  	}
   257  	return i, nil
   258  }