diff --git a/Dockerfile.cli b/Dockerfile.cli index 0a8a77d60..43363e78c 100644 --- a/Dockerfile.cli +++ b/Dockerfile.cli @@ -19,6 +19,8 @@ RUN --mount=type=cache,target=/go/pkg/mod \ FROM ${VMCLARITY_TOOLS_BASE} +RUN apk add util-linux + WORKDIR /app COPY --from=builder /build/cli/cli ./vmclarity-cli diff --git a/cli/cmd/root.go b/cli/cmd/root.go index c55548a68..84379314d 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -21,11 +21,14 @@ import ( "os" "github.com/ghodss/yaml" + "github.com/openclarity/kubeclarity/shared/pkg/utils" + uuid "github.com/satori/go.uuid" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/openclarity/vmclarity/cli/pkg" + "github.com/openclarity/vmclarity/cli/pkg/mount" "github.com/openclarity/vmclarity/shared/pkg/families" "github.com/openclarity/vmclarity/shared/pkg/families/exploits" "github.com/openclarity/vmclarity/shared/pkg/families/results" @@ -42,6 +45,12 @@ var ( server string scanResultID string + mountVolume bool +) + +const ( + fsTypeExt4 = "ext4" + fsTypeXFS = "xfs" ) // rootCmd represents the base command when called without any subcommands. @@ -53,6 +62,15 @@ var rootCmd = &cobra.Command{ SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { logger.Infof("Running...") + + if mountVolume { + mountPoints, err := mountAttachedVolume() + if err != nil { + return fmt.Errorf("failed to mount attached volume: %v", err) + } + setMountPointsForFamiliesInput(mountPoints, config) + } + var exporter *Exporter if server != "" { exp, err := CreateExporter() @@ -193,6 +211,7 @@ func init() { rootCmd.PersistentFlags().StringVar(&output, "output", "", "set file path output (default: stdout)") rootCmd.PersistentFlags().StringVar(&server, "server", "", "VMClarity server to export scan results to, for example: http://localhost:9999/api") rootCmd.PersistentFlags().StringVar(&scanResultID, "scan-result-id", "", "the ScanResult ID to export the scan results to") + rootCmd.PersistentFlags().BoolVar(&mountVolume, "mount-attached-volume", false, "discover for an attached volume and mount it before the scan") // TODO(sambetts) we may have to change this to our own validation when // we add the CI/CD scenario and there isn't an existing scan-result-id @@ -266,3 +285,63 @@ func Output(bytes []byte, outputPrefix string) error { return nil } + +func isSupportedFS(fs string) bool { + switch fs { + case fsTypeExt4, fsTypeXFS: + return true + } + return false +} + +func setMountPointsForFamiliesInput(mountPoints []string, familiesConfig *families.Config) *families.Config { + // update families inputs with the mount point as rootfs + for _, mountDir := range mountPoints { + if familiesConfig.SBOM.Enabled { + familiesConfig.SBOM.Inputs = append(familiesConfig.SBOM.Inputs, sbom.Input{ + Input: mountDir, + InputType: string(utils.ROOTFS), + }) + } + if familiesConfig.Vulnerabilities.Enabled { + if familiesConfig.SBOM.Enabled { + familiesConfig.Vulnerabilities.InputFromSbom = true + } else { + familiesConfig.Vulnerabilities.Inputs = append(familiesConfig.Vulnerabilities.Inputs, vulnerabilities.Input{ + Input: mountDir, + InputType: string(utils.ROOTFS), + }) + } + } + if familiesConfig.Secrets.Enabled { + familiesConfig.Secrets.Inputs = append(familiesConfig.Secrets.Inputs, secrets.Input{ + Input: mountDir, + InputType: string(utils.ROOTFS), + }) + } + } + return familiesConfig +} + +func mountAttachedVolume() ([]string, error) { + var mountPoints []string + + devices, err := mount.ListBlockDevices() + if err != nil { + return nil, fmt.Errorf("failed to list block devices: %v", err) + } + for _, device := range devices { + // if the device is not mounted and of a supported filesystem type, + // we assume it belongs to the attached volume, so we mount it. + if device.MountPoint == "" && isSupportedFS(device.FilesystemType) { + mountDir := "/mnt/snapshot" + uuid.NewV4().String() + + if err := device.Mount(mountDir); err != nil { + return nil, fmt.Errorf("failed to mount device: %v", err) + } + logger.Infof("Mounted device %v on %v", device.DeviceName, mountDir) + mountPoints = append(mountPoints, mountDir) + } + } + return mountPoints, nil +} diff --git a/cli/cmd/root_test.go b/cli/cmd/root_test.go new file mode 100644 index 000000000..b12636abf --- /dev/null +++ b/cli/cmd/root_test.go @@ -0,0 +1,158 @@ +// Copyright © 2022 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "reflect" + "testing" + + "github.com/openclarity/kubeclarity/shared/pkg/utils" + + "github.com/openclarity/vmclarity/shared/pkg/families" + "github.com/openclarity/vmclarity/shared/pkg/families/sbom" + "github.com/openclarity/vmclarity/shared/pkg/families/secrets" + "github.com/openclarity/vmclarity/shared/pkg/families/vulnerabilities" +) + +func Test_isSupportedFS(t *testing.T) { + type args struct { + fs string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "supported ext4", + args: args{ + fs: fsTypeExt4, + }, + want: true, + }, + { + name: "supported xfs", + args: args{ + fs: fsTypeXFS, + }, + want: true, + }, + { + name: "not supported btrfs", + args: args{ + fs: "btrfs", + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isSupportedFS(tt.args.fs); got != tt.want { + t.Errorf("isSupportedFS() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_setMountPointsForFamiliesInput(t *testing.T) { + type args struct { + mountPoints []string + familiesConfig *families.Config + } + tests := []struct { + name string + args args + want *families.Config + }{ + { + name: "sbom, vuls and secrets are enabled", + args: args{ + mountPoints: []string{"/mnt/snapshot1"}, + familiesConfig: &families.Config{ + SBOM: sbom.Config{ + Enabled: true, + Inputs: nil, + }, + Vulnerabilities: vulnerabilities.Config{ + Enabled: true, + Inputs: nil, + InputFromSbom: false, + }, + Secrets: secrets.Config{ + Enabled: true, + Inputs: nil, + }, + }, + }, + want: &families.Config{ + SBOM: sbom.Config{ + Enabled: true, + Inputs: []sbom.Input{ + { + Input: "/mnt/snapshot1", + InputType: string(utils.ROOTFS), + }, + }, + }, + Vulnerabilities: vulnerabilities.Config{ + Enabled: true, + InputFromSbom: true, + }, + Secrets: secrets.Config{ + Enabled: true, + Inputs: []secrets.Input{ + { + Input: "/mnt/snapshot1", + InputType: string(utils.ROOTFS), + }, + }, + }, + }, + }, + { + name: "only vuls enabled", + args: args{ + mountPoints: []string{"/mnt/snapshot1"}, + familiesConfig: &families.Config{ + Vulnerabilities: vulnerabilities.Config{ + Enabled: true, + Inputs: nil, + InputFromSbom: false, + }, + }, + }, + want: &families.Config{ + Vulnerabilities: vulnerabilities.Config{ + Enabled: true, + Inputs: []vulnerabilities.Input{ + { + Input: "/mnt/snapshot1", + InputType: string(utils.ROOTFS), + }, + }, + InputFromSbom: false, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := setMountPointsForFamiliesInput(tt.args.mountPoints, tt.args.familiesConfig); !reflect.DeepEqual(got, tt.want) { + t.Errorf("setMountPointsForFamiliesInput() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/cli/pkg/mount/mount.go b/cli/pkg/mount/mount.go new file mode 100644 index 000000000..b18806767 --- /dev/null +++ b/cli/pkg/mount/mount.go @@ -0,0 +1,140 @@ +// Copyright © 2022 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mount + +import ( + "bufio" + "bytes" + "fmt" + "os" + "os/exec" + "regexp" + "strconv" + "strings" + + log "github.com/sirupsen/logrus" + "k8s.io/mount-utils" +) + +var pairsRE = regexp.MustCompile(`([A-Z]+)=(?:"(.*?)")`) + +type BlockDevice struct { + DeviceName string + Size uint64 + Label string + UUID string + FilesystemType string + MountPoint string +} + +const ( + base = 10 + baseSize = 64 + kbToByte = 1024 +) + +// ListBlockDevices Taken from https://github.com/BishopFox/dufflebag +// nolint:cyclop +func ListBlockDevices() ([]BlockDevice, error) { + log.Info("Listing block devices...") + columns := []string{ + "NAME", // name + "SIZE", // size + "LABEL", // filesystem label + "UUID", // filesystem UUID + "FSTYPE", // filesystem type + "TYPE", // device type + "MOUNTPOINT", // device mountpoint + } + + log.Info("executing lsblk...") + //nolint: gosec + output, err := exec.Command( + "lsblk", + "-b", // output size in bytes + "-P", // output fields as key=value pairs + "-o", strings.Join(columns, ","), + ).Output() + if err != nil { + return nil, fmt.Errorf("cannot list block devices: %v", err) + } + + blockDeviceMap := make(map[string]BlockDevice) + s := bufio.NewScanner(bytes.NewReader(output)) + for s.Scan() { + pairs := pairsRE.FindAllStringSubmatch(s.Text(), -1) + var dev BlockDevice + var deviceType string + for _, pair := range pairs { + switch pair[1] { + case "NAME": + dev.DeviceName = pair[2] + case "SIZE": + size, err := strconv.ParseUint(pair[2], base, baseSize) + if err != nil { + log.Warnf( + "Invalid size %q from lsblk: %v", pair[2], err, + ) + } else { + // the number of bytes in a MiB. + dev.Size = size / kbToByte * kbToByte + } + case "LABEL": + dev.Label = pair[2] + case "UUID": + dev.UUID = pair[2] + case "FSTYPE": + dev.FilesystemType = pair[2] + case "TYPE": + deviceType = pair[2] + case "MOUNTPOINT": + dev.MountPoint = pair[2] + default: + log.Warnf("unexpected field from lsblk: %q", pair[1]) + } + } + + if deviceType == "loop" { + continue + } + + blockDeviceMap[dev.DeviceName] = dev + } + if err := s.Err(); err != nil { + return nil, fmt.Errorf("cannot parse lsblk output: %v", err) + } + + blockDevices := make([]BlockDevice, 0, len(blockDeviceMap)) + for _, dev := range blockDeviceMap { + blockDevices = append(blockDevices, dev) + } + return blockDevices, nil +} + +func (b BlockDevice) Mount(mountPoint string) error { + // Make a directory for the device to mount to + if err := os.MkdirAll(mountPoint, os.ModePerm); err != nil { + return fmt.Errorf("failed to run mkdir comand: %v", err) + } + + // Do the mount + mounter := mount.New(mountPoint) + if err := mounter.Mount("/dev/"+b.DeviceName, mountPoint, b.FilesystemType, nil); err != nil { + return fmt.Errorf("failed to run mount command: %v", err) + } + + return nil +} diff --git a/go.mod b/go.mod index abbf4e840..78413bf65 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( gorm.io/driver/sqlite v1.3.6 gorm.io/gorm v1.23.5 gotest.tools/v3 v3.4.0 + k8s.io/mount-utils v0.26.2 ) require ( @@ -424,7 +425,7 @@ require ( golang.org/x/net v0.2.0 // indirect golang.org/x/oauth2 v0.1.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.2.0 // indirect + golang.org/x/sys v0.5.0 // indirect golang.org/x/term v0.2.0 // indirect golang.org/x/text v0.4.0 // indirect golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect @@ -452,10 +453,10 @@ require ( k8s.io/cli-runtime v0.25.3 // indirect k8s.io/client-go v0.25.3 // indirect k8s.io/component-base v0.25.3 // indirect - k8s.io/klog/v2 v2.70.1 // indirect + k8s.io/klog/v2 v2.80.1 // indirect k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect k8s.io/kubectl v0.25.3 // indirect - k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect + k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect lukechampine.com/uint128 v1.1.1 // indirect modernc.org/cc/v3 v3.36.0 // indirect modernc.org/ccgo/v3 v3.16.6 // indirect diff --git a/go.sum b/go.sum index 2d3f51b9c..3efcdde2a 100644 --- a/go.sum +++ b/go.sum @@ -2865,8 +2865,9 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -3394,8 +3395,8 @@ k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= -k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= @@ -3406,13 +3407,15 @@ k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHU k8s.io/kubectl v0.25.3 h1:HnWJziEtmsm4JaJiKT33kG0kadx68MXxUE8UEbXnN4U= k8s.io/kubectl v0.25.3/go.mod h1:glU7PiVj/R6Ud4A9FJdTcJjyzOtCJyc0eO7Mrbh3jlI= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/mount-utils v0.26.2 h1:KoRKqCAAK2l37l71YMvKx6vaLToh52RkNx1RU/dSLGQ= +k8s.io/mount-utils v0.26.2/go.mod h1:95yx9K6N37y8YZ0/lUh9U6ITosMODNaW0/v4wvaa0Xw= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= -k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs= +k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/cc/v3 v3.36.0 h1:0kmRkTmqNidmu3c7BNDSdVHCxXCkWLmWmCIVX4LUboo= diff --git a/installation/aws/VmClarity.cfn b/installation/aws/VmClarity.cfn index f362a71f0..c4843d6f3 100644 --- a/installation/aws/VmClarity.cfn +++ b/installation/aws/VmClarity.cfn @@ -163,7 +163,7 @@ Resources: LOCAL_DB_PATH=/data/vmclarity.db BACKEND_REST_ADDRESS=__BACKEND_REST_ADDRESS__ BACKEND_REST_PORT=8888 - SCANNER_CONTAINER_IMAGE=tehsmash/vmclarity-cli:f571174635a95252a046a0083968332b23c8d5f1 + SCANNER_CONTAINER_IMAGE=erezfish/vmclarity-cli:mount - JobImageID: !FindInMap - AWSRegionArch2AMI - !Ref "AWS::Region" @@ -212,13 +212,13 @@ Resources: ExecStartPre=-/usr/bin/docker stop %n ExecStartPre=-/usr/bin/docker rm %n ExecStartPre=/usr/bin/mkdir -p /opt/vmclarity - ExecStartPre=/usr/bin/docker pull tehsmash/vmclarity-backend:f571174635a95252a046a0083968332b23c8d5f1 + ExecStartPre=/usr/bin/docker pull erezfish/vmclarity-backend:mount ExecStart=/usr/bin/docker run \ --rm --name %n \ -p 0.0.0.0:8888:8888/tcp \ -v /opt/vmclarity:/data \ --env-file /etc/vmclarity/config.env \ - tehsmash/vmclarity-backend:f571174635a95252a046a0083968332b23c8d5f1 run --log-level info + erezfish/vmclarity-backend:mount run --log-level info [Install] WantedBy=multi-user.target diff --git a/runtime_scan/pkg/cloudinit/cloudinit.go b/runtime_scan/pkg/cloudinit/cloudinit.go index cde6c300d..795d79648 100644 --- a/runtime_scan/pkg/cloudinit/cloudinit.go +++ b/runtime_scan/pkg/cloudinit/cloudinit.go @@ -24,12 +24,11 @@ import ( ) type Data struct { - Volume string // Volume to mount e.g. /dev/sdc - VolumeMountDirectory string // Directory to mount the volume too (must match whats in the ScannerConfig for inputs) - ScannerCLIConfig string // Scanner families configuration file yaml - ScannerImage string // Scanner container image to use - ServerAddress string // IP address of VMClarity backend for export - ScanResultID string // ScanResult ID to export the results to + Volume string // Volume to mount e.g. /dev/sdc + ScannerCLIConfig string // Scanner families configuration file yaml + ScannerImage string // Scanner container image to use + ServerAddress string // IP address of VMClarity backend for export + ScanResultID string // ScanResult ID to export the results to } func GenerateCloudInit(data Data) (string, error) { diff --git a/runtime_scan/pkg/cloudinit/cloudinit_template.go b/runtime_scan/pkg/cloudinit/cloudinit_template.go index 757eb9aa5..3d1cc2dee 100644 --- a/runtime_scan/pkg/cloudinit/cloudinit_template.go +++ b/runtime_scan/pkg/cloudinit/cloudinit_template.go @@ -36,12 +36,13 @@ write_files: Type=oneshot WorkingDirectory=/opt/vmclarity ExecStartPre=docker pull {{ .ScannerImage }} - ExecStart=docker run --rm --name %n \ - -v /mnt/snapshot:{{ .VolumeMountDirectory }} \ + ExecStart=docker run --rm --name %n --privileged \ -v /opt/vmclarity:/opt/vmclarity \ + -v /run:/run \ {{ .ScannerImage }} \ --config /opt/vmclarity/scanconfig.yaml \ --server {{ .ServerAddress }} \ + --mount-attached-volume \ --scan-result-id {{ .ScanResultID }} [Install] @@ -49,7 +50,5 @@ write_files: runcmd: - [ systemctl, daemon-reload ] - [ systemctl, start, docker.service ] - - [ mkdir, -p, /mnt/snapshot ] - - [ mount, /dev/{{ .Volume }}1, /mnt/snapshot ] - [ systemctl, start, vmclarity-scanner.service ] ` diff --git a/runtime_scan/pkg/provider/aws/client.go b/runtime_scan/pkg/provider/aws/client.go index 9b4ce70de..f22027099 100644 --- a/runtime_scan/pkg/provider/aws/client.go +++ b/runtime_scan/pkg/provider/aws/client.go @@ -190,12 +190,11 @@ func convertBool(all *bool) bool { func (c *Client) RunScanningJob(ctx context.Context, snapshot types.Snapshot, config provider.ScanningJobConfig) (types.Instance, error) { cloudInitData := cloudinit.Data{ - Volume: c.awsConfig.DeviceName, - ScannerCLIConfig: config.ScannerCLIConfig, - ScannerImage: config.ScannerImage, - ServerAddress: config.VMClarityAddress, - ScanResultID: config.ScanResultID, - VolumeMountDirectory: config.VolumeMountDirectory, + Volume: c.awsConfig.DeviceName, + ScannerCLIConfig: config.ScannerCLIConfig, + ScannerImage: config.ScannerImage, + ServerAddress: config.VMClarityAddress, + ScanResultID: config.ScanResultID, } userData, err := cloudinit.GenerateCloudInit(cloudInitData) if err != nil { diff --git a/runtime_scan/pkg/provider/client.go b/runtime_scan/pkg/provider/client.go index 8013471f9..98ba5224a 100644 --- a/runtime_scan/pkg/provider/client.go +++ b/runtime_scan/pkg/provider/client.go @@ -23,12 +23,11 @@ import ( ) type ScanningJobConfig struct { - ScannerImage string // Scanner Container Image to use containing the vmclarity-cli and tools - ScannerCLIConfig string // Scanner CLI config yaml (families config yaml) - VolumeMountDirectory string // The directory where the scanner should mount the volume to scan so that the CLI can find it - VMClarityAddress string // The backend address for the scanner CLI to export too - ScanResultID string // The ID of the ScanResult that the scanner CLI should update - KeyPairName string // The name of the key pair to set on the instance, ignored if not set, used mainly for debugging. + ScannerImage string // Scanner Container Image to use containing the vmclarity-cli and tools + ScannerCLIConfig string // Scanner CLI config yaml (families config yaml) + VMClarityAddress string // The backend address for the scanner CLI to export too + ScanResultID string // The ID of the ScanResult that the scanner CLI should update + KeyPairName string // The name of the key pair to set on the instance, ignored if not set, used mainly for debugging. } type Client interface { diff --git a/runtime_scan/pkg/scanner/job_managment.go b/runtime_scan/pkg/scanner/job_managment.go index 61cb037d6..bcf5bc790 100644 --- a/runtime_scan/pkg/scanner/job_managment.go +++ b/runtime_scan/pkg/scanner/job_managment.go @@ -26,7 +26,6 @@ import ( "gopkg.in/yaml.v3" kubeclarityConfig "github.com/openclarity/kubeclarity/shared/pkg/config" - "github.com/openclarity/kubeclarity/shared/pkg/utils" "github.com/openclarity/vmclarity/api/models" "github.com/openclarity/vmclarity/runtime_scan/pkg/config" @@ -362,22 +361,17 @@ func (s *Scanner) runJob(ctx context.Context, data *scanData) (types.Job, error) } } - // Scanner job picks the path where the VM volume should be mounted so - // that the VMClarity CLI config and the provider are synced up on the - // expected location of the volume on disk. - volumeMountDirectory := "/vmToBeScanned" - familiesConfiguration, err := s.generateFamiliesConfigurationYaml(volumeMountDirectory) + familiesConfiguration, err := s.generateFamiliesConfigurationYaml() if err != nil { return types.Job{}, fmt.Errorf("failed to generate scanner configuration yaml: %w", err) } scanningJobConfig := provider.ScanningJobConfig{ - ScannerImage: s.config.ScannerImage, - ScannerCLIConfig: familiesConfiguration, - VolumeMountDirectory: volumeMountDirectory, - VMClarityAddress: s.config.ScannerBackendAddress, - ScanResultID: data.scanResultID, - KeyPairName: s.config.ScannerKeyPairName, + ScannerImage: s.config.ScannerImage, + ScannerCLIConfig: familiesConfiguration, + VMClarityAddress: s.config.ScannerBackendAddress, + ScanResultID: data.scanResultID, + KeyPairName: s.config.ScannerKeyPairName, } launchInstance, err = s.providerClient.RunScanningJob(ctx, launchSnapshot, scanningJobConfig) if err != nil { @@ -388,11 +382,11 @@ func (s *Scanner) runJob(ctx context.Context, data *scanData) (types.Job, error) return job, nil } -func (s *Scanner) generateFamiliesConfigurationYaml(scanRootDirectory string) (string, error) { +func (s *Scanner) generateFamiliesConfigurationYaml() (string, error) { famConfig := families.Config{ - SBOM: userSBOMConfigToFamiliesSbomConfig(s.scanConfig.ScanFamiliesConfig.Sbom, scanRootDirectory), + SBOM: userSBOMConfigToFamiliesSbomConfig(s.scanConfig.ScanFamiliesConfig.Sbom), Vulnerabilities: userVulnConfigToFamiliesVulnConfig(s.scanConfig.ScanFamiliesConfig.Vulnerabilities), - Secrets: userSecretsConfigToFamiliesSecretsConfig(s.scanConfig.ScanFamiliesConfig.Secrets, scanRootDirectory, s.config.GitleaksBinaryPath), + Secrets: userSecretsConfigToFamiliesSecretsConfig(s.scanConfig.ScanFamiliesConfig.Secrets, s.config.GitleaksBinaryPath), Exploits: userExploitsConfigToFamiliesExploitsConfig(s.scanConfig.ScanFamiliesConfig.Exploits, s.config.ExploitsDBAddress), // TODO(sambetts) Configure other families once we've got the known working ones working e2e } @@ -405,7 +399,7 @@ func (s *Scanner) generateFamiliesConfigurationYaml(scanRootDirectory string) (s return string(famConfigYaml), nil } -func userSecretsConfigToFamiliesSecretsConfig(secretsConfig *models.SecretsConfig, scanRootDirectory string, gitleaksBinaryPath string) secrets.Config { +func userSecretsConfigToFamiliesSecretsConfig(secretsConfig *models.SecretsConfig, gitleaksBinaryPath string) secrets.Config { if secretsConfig == nil || secretsConfig.Enabled == nil || !*secretsConfig.Enabled { return secrets.Config{} } @@ -413,12 +407,7 @@ func userSecretsConfigToFamiliesSecretsConfig(secretsConfig *models.SecretsConfi Enabled: true, // TODO(idanf) This choice should come from the user's configuration ScannersList: []string{"gitleaks"}, - Inputs: []secrets.Input{ - { - Input: scanRootDirectory, - InputType: string(utils.DIR), - }, - }, + Inputs: nil, // rootfs directory will be determined by the CLI after mount. ScannersConfig: &common.ScannersConfig{ Gitleaks: gitleaksconfig.Config{ BinaryPath: gitleaksBinaryPath, @@ -427,7 +416,7 @@ func userSecretsConfigToFamiliesSecretsConfig(secretsConfig *models.SecretsConfi } } -func userSBOMConfigToFamiliesSbomConfig(sbomConfig *models.SBOMConfig, scanRootDirectory string) familiesSbom.Config { +func userSBOMConfigToFamiliesSbomConfig(sbomConfig *models.SBOMConfig) familiesSbom.Config { if sbomConfig == nil || sbomConfig.Enabled == nil || !*sbomConfig.Enabled { return familiesSbom.Config{} } @@ -435,12 +424,7 @@ func userSBOMConfigToFamiliesSbomConfig(sbomConfig *models.SBOMConfig, scanRootD Enabled: true, // TODO(sambetts) This choice should come from the user's configuration AnalyzersList: []string{"syft", "trivy"}, - Inputs: []familiesSbom.Input{ - { - Input: scanRootDirectory, - InputType: string(utils.ROOTFS), - }, - }, + Inputs: nil, // rootfs directory will be determined by the CLI after mount. AnalyzersConfig: &kubeclarityConfig.Config{ // TODO(sambetts) The user needs to be able to provide this configuration Registry: &kubeclarityConfig.Registry{}, @@ -462,7 +446,7 @@ func userVulnConfigToFamiliesVulnConfig(vulnerabilitiesConfig *models.Vulnerabil Enabled: true, // TODO(sambetts) This choice should come from the user's configuration ScannersList: []string{"grype", "trivy"}, - InputFromSbom: true, + InputFromSbom: false, // will be determined by the CLI. ScannersConfig: &kubeclarityConfig.Config{ // TODO(sambetts) The user needs to be able to provide this configuration Registry: &kubeclarityConfig.Registry{}, diff --git a/runtime_scan/pkg/scanner/job_managment_test.go b/runtime_scan/pkg/scanner/job_managment_test.go index b8217a955..a7dc321ca 100644 --- a/runtime_scan/pkg/scanner/job_managment_test.go +++ b/runtime_scan/pkg/scanner/job_managment_test.go @@ -21,7 +21,6 @@ import ( "github.com/anchore/syft/syft/source" "github.com/google/go-cmp/cmp" kubeclarityConfig "github.com/openclarity/kubeclarity/shared/pkg/config" - kubeclarityUtils "github.com/openclarity/kubeclarity/shared/pkg/utils" "github.com/openclarity/vmclarity/api/models" familiesSbom "github.com/openclarity/vmclarity/shared/pkg/families/sbom" @@ -34,8 +33,7 @@ import ( func Test_userSBOMConfigToFamiliesSbomConfig(t *testing.T) { type args struct { - sbomConfig *models.SBOMConfig - scanRootDirectory string + sbomConfig *models.SBOMConfig } type returns struct { config familiesSbom.Config @@ -48,8 +46,7 @@ func Test_userSBOMConfigToFamiliesSbomConfig(t *testing.T) { { name: "No SBOM Config", args: args{ - sbomConfig: nil, - scanRootDirectory: "/test", + sbomConfig: nil, }, want: returns{ config: familiesSbom.Config{}, @@ -58,8 +55,7 @@ func Test_userSBOMConfigToFamiliesSbomConfig(t *testing.T) { { name: "Missing Enabled", args: args{ - sbomConfig: &models.SBOMConfig{}, - scanRootDirectory: "/test", + sbomConfig: &models.SBOMConfig{}, }, want: returns{ config: familiesSbom.Config{}, @@ -71,7 +67,6 @@ func Test_userSBOMConfigToFamiliesSbomConfig(t *testing.T) { sbomConfig: &models.SBOMConfig{ Enabled: utils.BoolPtr(false), }, - scanRootDirectory: "/test", }, want: returns{ config: familiesSbom.Config{}, @@ -83,18 +78,11 @@ func Test_userSBOMConfigToFamiliesSbomConfig(t *testing.T) { sbomConfig: &models.SBOMConfig{ Enabled: utils.BoolPtr(true), }, - scanRootDirectory: "/test", }, want: returns{ config: familiesSbom.Config{ Enabled: true, AnalyzersList: []string{"syft", "trivy"}, - Inputs: []familiesSbom.Input{ - { - Input: "/test", - InputType: string(kubeclarityUtils.ROOTFS), - }, - }, AnalyzersConfig: &kubeclarityConfig.Config{ Registry: &kubeclarityConfig.Registry{}, Analyzer: &kubeclarityConfig.Analyzer{ @@ -111,7 +99,7 @@ func Test_userSBOMConfigToFamiliesSbomConfig(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := userSBOMConfigToFamiliesSbomConfig(tt.args.sbomConfig, tt.args.scanRootDirectory) + got := userSBOMConfigToFamiliesSbomConfig(tt.args.sbomConfig) if diff := cmp.Diff(tt.want.config, got); diff != "" { t.Errorf("userSBOMConfigToFamiliesSbomConfig() mismatch (-want +got):\n%s", diff) } @@ -171,8 +159,7 @@ func Test_userVulnConfigToFamiliesVulnConfig(t *testing.T) { config: familiesVulnerabilities.Config{ Enabled: true, // TODO(sambetts) This choice should come from the user's configuration - ScannersList: []string{"grype", "trivy"}, - InputFromSbom: true, + ScannersList: []string{"grype", "trivy"}, ScannersConfig: &kubeclarityConfig.Config{ // TODO(sambetts) The user needs to be able to provide this configuration Registry: &kubeclarityConfig.Registry{}, @@ -210,7 +197,6 @@ func Test_userVulnConfigToFamiliesVulnConfig(t *testing.T) { func Test_userSecretsConfigToFamiliesSecretsConfig(t *testing.T) { type args struct { secretsConfig *models.SecretsConfig - scanRootDirectory string gitleaksBinaryPath string } tests := []struct { @@ -221,8 +207,7 @@ func Test_userSecretsConfigToFamiliesSecretsConfig(t *testing.T) { { name: "no config", args: args{ - secretsConfig: nil, - scanRootDirectory: "", + secretsConfig: nil, }, want: secrets.Config{ Enabled: false, @@ -234,7 +219,6 @@ func Test_userSecretsConfigToFamiliesSecretsConfig(t *testing.T) { secretsConfig: &models.SecretsConfig{ Enabled: nil, }, - scanRootDirectory: "", }, want: secrets.Config{ Enabled: false, @@ -246,7 +230,6 @@ func Test_userSecretsConfigToFamiliesSecretsConfig(t *testing.T) { secretsConfig: &models.SecretsConfig{ Enabled: utils.BoolPtr(false), }, - scanRootDirectory: "", }, want: secrets.Config{ Enabled: false, @@ -258,18 +241,11 @@ func Test_userSecretsConfigToFamiliesSecretsConfig(t *testing.T) { secretsConfig: &models.SecretsConfig{ Enabled: utils.BoolPtr(true), }, - scanRootDirectory: "/scanRootDirectory", gitleaksBinaryPath: "gitleaksBinaryPath", }, want: secrets.Config{ Enabled: true, ScannersList: []string{"gitleaks"}, - Inputs: []secrets.Input{ - { - Input: "/scanRootDirectory", - InputType: string(kubeclarityUtils.DIR), - }, - }, ScannersConfig: &common.ScannersConfig{ Gitleaks: gitleaksconfig.Config{ BinaryPath: "gitleaksBinaryPath", @@ -280,7 +256,7 @@ func Test_userSecretsConfigToFamiliesSecretsConfig(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := userSecretsConfigToFamiliesSecretsConfig(tt.args.secretsConfig, tt.args.scanRootDirectory, tt.args.gitleaksBinaryPath) + got := userSecretsConfigToFamiliesSecretsConfig(tt.args.secretsConfig, tt.args.gitleaksBinaryPath) if diff := cmp.Diff(tt.want, got); diff != "" { t.Errorf("userSecretsConfigToFamiliesSecretsConfig() mismatch (-want +got):\n%s", diff) }