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 }