Skip to content

Commit

Permalink
update vm service to support secure bootstrapping
Browse files Browse the repository at this point in the history
  • Loading branch information
shysank committed Mar 23, 2022
1 parent 6b5f320 commit e461d64
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 22 deletions.
3 changes: 3 additions & 0 deletions azure/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,7 @@ const (
// See https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
// for annotation formatting rules.
RGTagsLastAppliedAnnotation = "sigs.k8s.io/cluster-api-provider-azure-last-applied-tags-rg"

// Cloudinit represents cloudinit instance initializer.
Cloudinit InstanceInitializer = "Cloudinit"
)
4 changes: 4 additions & 0 deletions azure/scope/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,15 @@ func (m *MachineScope) VMSpec() azure.ResourceSpecGetter {
SecurityProfile: m.AzureMachine.Spec.SecurityProfile,
AdditionalTags: m.AdditionalTags(),
ProviderID: m.ProviderID(),
SubscriptionID: m.SubscriptionID(),
SecureBootstrapEnabled: m.SecureBootstrapEnabled(),
Initializer: azure.Cloudinit,
}
if m.cache != nil {
spec.SKU = m.cache.VMSKU
spec.Image = m.cache.VMImage
spec.BootstrapData = m.cache.BootstrapData
spec.BootstrapDataCompressed = m.cache.BootstrapDataCompressed
}
return spec
}
Expand Down
83 changes: 61 additions & 22 deletions azure/services/virtualmachines/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,33 +26,38 @@ import (
infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
"sigs.k8s.io/cluster-api-provider-azure/azure"
"sigs.k8s.io/cluster-api-provider-azure/azure/converters"
"sigs.k8s.io/cluster-api-provider-azure/azure/services/cloudinit"
"sigs.k8s.io/cluster-api-provider-azure/azure/services/resourceskus"
"sigs.k8s.io/cluster-api-provider-azure/util/generators"
)

// VMSpec defines the specification for a Virtual Machine.
type VMSpec struct {
Name string
ResourceGroup string
Location string
ClusterName string
Role string
NICIDs []string
SSHKeyData string
Size string
AvailabilitySetID string
Zone string
Identity infrav1.VMIdentity
OSDisk infrav1.OSDisk
DataDisks []infrav1.DataDisk
UserAssignedIdentities []infrav1.UserAssignedIdentity
SpotVMOptions *infrav1.SpotVMOptions
SecurityProfile *infrav1.SecurityProfile
AdditionalTags infrav1.Tags
SKU resourceskus.SKU
Image *infrav1.Image
BootstrapData string
ProviderID string
Name string
ResourceGroup string
Location string
ClusterName string
Role string
NICIDs []string
SSHKeyData string
Size string
AvailabilitySetID string
Zone string
Identity infrav1.VMIdentity
OSDisk infrav1.OSDisk
DataDisks []infrav1.DataDisk
UserAssignedIdentities []infrav1.UserAssignedIdentity
SpotVMOptions *infrav1.SpotVMOptions
SecurityProfile *infrav1.SecurityProfile
AdditionalTags infrav1.Tags
SKU resourceskus.SKU
Image *infrav1.Image
BootstrapData string
BootstrapDataCompressed []byte
ProviderID string
SubscriptionID string
SecureBootstrapEnabled bool
Initializer azure.InstanceInitializer
}

// ResourceName returns the name of the virtual machine.
Expand Down Expand Up @@ -240,10 +245,19 @@ func (s *VMSpec) generateOSProfile() (*compute.OSProfile, error) {
return nil, errors.Wrap(err, "failed to decode ssh public key")
}

resolver, err := s.getUserdataResolver()
if err != nil {
return nil, err
}
userData, err := resolver.ResolveUserData()
if err != nil {
return nil, err
}

osProfile := &compute.OSProfile{
ComputerName: to.StringPtr(s.Name),
AdminUsername: to.StringPtr(azure.DefaultUserName),
CustomData: to.StringPtr(s.BootstrapData),
CustomData: to.StringPtr(userData),
}

