Skip to content

Commit

Permalink
⭐️ Azure snapshot/instance scanning (#2943)
Browse files Browse the repository at this point in the history
* ⭐️ Azure snapshot/instance scanning

Signed-off-by: Preslav <[email protected]>

* go mods.

* Add tests for LUN parsing, simplify conf for the snapshot provider

---------

Signed-off-by: Preslav <[email protected]>
  • Loading branch information
preslavgerchev authored Jan 5, 2024
1 parent 5880394 commit 6eb57f9
Show file tree
Hide file tree
Showing 16 changed files with 992 additions and 29 deletions.
10 changes: 0 additions & 10 deletions providers-sdk/v1/plugin/ids.go

This file was deleted.

12 changes: 8 additions & 4 deletions providers/azure/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@ package config

import (
"go.mondoo.com/cnquery/v9/providers-sdk/v1/plugin"
"go.mondoo.com/cnquery/v9/providers/azure/connection/azureinstancesnapshot"
"go.mondoo.com/cnquery/v9/providers/azure/provider"
"go.mondoo.com/cnquery/v9/providers/azure/resources"
)

var Config = plugin.Provider{
Name: "azure",
ID: "go.mondoo.com/cnquery/v9/providers/azure",
Version: "9.1.16",
ConnectionTypes: []string{provider.ConnectionType},
Name: "azure",
ID: "go.mondoo.com/cnquery/v9/providers/azure",
Version: "9.1.16",
ConnectionTypes: []string{
provider.ConnectionType,
string(azureinstancesnapshot.SnapshotConnectionType),
},
Connectors: []plugin.Connector{
{
Name: "azure",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Mondoo, Inc.
// SPDX-License-Identifier: BUSL-1.1

package connection
package auth

import (
"fmt"
Expand All @@ -13,7 +13,7 @@ import (
"go.mondoo.com/cnquery/v9/providers-sdk/v1/vault"
)

func getTokenCredential(credential *vault.Credential, tenantId, clientId string) (azcore.TokenCredential, error) {
func GetTokenCredential(credential *vault.Credential, tenantId, clientId string) (azcore.TokenCredential, error) {
var azCred azcore.TokenCredential
var err error
// fallback to CLI authorizer if no credentials are specified
Expand All @@ -24,7 +24,6 @@ func getTokenCredential(credential *vault.Credential, tenantId, clientId string)
return nil, errors.Wrap(err, "error creating CLI credentials")
}
} else {
// we only support private key authentication for ms 365
switch credential.Type {
case vault.CredentialType_pkcs12:
certs, privateKey, err := azidentity.ParseCertificates(credential.Secret, []byte(credential.Password))
Expand Down
114 changes: 114 additions & 0 deletions providers/azure/connection/azureinstancesnapshot/lun.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright (c) Mondoo, Inc.
// SPDX-License-Identifier: BUSL-1.1

package azureinstancesnapshot

import (
"fmt"
"io"
"strconv"
"strings"

"github.com/cockroachdb/errors"
"github.com/rs/zerolog/log"
"go.mondoo.com/cnquery/v9/providers/os/connection"
)

type deviceInfo struct {
// the LUN number, e.g. 3
Lun int32
// where the disk is mounted, e.g. /dev/sda
VolumePath string
}

func (a *azureScannerInstance) getAvailableLun(mountedDevices []deviceInfo) (int32, error) {
takenLuns := []int32{}
for _, d := range mountedDevices {
takenLuns = append(takenLuns, d.Lun)
}

availableLuns := []int32{}
// the available LUNs are 0-63, so we exclude everything thats in takenLuns
for i := int32(0); i < 64; i++ {
exists := false
for _, d := range takenLuns {
if d == i {
exists = true
break
}
}
if exists {
// log just for visibility
log.Debug().Int32("LUN", i).Msg("azure snapshot> LUN is taken, skipping")
} else {
availableLuns = append(availableLuns, i)
}
}
if len(availableLuns) == 0 {
return 0, errors.New("no available LUNs to attach disk to")
}
return availableLuns[0], nil
}

// https://learn.microsoft.com/en-us/azure/virtual-machines/linux/azure-to-guest-disk-mapping
// for more information. we want to find the LUNs of the data disks and their mount location
func getMountedDevices(localConn *connection.LocalConnection) ([]deviceInfo, error) {
cmd, err := localConn.RunCommand("lsscsi --brief")
if err != nil {
return nil, err
}
if cmd.ExitStatus != 0 {
outErr, err := io.ReadAll(cmd.Stderr)
if err != nil {
return nil, err
}
return nil, fmt.Errorf("failed to list logical unit numbers: %s", outErr)
}
data, err := io.ReadAll(cmd.Stdout)
if err != nil {
return nil, err
}
output := string(data)
return parseLsscsiOutput(output)
}

func getMatchingDevice(mountedDevices []deviceInfo, lun int32) (deviceInfo, error) {
for _, d := range mountedDevices {
if d.Lun == lun {
return d, nil
}
}
return deviceInfo{}, errors.New("could not find matching device")
}

// parses the output from running 'lsscsi --brief' and gets the device info, the output looks like this:
// [0:0:0:0] /dev/sda
// [1:0:0:0] /dev/sdb
func parseLsscsiOutput(output string) ([]deviceInfo, error) {
lines := strings.Split(strings.TrimSpace(output), "\n")
mountedDevices := []deviceInfo{}
for _, line := range lines {
log.Debug().Str("line", line).Msg("azure snapshot> parsing lsscsi output")
if line == "" {
continue
}
parts := strings.Fields(strings.TrimSpace(line))
if len(parts) != 2 {
return nil, fmt.Errorf("invalid lsscsi output: %s", line)
}
lunInfo := parts[0]
path := parts[1]
// trim the [], turning [1:0:0:0] into 1:0:0:0
trimLun := strings.Trim(lunInfo, "[]")
splitLun := strings.Split(trimLun, ":")
// the LUN is the last one
lun := splitLun[len(splitLun)-1]
lunInt, err := strconv.Atoi(lun)
if err != nil {
return nil, err
}
mountedDevices = append(mountedDevices, deviceInfo{Lun: int32(lunInt), VolumePath: path})
}

return mountedDevices, nil
}
31 changes: 31 additions & 0 deletions providers/azure/connection/azureinstancesnapshot/lun_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Mondoo, Inc.
// SPDX-License-Identifier: BUSL-1.1

package azureinstancesnapshot

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestParseLsscsiOutput(t *testing.T) {
// different padding for the device names on purpose + an extra blank line
output := `
[0:0:0:0] /dev/sda
[0:0:1:1] /dev/sdb
[0:0:1:2] /dev/sdc
[0:0:0:3] /dev/sdd
`
devices, err := parseLsscsiOutput(output)
assert.NoError(t, err)
assert.Len(t, devices, 4)
expected := []deviceInfo{
{Lun: 0, VolumePath: "/dev/sda"},
{Lun: 1, VolumePath: "/dev/sdb"},
{Lun: 2, VolumePath: "/dev/sdc"},
{Lun: 3, VolumePath: "/dev/sdd"},
}
assert.ElementsMatch(t, expected, devices)
}
8 changes: 8 additions & 0 deletions providers/azure/connection/azureinstancesnapshot/platform.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) Mondoo, Inc.
// SPDX-License-Identifier: BUSL-1.1

package azureinstancesnapshot

func SnapshotPlatformMrn(snapshotId string) string {
return "//platformid.api.mondoo.app/runtime/azure" + snapshotId
}
Loading

0 comments on commit 6eb57f9

Please sign in to comment.