Skip to content

Commit

Permalink
Adds inline sysprep support
Browse files Browse the repository at this point in the history
This patch adds support for inline sysprep which was introduced in the
API in v1alpha2. For the secrets, we are currently decoding them and
adding the data to the Data field under BootstrapArgs. There could be a
situiation wherein 2 or more secrets are storing data under the same key
which will cause the secret values to be overidden.

This patch adds tests for the changes to enable inline sysprep.
It also reads the secret data into distinct fields to avoid the
overwriting of values if the selector key for the fields in UserData,
Identification anf GUIAttended are the same.

Instead of waiting till doBootstrap to pull the secret data for inline
sysprep, this patch adds the secret data to the BootstrapArgs earlier
for verification purposes.

Signed-off-by: Sagar Muchhal <[email protected]>
  • Loading branch information
srm09 committed Dec 14, 2023
1 parent 5477585 commit bfc85cb
Show file tree
Hide file tree
Showing 24 changed files with 978 additions and 84 deletions.
3 changes: 3 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,6 @@ issues:
text: ".* `ctx` is unused"
- path: pkg/vmprovider/providers/vsphere/internal/internal.go
text: ".*ST1003|don\'t use underscores in Go names.*"
- path: _test.go
linters:
- gosec
21 changes: 15 additions & 6 deletions api/v1alpha2/sysprep/sysprep.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ type Sysprep struct {
GUIRunOnce GUIRunOnce `json:"guiRunOnce,omitempty"`

// GUIUnattended is a representation of the Sysprep GUIUnattended key.
GUIUnattended GUIUnattended `json:"guiUnattended"`
GUIUnattended *GUIUnattended `json:"guiUnattended"`

// Identification is a representation of the Sysprep Identification key.
Identification Identification `json:"identification"`
Identification *Identification `json:"identification"`

// LicenseFilePrintData is a representation of the Sysprep
// LicenseFilePrintData key.
Expand All @@ -40,7 +40,7 @@ type Sysprep struct {
LicenseFilePrintData *LicenseFilePrintData `json:"licenseFilePrintData,omitempty"`

// UserData is a representation of the Sysprep UserData key.
UserData UserData `json:"userData"`
UserData *UserData `json:"userData"`
}

// GUIRunOnce maps to the GuiRunOnce key in the sysprep.xml answer file.
Expand All @@ -55,10 +55,10 @@ type GUIRunOnce struct {
// GUIUnattended maps to the GuiUnattended key in the sysprep.xml answer file.
type GUIUnattended struct {

// AutoLogon determine whether or not the machine automatically logs on as
// AutoLogon determine whether the machine automatically logs on as
// Administrator.
//
// Please note if AutoLogin is true, then Password must be set or guest
// Please note if AutoLogon is true, then Password must be set or guest
// customization will fail.
//
// +optional
Expand All @@ -71,7 +71,7 @@ type GUIUnattended struct {
// you may want to increase it. This number may be determined by the list of
// commands executed by the GuiRunOnce command.
//
// Please note this field only matters if AutoLogin is true.
// Please note this field only matters if AutoLogon is true.
//
// +optional
AutoLogonCount int32 `json:"autoLogonCount,omitempty"`
Expand All @@ -90,6 +90,9 @@ type GUIUnattended struct {
// plainText attribute to true, so that the customization process does not
// attempt to decrypt the string.
//
// When not explicitly specified, the Key field for the selector defaults to
// `password`.
//
// +optional
Password *common.SecretKeySelector `json:"password,omitempty"`

Expand Down Expand Up @@ -117,6 +120,9 @@ type Identification struct {
// DomainAdminPassword is the password for the domain user account used for
// authentication if the virtual machine is joining a domain.
//
// When not explicitly specified, the Key field for the selector defaults to
// `domain_admin_password`.
//
// +optional
DomainAdminPassword *common.SecretKeySelector `json:"domainAdminPassword,omitempty"`

Expand Down Expand Up @@ -188,6 +194,9 @@ type UserData struct {
// Please note unless the VirtualMachineImage was installed with a volume
// license key, ProductID must be set or guest customization will fail.
//
// When not explicitly specified, the Key field for the selector defaults to
// `domain_admin_password`.
//
// +optional
ProductID *common.SecretKeySelector `json:"productID,omitempty"`
}
18 changes: 15 additions & 3 deletions api/v1alpha2/sysprep/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 16 additions & 10 deletions config/crd/bases/vmoperator.vmware.com_virtualmachines.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1085,10 +1085,10 @@ spec:
Sysprep GUIUnattended key.
properties:
autoLogon:
description: "AutoLogon determine whether or not the
machine automatically logs on as Administrator.
\n Please note if AutoLogin is true, then Password
must be set or guest customization will fail."
description: "AutoLogon determine whether the machine
automatically logs on as Administrator. \n Please
note if AutoLogon is true, then Password must be
set or guest customization will fail."
type: boolean
autoLogonCount:
description: "AutoLogonCount specifies the number
Expand All @@ -1098,7 +1098,7 @@ spec:
may want to increase it. This number may be determined
by the list of commands executed by the GuiRunOnce
command. \n Please note this field only matters
if AutoLogin is true."
if AutoLogon is true."
format: int32
type: integer
password:
Expand All @@ -1113,7 +1113,9 @@ spec:
Wizard, then the password is encrypted. Otherwise,
the client should set the plainText attribute to
true, so that the customization process does not
attempt to decrypt the string."
attempt to decrypt the string. \n When not explicitly
specified, the Key field for the selector defaults
to `password`."
properties:
key:
description: Key is the key in the secret that
Expand Down Expand Up @@ -1146,9 +1148,11 @@ spec:
domain.
type: string
domainAdminPassword:
description: DomainAdminPassword is the password for
the domain user account used for authentication
if the virtual machine is joining a domain.
description: "DomainAdminPassword is the password
for the domain user account used for authentication
if the virtual machine is joining a domain. \n When
not explicitly specified, the Key field for the
selector defaults to `domain_admin_password`."
properties:
key:
description: Key is the key in the secret that
Expand Down Expand Up @@ -1212,7 +1216,9 @@ spec:
description: "ProductID is a valid serial number.
\n Please note unless the VirtualMachineImage was
installed with a volume license key, ProductID must
be set or guest customization will fail."
be set or guest customization will fail. \n When
not explicitly specified, the Key field for the
selector defaults to `domain_admin_password`."
properties:
key:
description: Key is the key in the secret that
Expand Down
4 changes: 2 additions & 2 deletions pkg/util/enc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ var _ = Describe("TryToDecodeBase64Gzip", func() {
gz := func(data []byte) []byte {
var w bytes.Buffer
gzw := gzip.NewWriter(&w)
//nolint:errcheck,gosec
//nolint:errcheck
gzw.Write(data)
//nolint:errcheck,gosec
//nolint:errcheck
gzw.Close()
return w.Bytes()
}
Expand Down
45 changes: 45 additions & 0 deletions pkg/util/secret.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) 2023 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package util

import (
"context"
"fmt"

corev1 "k8s.io/api/core/v1"
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
)

func GetSecretData(
ctx context.Context,
k8sClient ctrlclient.Client,
secretNamespace, secretName, secretKey string,
out *string) error {

secret, err := GetSecretResource(ctx, k8sClient, secretNamespace, secretName)
if err != nil {
return err
}
data := secret.Data[secretKey]
if len(data) == 0 {
return fmt.Errorf(
"no data found for key %q for secret %s/%s",
secretKey, secretNamespace, secretName)
}
*out = string(data)
return nil
}

func GetSecretResource(
ctx context.Context,
k8sClient ctrlclient.Client,
secretNamespace, secretName string) (*corev1.Secret, error) {

var secret corev1.Secret
key := ctrlclient.ObjectKey{Name: secretName, Namespace: secretNamespace}
if err := k8sClient.Get(ctx, key, &secret); err != nil {
return nil, err
}
return &secret, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -798,7 +798,6 @@ data:
nicMacAddr: "{{ V1alpha1_FirstNicMacAddr }}"
`

//nolint:gosec
const testSecretYAML1 = `
apiVersion: v1
kind: Secret
Expand Down
2 changes: 1 addition & 1 deletion pkg/vmprovider/providers/vsphere/vmprovider_vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1316,7 +1316,7 @@ func vmTests() {
})

It("creates VM in assigned zone", func() {
azName := ctx.ZoneNames[rand.Intn(len(ctx.ZoneNames))] //nolint:gosec
azName := ctx.ZoneNames[rand.Intn(len(ctx.ZoneNames))]
vm.Labels[topology.KubernetesTopologyZoneLabelKey] = azName

vcVM, err := createOrUpdateAndGetVcVM(ctx, vm)
Expand Down
7 changes: 4 additions & 3 deletions pkg/vmprovider/providers/vsphere2/cloudinit/cloudconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"github.com/vmware-tanzu/vm-operator/api/v1alpha2/cloudinit"
"github.com/vmware-tanzu/vm-operator/api/v1alpha2/common"
"github.com/vmware-tanzu/vm-operator/pkg/util"
)

// cloudConfig provides support for marshalling the object to a valid
Expand Down Expand Up @@ -246,7 +247,7 @@ func GetSecretResources(

for i := range in.Users {
if v := in.Users[i].HashedPasswd; v != nil {
s, err := getSecretResource(
s, err := util.GetSecretResource(
ctx,
k8sClient,
secretNamespace,
Expand All @@ -257,7 +258,7 @@ func GetSecretResources(
captureSecret(s, v.Name)
}
if v := in.Users[i].Passwd; v != nil {
s, err := getSecretResource(
s, err := util.GetSecretResource(
ctx,
k8sClient,
secretNamespace,
Expand All @@ -273,7 +274,7 @@ func GetSecretResources(
if v := in.WriteFiles[i].Content; len(v) > 0 {
var sks common.SecretKeySelector
if err := yaml.Unmarshal(v, &sks); err == nil {
s, err := getSecretResource(
s, err := util.GetSecretResource(
ctx,
k8sClient,
secretNamespace,
Expand Down
41 changes: 4 additions & 37 deletions pkg/vmprovider/providers/vsphere2/cloudinit/cloudconfig_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import (
"fmt"

"gopkg.in/yaml.v3"
corev1 "k8s.io/api/core/v1"
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"

"github.com/vmware-tanzu/vm-operator/api/v1alpha2/cloudinit"
"github.com/vmware-tanzu/vm-operator/api/v1alpha2/common"
"github.com/vmware-tanzu/vm-operator/pkg/util"
)

// CloudConfigSecretData is used to provide the sensitive data that may have
Expand Down Expand Up @@ -93,7 +93,7 @@ func getSecretDataForUser(
}

if v := in.HashedPasswd; v != nil {
if err := getSecretData(
if err := util.GetSecretData(
ctx, k8sClient,
secretNamespace, v.Name, v.Key,
&out.HashPasswd); err != nil {
Expand All @@ -102,7 +102,7 @@ func getSecretDataForUser(
}
}
if v := in.Passwd; v != nil {
if err := getSecretData(
if err := util.GetSecretData(
ctx, k8sClient,
secretNamespace, v.Name, v.Key,
&out.Passwd); err != nil {
Expand Down Expand Up @@ -140,7 +140,7 @@ func getSecretDataForWriteFile(
return err
}

if err := getSecretData(
if err := util.GetSecretData(
ctx,
k8sClient,
secretNamespace,
Expand All @@ -152,36 +152,3 @@ func getSecretDataForWriteFile(

return nil
}

func getSecretData(
ctx context.Context,
k8sClient ctrlclient.Client,
secretNamespace, secretName, secretKey string,
out *string) error {

secret, err := getSecretResource(ctx, k8sClient, secretNamespace, secretName)
if err != nil {
return err
}
data := secret.Data[secretKey]
if len(data) == 0 {
return fmt.Errorf(
"no data found for key %q for secret %s/%s",
secretKey, secretNamespace, secretName)
}
*out = string(data)
return nil
}

func getSecretResource(
ctx context.Context,
k8sClient ctrlclient.Client,
secretNamespace, secretName string) (*corev1.Secret, error) {

var secret corev1.Secret
key := ctrlclient.ObjectKey{Name: secretName, Namespace: secretNamespace}
if err := k8sClient.Get(ctx, key, &secret); err != nil {
return nil, err
}
return &secret, nil
}
Loading

0 comments on commit bfc85cb

Please sign in to comment.