switch s.OSDisk.OSType {
Expand Down Expand Up @@ -276,6 +290,31 @@ func (s *VMSpec) generateOSProfile() (*compute.OSProfile, error) {
return osProfile, nil
}

func (s *VMSpec) getUserdataResolver() (azure.UserDataResolver, error) {
if !s.SecureBootstrapEnabled {
return cloudinit.SimpleUserDataResolver{
Data: s.BootstrapData,
}, nil
}

if s.Identity != infrav1.VMIdentityUserAssigned || len(s.UserAssignedIdentities) == 0 {
return nil, errors.Errorf("secure bootstrap cannot be used with identity of type %q, "+
"only UserAssigned identity is allowed", s.Identity)
}

switch s.Initializer {
case azure.Cloudinit:
return cloudinit.SecureUserDataResolver{
Data: s.BootstrapDataCompressed,
Identity: s.UserAssignedIdentities[0].ProviderID,
ClusterName: s.ClusterName,
MachineName: s.Name,
}, nil
default:
return nil, errors.Errorf("unsupported cloud instance initializer %s", s.Initializer)
}
}

func (s *VMSpec) generateSecurityProfile() (*compute.SecurityProfile, error) {
if s.SecurityProfile == nil {
return nil, nil
Expand Down
47 changes: 47 additions & 0 deletions azure/services/virtualmachines/spec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,53 @@ func TestParameters(t *testing.T) {
},
expectedError: "reconcile error that cannot be recovered occurred: vm size Standard_D2v3 does not support ultra disks in location test-location. select a different vm size or disable ultra disks. Object will not be requeued",
},
{
name: "can create a vm with secure bootstrap enabled",
spec: &VMSpec{
Name: "my-vm",
Role: infrav1.Node,
NICIDs: []string{"my-nic"},
SSHKeyData: "fakesshpublickey",
Size: "Standard_D2v3",
Zone: "1",
Image: &infrav1.Image{ID: to.StringPtr("fake-image-id")},
Identity: infrav1.VMIdentityUserAssigned,
UserAssignedIdentities: []infrav1.UserAssignedIdentity{{ProviderID: "my-user-id"}},
SKU: validSKU,
SecureBootstrapEnabled: true,
BootstrapDataCompressed: []byte("bootstrap data compressed which will be stored in a secret"),
Initializer: azure.Cloudinit,
},
existing: nil,
expect: func(g *WithT, result interface{}) {
g.Expect(result).To(BeAssignableToTypeOf(compute.VirtualMachine{}))
g.Expect(result.(compute.VirtualMachine).Identity.Type).To(Equal(compute.ResourceIdentityTypeUserAssigned))
g.Expect(result.(compute.VirtualMachine).Identity.UserAssignedIdentities).To(Equal(map[string]*compute.VirtualMachineIdentityUserAssignedIdentitiesValue{"my-user-id": {}}))
},
expectedError: "",
},
{
name: "cannot create a vm with secure bootstrap enabled if user identity is not used",
spec: &VMSpec{
Name: "my-vm",
Role: infrav1.Node,
NICIDs: []string{"my-nic"},
SSHKeyData: "fakesshpublickey",
Size: "Standard_D2v3",
Zone: "1",
Image: &infrav1.Image{ID: to.StringPtr("fake-image-id")},
Identity: infrav1.VMIdentitySystemAssigned,
SKU: validSKU,
SecureBootstrapEnabled: true,
BootstrapDataCompressed: []byte("bootstrap data compressed which will be stored in a secret"),
Initializer: azure.Cloudinit,
},
existing: nil,
expect: func(g *WithT, result interface{}) {
g.Expect(result).To(BeNil())
},
expectedError: "failed to generate OS Profile: secure bootstrap cannot be used with identity of type \"SystemAssigned\", only UserAssigned identity is allowed",
},
}
for _, tc := range testcases {
tc := tc
Expand Down
3 changes: 3 additions & 0 deletions azure/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,3 +365,6 @@ type AgentPoolSpec struct {
// OsDiskType specifies the OS disk type for each node in the pool. Allowed values are 'Ephemeral' and 'Managed'.
OsDiskType *string `json:"osDiskType,omitempty"`
}

// InstanceInitializer represents the type of initializer used to initialize VMs.
type InstanceInitializer string

0 comments on commit e461d64

Please sign in to comment.