Skip to content

Commit

Permalink
feat: add support for reading OVF data on VMWare
Browse files Browse the repository at this point in the history
The OVF environment is a way to supply guestinfo to guests. It is
a datastructure (XML) put in `extraConfig` (commonly referred to as
`guestinfo`) under the key `ovfenv`.

This OVF env is said to be the proper way to supply customization data
to guests (ie, not through `extraConfig`), and on some platforms (eg,
vCD), it is even the only option.

This change also enables the actual OVF transport in the OVA.

Signed-off-by: Jorik Jonker <jorik.jonker@eu.equinix.com>
  • Loading branch information
jonkerj authored and smira committed Apr 13, 2021
1 parent 04dbafc commit 8b8542e
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 17 deletions.
2 changes: 1 addition & 1 deletion cmd/installer/pkg/ova/ova.go
Expand Up @@ -51,7 +51,7 @@ const ovfTpl = `<?xml version="1.0" encoding="UTF-8"?>
<OperatingSystemSection ovf:id="101" vmw:osType="otherLinux64Guest">
<Info>The kind of installed guest operating system</Info>
</OperatingSystemSection>
<VirtualHardwareSection>
<VirtualHardwareSection ovf:transport="com.vmware.guestInfo">
<Info>Virtual hardware requirements</Info>
<System>
<vssd:ElementName>Virtual Hardware Family</vssd:ElementName>
Expand Down
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -78,6 +78,7 @@ require (
github.com/talos-systems/talos/pkg/machinery v0.0.0-20210302191918-8ffb55943c71
github.com/u-root/u-root v7.0.0+incompatible
github.com/vmware-tanzu/sonobuoy v0.20.0
github.com/vmware/govmomi v0.24.0
github.com/vmware/vmw-guestinfo v0.0.0-20200218095840-687661b8bd8e
go.etcd.io/etcd/api/v3 v3.5.0-alpha.0
go.etcd.io/etcd/client/v3 v3.5.0-alpha.0
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Expand Up @@ -252,6 +252,7 @@ github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjI
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892/go.mod h1:CTDl0pzVzE5DEzZhPfvhY/9sPFMQIxaJ9VAMs9AagrE=
github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE=
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
Expand Down Expand Up @@ -288,6 +289,7 @@ github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkg
github.com/elazarl/goproxy v0.0.0-20190911111923-ecfe977594f1/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/elazarl/goproxy v0.0.0-20210110162100-a92cc753f88e h1:/cwV7t2xezilMljIftb7WlFtzGANRCnoOhPjtl2ifcs=
github.com/elazarl/goproxy v0.0.0-20210110162100-a92cc753f88e/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/emicklei/dot v0.15.0 h1:XDBW0Xco1QNyRb33cqLe10cT04yMWL1XpCZfa98Q6Og=
github.com/emicklei/dot v0.15.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
Expand Down Expand Up @@ -541,6 +543,7 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v0.0.0-20170306145142-6a5e28554805/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
Expand Down Expand Up @@ -1066,6 +1069,9 @@ github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3C
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vmware-tanzu/sonobuoy v0.20.0 h1:LIfAWbS9G66PFsCDjaaO2J0kQj9+ED1i/kKYLaK0uNc=
github.com/vmware-tanzu/sonobuoy v0.20.0/go.mod h1:4CnFknIEj/FI9+l1HmbblgwpTR2shupF5ZA1fLoEFVE=
github.com/vmware/govmomi v0.24.0 h1:G7YFF6unMTG3OY25Dh278fsomVTKs46m2ENlEFSbmbs=
github.com/vmware/govmomi v0.24.0/go.mod h1:Y+Wq4lst78L85Ge/F8+ORXIWiKYqaro1vhAulACy9Lc=
github.com/vmware/vmw-guestinfo v0.0.0-20170707015358-25eff159a728/go.mod h1:x9oS4Wk2s2u4tS29nEaDLdzvuHdB19CvSGJjPgkZJNk=
github.com/vmware/vmw-guestinfo v0.0.0-20200218095840-687661b8bd8e h1:In34xdQmxmPpV5YWm3D9ovtyUtkasFWYDfc3UGPdkeo=
github.com/vmware/vmw-guestinfo v0.0.0-20200218095840-687661b8bd8e/go.mod h1:/3jxIXT64LBNFMdpUk5XfFWYK40Z9+HwGH1sjilaV8Y=
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243 h1:R43TdZy32XXSXjJn7M/HhALJ9imq6ztLnChfYJpVDnM=
Expand Down
Expand Up @@ -9,12 +9,14 @@ package vmware
import (
"context"
"encoding/base64"
"encoding/xml"
"errors"
"fmt"
"log"
"net"

"github.com/talos-systems/go-procfs/procfs"
"github.com/vmware/govmomi/ovf"
"github.com/vmware/vmw-guestinfo/rpcvmx"
"github.com/vmware/vmw-guestinfo/vmcheck"

Expand All @@ -31,51 +33,135 @@ func (v *VMware) Name() string {
return "vmware"
}

// Read and de-base64 a property from `extraConfig`. This is commonly referred to as `guestinfo`.
func readConfigFromExtraConfig(extraConfig *rpcvmx.Config, key string) ([]byte, error) {
val, err := extraConfig.String(key, "")
if err != nil {
return nil, fmt.Errorf("failed to get extraConfig %s: %w", key, err)
}

if val == "" { // not present
log.Printf("Empty (thus absent) %s", key)

return nil, nil
}

decoded, err := base64.StdEncoding.DecodeString(val)
if err != nil {
return nil, fmt.Errorf("failed to decode extraConfig %s: %w", key, err)
}

return decoded, nil
}

// Read and de-base64 a property from the OVF env. This is different way to pass data to your VM.
// This is how data gets passed when using vCloud Director.
func readConfigFromOvf(extraConfig *rpcvmx.Config, key string) ([]byte, error) {
ovfXML, err := extraConfig.String(constants.VMwareGuestInfoOvfEnvKey, "")
if err != nil {
return nil, fmt.Errorf("failed to read extraConfig var '%s': %w", key, err)
}

if ovfXML == "" { // value empty (probably because not present)
return nil, nil
}

var ovfEnv ovf.Env

err = xml.Unmarshal([]byte(ovfXML), &ovfEnv)
if err != nil {
return nil, fmt.Errorf("failed to unmarshall XML from OVF env: %w", err)
}

log.Printf("searching for property '%s' in OVF", key)

for _, property := range ovfEnv.Property.Properties { // iterate to check if our key is present
if property.Key == key {
log.Printf("it is there, decoding")

decoded, err := base64.StdEncoding.DecodeString(property.Value)
if err != nil {
return nil, fmt.Errorf("failed to decode OVF property %s: %w", property.Key, err)
}

return decoded, nil
}
}

return nil, nil
}

// Configuration implements the platform.Platform interface.
//nolint:gocyclo
func (v *VMware) Configuration(context.Context) ([]byte, error) {
var option *string
if option = procfs.ProcCmdline().Get(constants.KernelParamConfig).First(); option == nil {
return nil, fmt.Errorf("%s not found", constants.KernelParamConfig)
}

if *option == constants.ConfigGuestInfo {
log.Printf("fetching machine config from: guestinfo key %q", constants.VMwareGuestInfoConfigKey)
log.Printf("fetching machine config from VMWare extraConfig or OVF env")

ok, err := vmcheck.IsVirtualWorld()
if err != nil {
return nil, err
return nil, fmt.Errorf("error checking if we are virtual: %w", err)
}

if !ok {
return nil, errors.New("not a virtual world")
}

config := rpcvmx.NewConfig()
extraConfig := rpcvmx.NewConfig()

// try to fetch `talos.config` from OVF
log.Printf("trying to find '%s' in OVF env", constants.VMwareGuestInfoConfigKey)

val, err := config.String(constants.VMwareGuestInfoConfigKey, "")
config, err := readConfigFromOvf(extraConfig, constants.VMwareGuestInfoConfigKey)
if err != nil {
return nil, fmt.Errorf("failed to get guestinfo.%s: %w", constants.VMwareGuestInfoConfigKey, err)
return nil, err
}

if val == "" {
val, err = config.String(constants.VMwareGuestInfoFallbackKey, "")
if err != nil {
return nil, fmt.Errorf("failed to get guestinfo.%s: %w", constants.VMwareGuestInfoFallbackKey, err)
}
if config != nil {
return config, nil
}

if val == "" {
log.Printf("config is required, no value found for guestinfo: %q, %q", constants.VMwareGuestInfoConfigKey, constants.VMwareGuestInfoFallbackKey)
// try to fetch `userdata` from OVF
log.Printf("trying to find '%s' in OVF env", constants.VMwareGuestInfoFallbackKey)

config, err = readConfigFromOvf(extraConfig, constants.VMwareGuestInfoFallbackKey)
if err != nil {
return nil, err
}

return nil, platformerrors.ErrNoConfigSource
if config != nil {
return config, nil
}

b, err := base64.StdEncoding.DecodeString(val)
// try to fetch `talos.config` from plain extraConfig (ie, the old behavior)
log.Printf("trying to find '%s' in extraConfig", constants.VMwareGuestInfoConfigKey)

config, err = readConfigFromExtraConfig(extraConfig, constants.VMwareGuestInfoConfigKey)
if err != nil {
return nil, fmt.Errorf("failed to decode guestinfo.%s: %w", constants.VMwareGuestInfoConfigKey, err)
return nil, err
}

if config != nil {
return config, nil
}

// try to fetch `userdata` from plain extraConfig (ie, the old behavior)
log.Printf("trying to find '%s' in extraConfig", constants.VMwareGuestInfoFallbackKey)

config, err = readConfigFromExtraConfig(extraConfig, constants.VMwareGuestInfoFallbackKey)
if err != nil {
return nil, err
}

if config != nil {
return config, nil
}

return b, nil
return nil, platformerrors.ErrNoConfigSource
}

return nil, nil
Expand Down
3 changes: 3 additions & 0 deletions pkg/machinery/constants/constants.go
Expand Up @@ -273,6 +273,9 @@ const (
// VMwareGuestInfoFallbackKey is the fallback guestinfo key used to provide a config file.
VMwareGuestInfoFallbackKey = "userdata"

// VMwareGuestInfoOvfEnvKey is the guestinfo key used to provide the OVF environment.
VMwareGuestInfoOvfEnvKey = "ovfenv"

// AuditPolicyPath is the path to the audit-policy.yaml relative to initramfs.
AuditPolicyPath = "/etc/kubernetes/audit-policy.yaml"

Expand Down

0 comments on commit 8b8542e

Please sign in to comment.