diff --git a/builder/proxmox/clone/config.hcl2spec.go b/builder/proxmox/clone/config.hcl2spec.go index 781e4a65..10fe821a 100644 --- a/builder/proxmox/clone/config.hcl2spec.go +++ b/builder/proxmox/clone/config.hcl2spec.go @@ -99,6 +99,7 @@ type FlatConfig struct { EFIConfig *proxmox.FlatefiConfig `mapstructure:"efi_config" cty:"efi_config" hcl:"efi_config"` EFIDisk *string `mapstructure:"efidisk" cty:"efidisk" hcl:"efidisk"` Machine *string `mapstructure:"machine" cty:"machine" hcl:"machine"` + Rng0 *proxmox.Flatrng0Config `mapstructure:"rng0" cty:"rng0" hcl:"rng0"` VGA *proxmox.FlatvgaConfig `mapstructure:"vga" cty:"vga" hcl:"vga"` NICs []proxmox.FlatNICConfig `mapstructure:"network_adapters" cty:"network_adapters" hcl:"network_adapters"` Disks []proxmox.FlatdiskConfig `mapstructure:"disks" cty:"disks" hcl:"disks"` @@ -221,6 +222,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "efi_config": &hcldec.BlockSpec{TypeName: "efi_config", Nested: hcldec.ObjectSpec((*proxmox.FlatefiConfig)(nil).HCL2Spec())}, "efidisk": &hcldec.AttrSpec{Name: "efidisk", Type: cty.String, Required: false}, "machine": &hcldec.AttrSpec{Name: "machine", Type: cty.String, Required: false}, + "rng0": &hcldec.BlockSpec{TypeName: "rng0", Nested: hcldec.ObjectSpec((*proxmox.Flatrng0Config)(nil).HCL2Spec())}, "vga": &hcldec.BlockSpec{TypeName: "vga", Nested: hcldec.ObjectSpec((*proxmox.FlatvgaConfig)(nil).HCL2Spec())}, "network_adapters": &hcldec.BlockListSpec{TypeName: "network_adapters", Nested: hcldec.ObjectSpec((*proxmox.FlatNICConfig)(nil).HCL2Spec())}, "disks": &hcldec.BlockListSpec{TypeName: "disks", Nested: hcldec.ObjectSpec((*proxmox.FlatdiskConfig)(nil).HCL2Spec())}, diff --git a/builder/proxmox/common/config.go b/builder/proxmox/common/config.go index c22fcca1..b7975b01 100644 --- a/builder/proxmox/common/config.go +++ b/builder/proxmox/common/config.go @@ -2,7 +2,7 @@ // SPDX-License-Identifier: MPL-2.0 //go:generate packer-sdc struct-markdown -//go:generate packer-sdc mapstructure-to-hcl2 -type Config,NICConfig,diskConfig,vgaConfig,additionalISOsConfig,efiConfig +//go:generate packer-sdc mapstructure-to-hcl2 -type Config,NICConfig,diskConfig,rng0Config,vgaConfig,additionalISOsConfig,efiConfig package proxmox @@ -59,6 +59,7 @@ type Config struct { EFIConfig efiConfig `mapstructure:"efi_config"` EFIDisk string `mapstructure:"efidisk"` Machine string `mapstructure:"machine"` + Rng0 rng0Config `mapstructure:"rng0"` VGA vgaConfig `mapstructure:"vga"` NICs []NICConfig `mapstructure:"network_adapters"` Disks []diskConfig `mapstructure:"disks"` @@ -114,6 +115,50 @@ type efiConfig struct { PreEnrolledKeys bool `mapstructure:"pre_enrolled_keys"` EFIType string `mapstructure:"efi_type"` } + +// - `rng0` (object): Configure Random Number Generator via VirtIO. +// A virtual hardware-RNG can be used to provide entropy from the host system to a guest VM helping avoid entropy starvation which might cause the guest system slow down. +// The device is sourced from a host device and guest, his use can be limited: `max_bytes` bytes of data will become available on a `period` ms timer. +// [PVE documentation](https://pve.proxmox.com/pve-docs/pve-admin-guide.html) recommends to always use a limiter to avoid guests using too many host resources. +// +// HCL2 example: +// +// ```hcl +// rng0 { +// source = "/dev/urandom" +// max_bytes = 1024 +// period = 1000 +// } +// ``` +// +// JSON example: +// +// ```json +// { +// "rng0": { +// "source": "/dev/urandom", +// "max_bytes": 1024, +// "period": 1000 +// } +// } +// ``` +type rng0Config struct { + // Device on the host to gather entropy from. + // `/dev/urandom` should be preferred over `/dev/random` as Proxmox PVE documentation suggests. + // `/dev/hwrng` can be used to pass through a hardware RNG. + // Can be one of `/dev/urandom`, `/dev/random`, `/dev/hwrng`. + Source string `mapstructure:"source" required:"true"` + // Maximum bytes of entropy allowed to get injected into the guest every `period` milliseconds. + // Use a lower value when using `/dev/random` since can lead to entropy starvation on the host system. + // `0` disables limiting and according to PVE documentation is potentially dangerous for the host. + // Recommended value: `1024`. + MaxBytes int `mapstructure:"max_bytes" required:"true"` + // Period in milliseconds on which the the entropy-injection quota is reset. + // Can be a positive value. + // Recommended value: `1000`. + Period int `mapstructure:"period" required:"false"` +} + type vgaConfig struct { Type string `mapstructure:"type"` Memory int `mapstructure:"memory"` @@ -391,6 +436,21 @@ func (c *Config) Prepare(upper interface{}, raws ...interface{}) ([]string, []st errs = packersdk.MultiErrorAppend(errs, errors.New("efi_storage_pool not set for efi_config")) } } + if c.Rng0 != (rng0Config{}) { + if !(c.Rng0.Source == "/dev/urandom" || c.Rng0.Source == "/dev/random" || c.Rng0.Source == "/dev/hwrng") { + errs = packersdk.MultiErrorAppend(errs, errors.New("source must be one of \"/dev/urandom\", \"/dev/random\", \"/dev/hwrng\"")) + } + if c.Rng0.MaxBytes < 0 { + errs = packersdk.MultiErrorAppend(errs, errors.New("max_bytes must be greather or equal than 0")) + } else { + if c.Rng0.MaxBytes == 0 { + warnings = append(warnings, "max_bytes is 0: potentially dangerous: this disables limiting the entropy allowed to get injected into the guest") + } + } + if c.Rng0.Period < 0 { + errs = packersdk.MultiErrorAppend(errs, errors.New("period must be greather than 0")) + } + } if errs != nil && len(errs.Errors) > 0 { return nil, warnings, errs diff --git a/builder/proxmox/common/config.hcl2spec.go b/builder/proxmox/common/config.hcl2spec.go index 829029a1..e94b4cf0 100644 --- a/builder/proxmox/common/config.hcl2spec.go +++ b/builder/proxmox/common/config.hcl2spec.go @@ -98,6 +98,7 @@ type FlatConfig struct { EFIConfig *FlatefiConfig `mapstructure:"efi_config" cty:"efi_config" hcl:"efi_config"` EFIDisk *string `mapstructure:"efidisk" cty:"efidisk" hcl:"efidisk"` Machine *string `mapstructure:"machine" cty:"machine" hcl:"machine"` + Rng0 *Flatrng0Config `mapstructure:"rng0" cty:"rng0" hcl:"rng0"` VGA *FlatvgaConfig `mapstructure:"vga" cty:"vga" hcl:"vga"` NICs []FlatNICConfig `mapstructure:"network_adapters" cty:"network_adapters" hcl:"network_adapters"` Disks []FlatdiskConfig `mapstructure:"disks" cty:"disks" hcl:"disks"` @@ -214,6 +215,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "efi_config": &hcldec.BlockSpec{TypeName: "efi_config", Nested: hcldec.ObjectSpec((*FlatefiConfig)(nil).HCL2Spec())}, "efidisk": &hcldec.AttrSpec{Name: "efidisk", Type: cty.String, Required: false}, "machine": &hcldec.AttrSpec{Name: "machine", Type: cty.String, Required: false}, + "rng0": &hcldec.BlockSpec{TypeName: "rng0", Nested: hcldec.ObjectSpec((*Flatrng0Config)(nil).HCL2Spec())}, "vga": &hcldec.BlockSpec{TypeName: "vga", Nested: hcldec.ObjectSpec((*FlatvgaConfig)(nil).HCL2Spec())}, "network_adapters": &hcldec.BlockListSpec{TypeName: "network_adapters", Nested: hcldec.ObjectSpec((*FlatNICConfig)(nil).HCL2Spec())}, "disks": &hcldec.BlockListSpec{TypeName: "disks", Nested: hcldec.ObjectSpec((*FlatdiskConfig)(nil).HCL2Spec())}, @@ -374,6 +376,33 @@ func (*FlatefiConfig) HCL2Spec() map[string]hcldec.Spec { return s } +// Flatrng0Config is an auto-generated flat version of rng0Config. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type Flatrng0Config struct { + Source *string `mapstructure:"source" required:"true" cty:"source" hcl:"source"` + MaxBytes *int `mapstructure:"max_bytes" required:"true" cty:"max_bytes" hcl:"max_bytes"` + Period *int `mapstructure:"period" required:"false" cty:"period" hcl:"period"` +} + +// FlatMapstructure returns a new Flatrng0Config. +// Flatrng0Config is an auto-generated flat version of rng0Config. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*rng0Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(Flatrng0Config) +} + +// HCL2Spec returns the hcl spec of a rng0Config. +// This spec is used by HCL to read the fields of rng0Config. +// The decoded values from this spec will then be applied to a Flatrng0Config. +func (*Flatrng0Config) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "source": &hcldec.AttrSpec{Name: "source", Type: cty.String, Required: false}, + "max_bytes": &hcldec.AttrSpec{Name: "max_bytes", Type: cty.Number, Required: false}, + "period": &hcldec.AttrSpec{Name: "period", Type: cty.Number, Required: false}, + } + return s +} + // FlatvgaConfig is an auto-generated flat version of vgaConfig. // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. type FlatvgaConfig struct { diff --git a/builder/proxmox/common/config_test.go b/builder/proxmox/common/config_test.go index 855db967..d8221a73 100644 --- a/builder/proxmox/common/config_test.go +++ b/builder/proxmox/common/config_test.go @@ -246,6 +246,89 @@ func TestAdditionalISOs(t *testing.T) { } +func TestRng0(t *testing.T) { + Rng0Test := []struct { + name string + rng_config rng0Config + expectFailure bool + }{ + { + name: "no error", + expectFailure: false, + rng_config: rng0Config{ + Source: "/dev/urandom", + MaxBytes: 1024, + Period: 1000, + }, + }, + { + name: "empty Source, error", + expectFailure: true, + rng_config: rng0Config{ + Source: "", + MaxBytes: 1024, + Period: 1000, + }, + }, + { + name: "negative Period, error", + expectFailure: true, + rng_config: rng0Config{ + Source: "/dev/urandom", + MaxBytes: 1024, + Period: -10, + }, + }, + { + name: "zero Period, noerror", + expectFailure: false, + rng_config: rng0Config{ + Source: "/dev/urandom", + MaxBytes: 1024, + Period: 0, + }, + }, + { + name: "malformed Source error, error", + expectFailure: true, + rng_config: rng0Config{ + Source: "/dev/abcde", + MaxBytes: 1024, + Period: 1000, + }, + }, + { + name: "negative Period, error", + expectFailure: true, + rng_config: rng0Config{ + Source: "/dev/urandom", + MaxBytes: 1024, + Period: -10, + }, + }, + } + + for _, tt := range Rng0Test { + t.Run(tt.name, func(t *testing.T) { + cfg := mandatoryConfig(t) + cfg["rng0"] = &tt.rng_config + + var c Config + _, _, err := c.Prepare(&c, cfg) + if err != nil { + if !tt.expectFailure { + t.Fatalf("unexpected failure to prepare config: %s", err) + } + t.Logf("got expected failure: %s", err) + } + + if err == nil && tt.expectFailure { + t.Errorf("expected failure, but prepare succeeded") + } + }) + } +} + func TestSerials(t *testing.T) { serialsTest := []struct { name string diff --git a/builder/proxmox/common/step_start_vm.go b/builder/proxmox/common/step_start_vm.go index f45faf99..709a1e69 100644 --- a/builder/proxmox/common/step_start_vm.go +++ b/builder/proxmox/common/step_start_vm.go @@ -123,6 +123,7 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist Bios: c.BIOS, EFIDisk: generateProxmoxEfi(c.EFIConfig), Machine: c.Machine, + RNGDrive: generateProxmoxRng0(c.Rng0), QemuVga: generateProxmoxVga(c.VGA), QemuNetworks: generateProxmoxNetworkAdapters(c.NICs), QemuDisks: generateProxmoxDisks(c.Disks), @@ -295,6 +296,19 @@ func generateProxmoxSerials(serials []string) proxmox.QemuDevices { return devs } +func generateProxmoxRng0(rng0 rng0Config) proxmox.QemuDevice { + dev := make(proxmox.QemuDevice) + setDeviceParamIfDefined(dev, "source", rng0.Source) + + if rng0.MaxBytes >= 0 { + dev["max_bytes"] = rng0.MaxBytes + } + if rng0.Period > 0 { + dev["period"] = rng0.Period + } + return dev +} + func generateProxmoxVga(vga vgaConfig) proxmox.QemuDevice { dev := make(proxmox.QemuDevice) setDeviceParamIfDefined(dev, "type", vga.Type) diff --git a/builder/proxmox/iso/config.hcl2spec.go b/builder/proxmox/iso/config.hcl2spec.go index d0ed202d..c64c54ed 100644 --- a/builder/proxmox/iso/config.hcl2spec.go +++ b/builder/proxmox/iso/config.hcl2spec.go @@ -99,6 +99,7 @@ type FlatConfig struct { EFIConfig *proxmox.FlatefiConfig `mapstructure:"efi_config" cty:"efi_config" hcl:"efi_config"` EFIDisk *string `mapstructure:"efidisk" cty:"efidisk" hcl:"efidisk"` Machine *string `mapstructure:"machine" cty:"machine" hcl:"machine"` + Rng0 *proxmox.Flatrng0Config `mapstructure:"rng0" cty:"rng0" hcl:"rng0"` VGA *proxmox.FlatvgaConfig `mapstructure:"vga" cty:"vga" hcl:"vga"` NICs []proxmox.FlatNICConfig `mapstructure:"network_adapters" cty:"network_adapters" hcl:"network_adapters"` Disks []proxmox.FlatdiskConfig `mapstructure:"disks" cty:"disks" hcl:"disks"` @@ -224,6 +225,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "efi_config": &hcldec.BlockSpec{TypeName: "efi_config", Nested: hcldec.ObjectSpec((*proxmox.FlatefiConfig)(nil).HCL2Spec())}, "efidisk": &hcldec.AttrSpec{Name: "efidisk", Type: cty.String, Required: false}, "machine": &hcldec.AttrSpec{Name: "machine", Type: cty.String, Required: false}, + "rng0": &hcldec.BlockSpec{TypeName: "rng0", Nested: hcldec.ObjectSpec((*proxmox.Flatrng0Config)(nil).HCL2Spec())}, "vga": &hcldec.BlockSpec{TypeName: "vga", Nested: hcldec.ObjectSpec((*proxmox.FlatvgaConfig)(nil).HCL2Spec())}, "network_adapters": &hcldec.BlockListSpec{TypeName: "network_adapters", Nested: hcldec.ObjectSpec((*proxmox.FlatNICConfig)(nil).HCL2Spec())}, "disks": &hcldec.BlockListSpec{TypeName: "disks", Nested: hcldec.ObjectSpec((*proxmox.FlatdiskConfig)(nil).HCL2Spec())}, diff --git a/docs-partials/builder/proxmox/common/Config-not-required.mdx b/docs-partials/builder/proxmox/common/Config-not-required.mdx index 60e21c8d..7d43201d 100644 --- a/docs-partials/builder/proxmox/common/Config-not-required.mdx +++ b/docs-partials/builder/proxmox/common/Config-not-required.mdx @@ -44,6 +44,8 @@ - `machine` (string) - Machine +- `rng0` (rng0Config) - Rng 0 + - `vga` (vgaConfig) - VGA - `network_adapters` ([]NICConfig) - NI Cs diff --git a/docs-partials/builder/proxmox/common/rng0Config-not-required.mdx b/docs-partials/builder/proxmox/common/rng0Config-not-required.mdx new file mode 100644 index 00000000..4ab10e88 --- /dev/null +++ b/docs-partials/builder/proxmox/common/rng0Config-not-required.mdx @@ -0,0 +1,7 @@ + + +- `period` (int) - Period in milliseconds on which the the entropy-injection quota is reset. + Can be a positive value. + Recommended value: `1000`. + + diff --git a/docs-partials/builder/proxmox/common/rng0Config-required.mdx b/docs-partials/builder/proxmox/common/rng0Config-required.mdx new file mode 100644 index 00000000..a555f5cc --- /dev/null +++ b/docs-partials/builder/proxmox/common/rng0Config-required.mdx @@ -0,0 +1,13 @@ + + +- `source` (string) - Device on the host to gather entropy from. + `/dev/urandom` should be preferred over `/dev/random` as Proxmox PVE documentation suggests. + `/dev/hwrng` can be used to pass through a hardware RNG. + Can be one of `/dev/urandom`, `/dev/random`, `/dev/hwrng`. + +- `max_bytes` (int) - Maximum bytes of entropy allowed to get injected into the guest every `period` milliseconds. + Use a lower value when using `/dev/random` since can lead to entropy starvation on the host system. + `0` disables limiting and according to PVE documentation is potentially dangerous for the host. + Recommended value: `1024`. + + diff --git a/docs-partials/builder/proxmox/common/rng0Config.mdx b/docs-partials/builder/proxmox/common/rng0Config.mdx new file mode 100644 index 00000000..c5cdbbcb --- /dev/null +++ b/docs-partials/builder/proxmox/common/rng0Config.mdx @@ -0,0 +1,30 @@ + + +- `rng0` (object): Configure Random Number Generator via VirtIO. +A virtual hardware-RNG can be used to provide entropy from the host system to a guest VM helping avoid entropy starvation which might cause the guest system slow down. +The device is sourced from a host device and guest, his use can be limited: `max_bytes` bytes of data will become available on a `period` ms timer. +[PVE documentation](https://pve.proxmox.com/pve-docs/pve-admin-guide.html) recommends to always use a limiter to avoid guests using too many host resources. + +HCL2 example: + +```hcl +rng0 { + source = "/dev/urandom" + max_bytes = 1024 + period = 1000 +} +``` + +JSON example: + +```json +{ + "rng0": { + "source": "/dev/urandom", + "max_bytes": 1024, + "period": 1000 + } +} +``` + + diff --git a/docs/builders/clone.mdx b/docs/builders/clone.mdx index 69b65aec..1385a091 100644 --- a/docs/builders/clone.mdx +++ b/docs/builders/clone.mdx @@ -354,6 +354,18 @@ or responding to pattern `/dev/.+`. Example: - `machine` - (string) - Set the machine type. Supported values are 'pc' or 'q35'. +#### VirtIO RNG device + +@include 'builder/proxmox/common/rng0Config.mdx' + +##### Required: + +@include 'builder/proxmox/common/rng0Config-required.mdx' + +##### Optional: + +@include 'builder/proxmox/common/rng0Config-not-required.mdx' + ## Example: Cloud-Init enabled Debian Here is a basic example creating a Debian 10 server image. This assumes diff --git a/docs/builders/iso.mdx b/docs/builders/iso.mdx index dece584f..ae4db714 100644 --- a/docs/builders/iso.mdx +++ b/docs/builders/iso.mdx @@ -399,6 +399,18 @@ or responding to pattern `/dev/.+`. Example: - `machine` - (string) - Set the machine type. Supported values are 'pc' or 'q35'. +#### VirtIO RNG device + +@include 'builder/proxmox/common/rng0Config.mdx' + +##### Required: + +@include 'builder/proxmox/common/rng0Config-required.mdx' + +##### Optional: + +@include 'builder/proxmox/common/rng0Config-not-required.mdx' + ## Boot Command @include 'packer-plugin-sdk/bootcommand/BootConfig.mdx'