From f1915b40ccfb7431f70c90292c999ff6cbb564b4 Mon Sep 17 00:00:00 2001 From: Preslav Gerchev Date: Mon, 8 Jan 2024 12:52:20 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20support=20for=20scanning=20az?= =?UTF-8?q?ure=20disks.=20(#2974)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Preslav --- .../azureinstancesnapshot/platform.go | 4 + .../azureinstancesnapshot/provider.go | 63 +++++--- .../azureinstancesnapshot/provider_test.go | 66 +++++++-- .../azureinstancesnapshot/snapshot.go | 136 ++++++++++-------- providers/azure/provider/provider.go | 10 +- 5 files changed, 187 insertions(+), 92 deletions(-) diff --git a/providers/azure/connection/azureinstancesnapshot/platform.go b/providers/azure/connection/azureinstancesnapshot/platform.go index 34c96e1906..7520489fd1 100644 --- a/providers/azure/connection/azureinstancesnapshot/platform.go +++ b/providers/azure/connection/azureinstancesnapshot/platform.go @@ -6,3 +6,7 @@ package azureinstancesnapshot func SnapshotPlatformMrn(snapshotId string) string { return "//platformid.api.mondoo.app/runtime/azure" + snapshotId } + +func DiskPlatformMrn(diskId string) string { + return "//platformid.api.mondoo.app/runtime/azure" + diskId +} diff --git a/providers/azure/connection/azureinstancesnapshot/provider.go b/providers/azure/connection/azureinstancesnapshot/provider.go index bce7ad20d7..ef3c6d5e02 100644 --- a/providers/azure/connection/azureinstancesnapshot/provider.go +++ b/providers/azure/connection/azureinstancesnapshot/provider.go @@ -24,14 +24,11 @@ import ( "go.mondoo.com/cnquery/v9/providers/os/id/ids" ) -type scanTarget struct { - TargetType string - Target string - ResourceGroup string -} - const ( SnapshotConnectionType shared.ConnectionType = "azure-snapshot" + DiskTargetType string = "disk" + SnapshotTargetType string = "snapshot" + InstanceTargetType string = "instance" ) // the instance from which we're performing the scan @@ -39,6 +36,12 @@ type azureScannerInstance struct { instanceInfo } +type scanTarget struct { + TargetType string + Target string + ResourceGroup string +} + type mountInfo struct { deviceName string diskId string @@ -99,7 +102,7 @@ func ParseTarget(conf *inventory.Config, scanner *azureScannerInstance) (scanTar return scanTarget{ TargetType: conf.Options["type"], Target: conf.Options["target"], - ResourceGroup: scanner.ResourceGroup, + ResourceGroup: scanner.resourceGroup, }, nil } return scanTarget{ @@ -132,7 +135,7 @@ func NewAzureSnapshotConnection(id uint32, conf *inventory.Config, asset *invent } // determine the target - sc, err := NewSnapshotCreator(token, scanner.SubscriptionId) + sc, err := NewSnapshotCreator(token, scanner.subscriptionId) if err != nil { return nil, err } @@ -147,19 +150,19 @@ func NewAzureSnapshotConnection(id uint32, conf *inventory.Config, asset *invent // setup disk image so and attach it to the instance mi := mountInfo{} - diskName := "cnspec-" + target.Target + "-snapshot-" + time.Now().Format("2006-01-02t15-04-05z00-00") + diskName := "cnspec-" + target.TargetType + "-snapshot-" + time.Now().Format("2006-01-02t15-04-05z00-00") switch target.TargetType { - case "instance": - instanceInfo, err := sc.InstanceInfo(target.ResourceGroup, target.Target) + case InstanceTargetType: + instanceInfo, err := sc.instanceInfo(target.ResourceGroup, target.Target) if err != nil { return nil, err } - if instanceInfo.BootDiskId == "" { + if instanceInfo.bootDiskId == "" { return nil, fmt.Errorf("could not find boot disk for instance %s", target.Target) } - log.Debug().Str("boot disk", instanceInfo.BootDiskId).Msg("found boot disk for instance, cloning") - disk, err := sc.cloneDisk(instanceInfo.BootDiskId, scanner.ResourceGroup, diskName, scanner.Location, scanner.Vm.Zones) + log.Debug().Str("boot disk", instanceInfo.bootDiskId).Msg("found boot disk for instance, cloning") + disk, err := sc.cloneDisk(instanceInfo.bootDiskId, scanner.resourceGroup, diskName, scanner.location, scanner.vm.Zones) if err != nil { log.Error().Err(err).Msg("could not complete disk cloning") return nil, errors.Wrap(err, "could not complete disk cloning") @@ -167,15 +170,15 @@ func NewAzureSnapshotConnection(id uint32, conf *inventory.Config, asset *invent log.Debug().Str("disk", *disk.ID).Msg("cloned disk from instance boot disk") mi.diskId = *disk.ID mi.diskName = *disk.Name - asset.Name = instanceInfo.InstanceName - conf.PlatformId = azcompute.MondooAzureInstanceID(*instanceInfo.Vm.ID) - case "snapshot": - snapshotInfo, err := sc.SnapshotInfo(target.ResourceGroup, target.Target) + asset.Name = instanceInfo.instanceName + conf.PlatformId = azcompute.MondooAzureInstanceID(*instanceInfo.vm.ID) + case SnapshotTargetType: + snapshotInfo, err := sc.snapshotInfo(target.ResourceGroup, target.Target) if err != nil { return nil, err } - disk, err := sc.createSnapshotDisk(snapshotInfo.SnapshotId, scanner.ResourceGroup, diskName, scanner.Location, scanner.Vm.Zones) + disk, err := sc.createSnapshotDisk(snapshotInfo.snapshotId, scanner.resourceGroup, diskName, scanner.location, scanner.vm.Zones) if err != nil { log.Error().Err(err).Msg("could not complete snapshot disk creation") return nil, errors.Wrap(err, "could not create disk from snapshot") @@ -184,7 +187,23 @@ func NewAzureSnapshotConnection(id uint32, conf *inventory.Config, asset *invent mi.diskId = *disk.ID mi.diskName = *disk.Name asset.Name = target.Target - conf.PlatformId = SnapshotPlatformMrn(snapshotInfo.SnapshotId) + conf.PlatformId = SnapshotPlatformMrn(snapshotInfo.snapshotId) + case DiskTargetType: + diskInfo, err := sc.diskInfo(target.ResourceGroup, target.Target) + if err != nil { + return nil, err + } + + disk, err := sc.cloneDisk(diskInfo.diskId, scanner.resourceGroup, diskName, scanner.location, scanner.vm.Zones) + if err != nil { + log.Error().Err(err).Msg("could not complete disk cloning") + return nil, errors.Wrap(err, "could not complete disk cloning") + } + log.Debug().Str("disk", *disk.ID).Msg("cloned disk from target disk") + mi.diskId = *disk.ID + mi.diskName = *disk.Name + asset.Name = diskInfo.diskName + conf.PlatformId = DiskPlatformMrn(diskInfo.diskId) default: return nil, errors.New("invalid target type") } @@ -264,7 +283,7 @@ type AzureSnapshotConnection struct { *fs.FileSystemConnection opts map[string]string volumeMounter *snapshot.VolumeMounter - snapshotCreator *SnapshotCreator + snapshotCreator *snapshotCreator scanner azureScannerInstance mountInfo mountInfo identifier string @@ -298,7 +317,7 @@ func (c *AzureSnapshotConnection) Close() { } if c.mountInfo.diskName != "" { - err := c.snapshotCreator.deleteCreatedDisk(c.scanner.ResourceGroup, c.mountInfo.diskName) + err := c.snapshotCreator.deleteCreatedDisk(c.scanner.resourceGroup, c.mountInfo.diskName) if err != nil { log.Error().Err(err).Msg("could not delete created disk") } diff --git a/providers/azure/connection/azureinstancesnapshot/provider_test.go b/providers/azure/connection/azureinstancesnapshot/provider_test.go index 9f739e3cf7..9a452f74f6 100644 --- a/providers/azure/connection/azureinstancesnapshot/provider_test.go +++ b/providers/azure/connection/azureinstancesnapshot/provider_test.go @@ -14,8 +14,8 @@ func TestParseTarget(t *testing.T) { t.Run("parse snapshot target with just a resource name", func(t *testing.T) { scanner := &azureScannerInstance{ instanceInfo: instanceInfo{ - ResourceGroup: "my-rg", - InstanceName: "my-instance", + resourceGroup: "my-rg", + instanceName: "my-instance", }, } target := "my-other-snapshot" @@ -30,13 +30,13 @@ func TestParseTarget(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "my-rg", scanTarget.ResourceGroup) assert.Equal(t, target, scanTarget.Target) - assert.Equal(t, "snapshot", scanTarget.TargetType) + assert.Equal(t, SnapshotTargetType, scanTarget.TargetType) }) t.Run("parse instance target with just a resource name", func(t *testing.T) { scanner := &azureScannerInstance{ instanceInfo: instanceInfo{ - ResourceGroup: "my-rg", - InstanceName: "my-instance", + resourceGroup: "my-rg", + instanceName: "my-instance", }, } target := "my-other-instance" @@ -51,13 +51,34 @@ func TestParseTarget(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "my-rg", scanTarget.ResourceGroup) assert.Equal(t, target, scanTarget.Target) - assert.Equal(t, "instance", scanTarget.TargetType) + assert.Equal(t, InstanceTargetType, scanTarget.TargetType) + }) + t.Run("parse disk target with just a resource name", func(t *testing.T) { + scanner := &azureScannerInstance{ + instanceInfo: instanceInfo{ + resourceGroup: "my-rg", + instanceName: "my-instance", + }, + } + target := "my-disk" + + conf := &inventory.Config{ + Options: map[string]string{ + "target": target, + "type": "disk", + }, + } + scanTarget, err := ParseTarget(conf, scanner) + assert.NoError(t, err) + assert.Equal(t, "my-rg", scanTarget.ResourceGroup) + assert.Equal(t, target, scanTarget.Target) + assert.Equal(t, DiskTargetType, scanTarget.TargetType) }) t.Run("parse snapshot target with a fully qualifed Azure resource id", func(t *testing.T) { scanner := &azureScannerInstance{ instanceInfo: instanceInfo{ - ResourceGroup: "my-rg", - InstanceName: "my-instance", + resourceGroup: "my-rg", + instanceName: "my-instance", }, } target := "/subscriptions/f1a2873a-6c27-4097-aa7c-3df51f103e91/resourceGroups/my-other-rg/providers/Microsoft.Compute/snapshots/test-snp" @@ -72,13 +93,13 @@ func TestParseTarget(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "my-other-rg", scanTarget.ResourceGroup) assert.Equal(t, "test-snp", scanTarget.Target) - assert.Equal(t, "snapshot", scanTarget.TargetType) + assert.Equal(t, SnapshotTargetType, scanTarget.TargetType) }) t.Run("parse instance target with a fully qualifed Azure resource id", func(t *testing.T) { scanner := &azureScannerInstance{ instanceInfo: instanceInfo{ - ResourceGroup: "my-rg", - InstanceName: "my-instance", + resourceGroup: "my-rg", + instanceName: "my-instance", }, } target := "/subscriptions/f1a2873a-6b27-4097-aa7c-3df51f103e96/resourceGroups/debian_group/providers/Microsoft.Compute/virtualMachines/debian" @@ -93,6 +114,27 @@ func TestParseTarget(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "debian_group", scanTarget.ResourceGroup) assert.Equal(t, "debian", scanTarget.Target) - assert.Equal(t, "instance", scanTarget.TargetType) + assert.Equal(t, InstanceTargetType, scanTarget.TargetType) + }) + t.Run("parse disk target with a fully qualifed Azure resource id", func(t *testing.T) { + scanner := &azureScannerInstance{ + instanceInfo: instanceInfo{ + resourceGroup: "my-rg", + instanceName: "my-instance", + }, + } + target := "/subscriptions/f1a2873a-6b27-4097-aa7c-3df51f103e96/resourceGroups/debian_group/providers/Microsoft.Compute/disks/disk-1" + + conf := &inventory.Config{ + Options: map[string]string{ + "target": target, + "type": "disk", + }, + } + scanTarget, err := ParseTarget(conf, scanner) + assert.NoError(t, err) + assert.Equal(t, "debian_group", scanTarget.ResourceGroup) + assert.Equal(t, "disk-1", scanTarget.Target) + assert.Equal(t, DiskTargetType, scanTarget.TargetType) }) } diff --git a/providers/azure/connection/azureinstancesnapshot/snapshot.go b/providers/azure/connection/azureinstancesnapshot/snapshot.go index 899957cbc6..79b2f5cda7 100644 --- a/providers/azure/connection/azureinstancesnapshot/snapshot.go +++ b/providers/azure/connection/azureinstancesnapshot/snapshot.go @@ -20,9 +20,39 @@ const ( createdValue = "cnspec" ) -func NewSnapshotCreator(token azcore.TokenCredential, subscriptionId string) (*SnapshotCreator, error) { +type snapshotCreator struct { + subscriptionId string + token azcore.TokenCredential + opts *policy.ClientOptions + labels map[string]*string +} + +type instanceInfo struct { + subscriptionId string + resourceGroup string + instanceName string + location string + bootDiskId string + zones []*string + // Attach the entire VM response as well + vm compute.VirtualMachine +} + +type snapshotInfo struct { + snapshotName string + snapshotId string + location string +} + +type diskInfo struct { + diskName string + diskId string + location string +} + +func NewSnapshotCreator(token azcore.TokenCredential, subscriptionId string) (*snapshotCreator, error) { createdByVal := createdValue - sc := &SnapshotCreator{ + sc := &snapshotCreator{ labels: map[string]*string{ createdByLabel: &createdByVal, }, @@ -32,22 +62,15 @@ func NewSnapshotCreator(token azcore.TokenCredential, subscriptionId string) (*S return sc, nil } -type SnapshotCreator struct { - subscriptionId string - token azcore.TokenCredential - opts *policy.ClientOptions - labels map[string]*string -} - -func (sc *SnapshotCreator) snapshotClient() (*compute.SnapshotsClient, error) { +func (sc *snapshotCreator) snapshotClient() (*compute.SnapshotsClient, error) { return compute.NewSnapshotsClient(sc.subscriptionId, sc.token, sc.opts) } -func (sc *SnapshotCreator) diskClient() (*compute.DisksClient, error) { +func (sc *snapshotCreator) diskClient() (*compute.DisksClient, error) { return compute.NewDisksClient(sc.subscriptionId, sc.token, sc.opts) } -func (sc *SnapshotCreator) computeClient() (*compute.VirtualMachinesClient, error) { +func (sc *snapshotCreator) computeClient() (*compute.VirtualMachinesClient, error) { return computeClient(sc.token, sc.subscriptionId, sc.opts) } @@ -55,18 +78,7 @@ func computeClient(token azcore.TokenCredential, subId string, opts *policy.Clie return compute.NewVirtualMachinesClient(subId, token, opts) } -type instanceInfo struct { - SubscriptionId string - ResourceGroup string - InstanceName string - Location string - BootDiskId string - Zones []*string - // Attach the entire VM response as well - Vm compute.VirtualMachine -} - -func (sc *SnapshotCreator) InstanceInfo(resourceGroup, instanceName string) (instanceInfo, error) { +func (sc *snapshotCreator) instanceInfo(resourceGroup, instanceName string) (instanceInfo, error) { return InstanceInfo(resourceGroup, instanceName, sc.subscriptionId, sc.token) } @@ -83,25 +95,17 @@ func InstanceInfo(resourceGroup, instanceName, subId string, token azcore.TokenC if err != nil { return ii, err } - ii.ResourceGroup = resourceGroup - ii.InstanceName = *instance.Name - ii.BootDiskId = *instance.Properties.StorageProfile.OSDisk.ManagedDisk.ID - ii.Location = *instance.Location - ii.SubscriptionId = subId - ii.Zones = instance.Zones - ii.Vm = instance.VirtualMachine + ii.resourceGroup = resourceGroup + ii.instanceName = *instance.Name + ii.bootDiskId = *instance.Properties.StorageProfile.OSDisk.ManagedDisk.ID + ii.location = *instance.Location + ii.subscriptionId = subId + ii.zones = instance.Zones + ii.vm = instance.VirtualMachine return ii, nil } -type snapshotInfo struct { - PlatformMrn string - ResourceGroup string - SnapshotName string - SnapshotId string - Location string -} - -func (sc *SnapshotCreator) SnapshotInfo(resourceGroup, snapshotName string) (snapshotInfo, error) { +func (sc *snapshotCreator) snapshotInfo(resourceGroup, snapshotName string) (snapshotInfo, error) { ctx := context.Background() si := snapshotInfo{} @@ -115,14 +119,34 @@ func (sc *SnapshotCreator) SnapshotInfo(resourceGroup, snapshotName string) (sna return si, err } - si.SnapshotName = *snapshot.Name - si.SnapshotId = *snapshot.ID - si.Location = *snapshot.Location + si.snapshotName = *snapshot.Name + si.snapshotId = *snapshot.ID + si.location = *snapshot.Location return si, nil } +func (sc *snapshotCreator) diskInfo(resourceGroup, snapshotName string) (diskInfo, error) { + ctx := context.Background() + di := diskInfo{} + + snapshotSvc, err := sc.diskClient() + if err != nil { + return di, err + } + + disk, err := snapshotSvc.Get(ctx, resourceGroup, snapshotName, &compute.DisksClientGetOptions{}) + if err != nil { + return di, err + } + + di.diskId = *disk.ID + di.diskName = *disk.Name + di.location = *disk.Location + return di, nil +} + // createDisk creates a new disk -func (sc *SnapshotCreator) createDisk(disk compute.Disk, resourceGroupName, diskName string) (compute.Disk, error) { +func (sc *snapshotCreator) createDisk(disk compute.Disk, resourceGroupName, diskName string) (compute.Disk, error) { ctx := context.Background() diskSvc, err := sc.diskClient() @@ -144,7 +168,7 @@ func (sc *SnapshotCreator) createDisk(disk compute.Disk, resourceGroupName, disk } // createSnapshotDisk creates a new disk from a snapshot -func (sc *SnapshotCreator) createSnapshotDisk(sourceSnaphotId, resourceGroupName, diskName, location string, zones []*string) (compute.Disk, error) { +func (sc *snapshotCreator) createSnapshotDisk(sourceSnaphotId, resourceGroupName, diskName, location string, zones []*string) (compute.Disk, error) { // create a new disk from snapshot createOpt := compute.DiskCreateOptionCopy disk := compute.Disk{ @@ -163,7 +187,7 @@ func (sc *SnapshotCreator) createSnapshotDisk(sourceSnaphotId, resourceGroupName } // cloneDisk clones a provided disk -func (sc *SnapshotCreator) cloneDisk(sourceDiskId, resourceGroupName, diskName string, location string, zones []*string) (compute.Disk, error) { +func (sc *snapshotCreator) cloneDisk(sourceDiskId, resourceGroupName, diskName string, location string, zones []*string) (compute.Disk, error) { // create a new disk by copying another disk createOpt := compute.DiskCreateOptionCopy disk := compute.Disk{ @@ -182,7 +206,7 @@ func (sc *SnapshotCreator) cloneDisk(sourceDiskId, resourceGroupName, diskName s } // attachDisk attaches a disk to an instance -func (sc *SnapshotCreator) attachDisk(targetInstance instanceInfo, diskName, diskId string, lun int32) error { +func (sc *snapshotCreator) attachDisk(targetInstance instanceInfo, diskName, diskId string, lun int32) error { ctx := context.Background() log.Debug().Str("disk-name", diskName).Int32("LUN", lun).Msg("attach disk") computeSvc, err := sc.computeClient() @@ -192,7 +216,7 @@ func (sc *SnapshotCreator) attachDisk(targetInstance instanceInfo, diskName, dis attachOpt := compute.DiskCreateOptionTypesAttach // the Azure API requires all disks to be specified, even the already attached ones. // we simply attach the new disk to the end of the already present list of data disks - disks := targetInstance.Vm.Properties.StorageProfile.DataDisks + disks := targetInstance.vm.Properties.StorageProfile.DataDisks disks = append(disks, &compute.DataDisk{ Name: &diskName, CreateOption: &attachOpt, @@ -202,7 +226,7 @@ func (sc *SnapshotCreator) attachDisk(targetInstance instanceInfo, diskName, dis }, }) vm := compute.VirtualMachine{ - Location: &targetInstance.Location, + Location: &targetInstance.location, Properties: &compute.VirtualMachineProperties{ StorageProfile: &compute.StorageProfile{ DataDisks: disks, @@ -210,7 +234,7 @@ func (sc *SnapshotCreator) attachDisk(targetInstance instanceInfo, diskName, dis }, } - poller, err := computeSvc.BeginCreateOrUpdate(ctx, targetInstance.ResourceGroup, targetInstance.InstanceName, vm, &compute.VirtualMachinesClientBeginCreateOrUpdateOptions{}) + poller, err := computeSvc.BeginCreateOrUpdate(ctx, targetInstance.resourceGroup, targetInstance.instanceName, vm, &compute.VirtualMachinesClientBeginCreateOrUpdateOptions{}) if err != nil { return err } @@ -232,9 +256,9 @@ func (sc *SnapshotCreator) attachDisk(targetInstance instanceInfo, diskName, dis return err } -func (sc *SnapshotCreator) detachDisk(diskName string, targetInstance instanceInfo) error { +func (sc *snapshotCreator) detachDisk(diskName string, targetInstance instanceInfo) error { ctx := context.Background() - log.Debug().Str("instance-name", targetInstance.InstanceName).Msg("detach disk from instance") + log.Debug().Str("instance-name", targetInstance.instanceName).Msg("detach disk from instance") computeSvc, err := sc.computeClient() if err != nil { return err @@ -243,15 +267,15 @@ func (sc *SnapshotCreator) detachDisk(diskName string, targetInstance instanceIn // we stored the disks as they were before attaching the new one in the targetInstance. // we simply use that list which will result in the new disk being detached vm := compute.VirtualMachine{ - Location: &targetInstance.Location, + Location: &targetInstance.location, Properties: &compute.VirtualMachineProperties{ StorageProfile: &compute.StorageProfile{ - DataDisks: targetInstance.Vm.Properties.StorageProfile.DataDisks, + DataDisks: targetInstance.vm.Properties.StorageProfile.DataDisks, }, }, } - poller, err := computeSvc.BeginCreateOrUpdate(ctx, targetInstance.ResourceGroup, targetInstance.InstanceName, vm, &compute.VirtualMachinesClientBeginCreateOrUpdateOptions{}) + poller, err := computeSvc.BeginCreateOrUpdate(ctx, targetInstance.resourceGroup, targetInstance.instanceName, vm, &compute.VirtualMachinesClientBeginCreateOrUpdateOptions{}) if err != nil { return err } @@ -274,7 +298,7 @@ func (sc *SnapshotCreator) detachDisk(diskName string, targetInstance instanceIn } // deleteCreatedDisk deletes the given disk if it matches the created label -func (sc *SnapshotCreator) deleteCreatedDisk(resourceGroup, diskName string) error { +func (sc *snapshotCreator) deleteCreatedDisk(resourceGroup, diskName string) error { ctx := context.Background() diskSvc, err := sc.diskClient() diff --git a/providers/azure/provider/provider.go b/providers/azure/provider/provider.go index 87e82345a8..f6940b549a 100644 --- a/providers/azure/provider/provider.go +++ b/providers/azure/provider/provider.go @@ -116,12 +116,18 @@ func handleAzureComputeSubcommands(args []string, config *inventory.Config) erro case "instance": config.Type = string(azureinstancesnapshot.SnapshotConnectionType) config.Discover = nil - config.Options["type"] = "instance" + config.Options["type"] = azureinstancesnapshot.InstanceTargetType config.Options["target"] = args[2] return nil case "snapshot": config.Type = string(azureinstancesnapshot.SnapshotConnectionType) - config.Options["type"] = "snapshot" + config.Options["type"] = azureinstancesnapshot.SnapshotTargetType + config.Options["target"] = args[2] + config.Discover = nil + return nil + case "disk": + config.Type = string(azureinstancesnapshot.SnapshotConnectionType) + config.Options["type"] = azureinstancesnapshot.DiskTargetType config.Options["target"] = args[2] config.Discover = nil return nil