qemu: add `args_allowlist` to sandbox VM command line inputs
The QEMU driver allows arbitrary command line options, but many of these options give access to host resources that operators may not want to expose such as devices. Add an optional allowlist to the plugin configuration so that operators can limit the resources for QEMU.
This commit is contained in:
parent
373243e289
commit
fc1d4814d9
|
@ -0,0 +1,3 @@
|
|||
```release-note:security
|
||||
Allow limiting QEMU arguments to reduce access to host resources. [CVE-2021-43415](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-43415)
|
||||
```
|
|
@ -84,7 +84,8 @@ var (
|
|||
|
||||
// configSpec is the hcl specification returned by the ConfigSchema RPC
|
||||
configSpec = hclspec.NewObject(map[string]*hclspec.Spec{
|
||||
"image_paths": hclspec.NewAttr("image_paths", "list(string)", false),
|
||||
"image_paths": hclspec.NewAttr("image_paths", "list(string)", false),
|
||||
"args_allowlist": hclspec.NewAttr("args_allowlist", "list(string)", false),
|
||||
})
|
||||
|
||||
// taskConfigSpec is the hcl specification for the driver config section of
|
||||
|
@ -136,6 +137,11 @@ type TaskState struct {
|
|||
type Config struct {
|
||||
// ImagePaths is an allow-list of paths qemu is allowed to load an image from
|
||||
ImagePaths []string `codec:"image_paths"`
|
||||
|
||||
// ArgsAllowList is an allow-list of arguments the jobspec can
|
||||
// include in arguments to qemu, so that cluster operators can can
|
||||
// prevent access to devices
|
||||
ArgsAllowList []string `codec:"args_allowlist"`
|
||||
}
|
||||
|
||||
// Driver is a driver for running images via Qemu
|
||||
|
@ -338,6 +344,26 @@ func isAllowedImagePath(allowedPaths []string, allocDir, imagePath string) bool
|
|||
return false
|
||||
}
|
||||
|
||||
// validateArgs ensures that all QEMU command line params are in the
|
||||
// allowlist. This function must be called after all interpolation has
|
||||
// taken place.
|
||||
func validateArgs(pluginConfigAllowList, args []string) error {
|
||||
if len(pluginConfigAllowList) > 0 {
|
||||
allowed := map[string]struct{}{}
|
||||
for _, arg := range pluginConfigAllowList {
|
||||
allowed[arg] = struct{}{}
|
||||
}
|
||||
for _, arg := range args {
|
||||
if strings.HasPrefix(strings.TrimSpace(arg), "-") {
|
||||
if _, ok := allowed[arg]; !ok {
|
||||
return fmt.Errorf("%q is not in args_allowlist", arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drivers.DriverNetwork, error) {
|
||||
if _, ok := d.tasks.Get(cfg.ID); ok {
|
||||
return nil, nil, fmt.Errorf("taskConfig with ID '%s' already started", cfg.ID)
|
||||
|
@ -355,6 +381,10 @@ func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drive
|
|||
handle := drivers.NewTaskHandle(taskHandleVersion)
|
||||
handle.Config = cfg
|
||||
|
||||
if err := validateArgs(d.config.ArgsAllowList, driverConfig.Args); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Get the image source
|
||||
vmPath := driverConfig.ImagePath
|
||||
if vmPath == "" {
|
||||
|
|
|
@ -453,3 +453,32 @@ func TestIsAllowedImagePath(t *testing.T) {
|
|||
require.Falsef(t, isAllowedImagePath(allowedPaths, allocDir, p), "path should be not allowed: %v", p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArgsAllowList(t *testing.T) {
|
||||
|
||||
pluginConfigAllowList := []string{"-drive", "-net", "-snapshot"}
|
||||
|
||||
validArgs := [][]string{
|
||||
{"-drive", "/path/to/wherever", "-snapshot"},
|
||||
{"-net", "tap,vlan=0,ifname=tap0"},
|
||||
}
|
||||
|
||||
invalidArgs := [][]string{
|
||||
{"-usbdevice", "mouse"},
|
||||
{"-singlestep"},
|
||||
{"--singlestep"},
|
||||
{" -singlestep"},
|
||||
{"\t-singlestep"},
|
||||
}
|
||||
|
||||
for _, args := range validArgs {
|
||||
require.NoError(t, validateArgs(pluginConfigAllowList, args))
|
||||
require.NoError(t, validateArgs([]string{}, args))
|
||||
|
||||
}
|
||||
for _, args := range invalidArgs {
|
||||
require.Error(t, validateArgs(pluginConfigAllowList, args))
|
||||
require.NoError(t, validateArgs([]string{}, args))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ The `qemu` driver supports the following configuration in the job spec:
|
|||
forcefully terminated. (Note that
|
||||
[prior to qemu 2.10.1](https://github.com/qemu/qemu/commit/ad9579aaa16d5b385922d49edac2c96c79bcfb6),
|
||||
the monitor socket path is limited to 108 characters. Graceful shutdown will
|
||||
be disabled if qemu is < 2.10.1 and the generated monitor path exceeds this
|
||||
be disabled if QEMU is < 2.10.1 and the generated monitor path exceeds this
|
||||
length. You may encounter this issue if you set long
|
||||
[data_dir](/docs/configuration#data_dir)
|
||||
or
|
||||
|
@ -72,7 +72,7 @@ The `qemu` driver supports the following configuration in the job spec:
|
|||
}
|
||||
```
|
||||
|
||||
- `args` - (Optional) A list of strings that is passed to qemu as command line
|
||||
- `args` - (Optional) A list of strings that is passed to QEMU as command line
|
||||
options.
|
||||
|
||||
## Examples
|
||||
|
@ -145,12 +145,19 @@ job "docs" {
|
|||
plugin "qemu" {
|
||||
config {
|
||||
image_paths = ["/mnt/image/paths"]
|
||||
args_allowlist = ["-drive", "-usbdevice"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `image_paths` (`[]string`: `[]`) - Specifies the host paths the QEMU driver is
|
||||
allowed to load images from.
|
||||
- `image_paths` (`[]string`: `[]`) - Specifies the host paths the QEMU
|
||||
driver is allowed to load images from.
|
||||
- `args_allowlist` (`[]string`: `[]`) - Specifies the command line
|
||||
flags that the [`args`] option is permitted to pass to QEMU. If
|
||||
unset, a job submitter can pass any command line flag into QEMU,
|
||||
including flags that provide the VM with access to host devices such
|
||||
as USB drives. Refer to the [QEMU documentation] for the available
|
||||
flags.
|
||||
|
||||
## Resource Isolation
|
||||
|
||||
|
@ -162,3 +169,11 @@ Virtualization provides the highest level of isolation for workloads that
|
|||
require additional security, and resource use is constrained by the QEMU
|
||||
hypervisor rather than the host kernel. VM network traffic still flows through
|
||||
the host's interface(s).
|
||||
|
||||
Note that the strong isolation provided by virtualization only applies
|
||||
to the workload once the VM is started. Operators should use the
|
||||
`args_allowlist` option to prevent job submitters from accessing
|
||||
devices and resources they are not allowed to access.
|
||||
|
||||
[`args`]: /docs/drivers/qemu#args
|
||||
[QEMU documentation]: https://www.qemu.org/docs/master/system/invocation.html
|
||||
|
|
Loading…
Reference in New Issue