Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Secure bootstrapping for capz machines #2189

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/v1alpha3/azurecluster_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ func (src *AzureCluster) ConvertTo(dstRaw conversion.Hub) error {
// Restore list of virtual network peerings
dst.Spec.NetworkSpec.Vnet.Peerings = restored.Spec.NetworkSpec.Vnet.Peerings

dst.Spec.SecureBootstrapEnabled = restored.Spec.SecureBootstrapEnabled

return nil
}

Expand Down
1 change: 1 addition & 0 deletions api/v1alpha3/zz_generated.conversion.go

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

2 changes: 2 additions & 0 deletions api/v1alpha4/azurecluster_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ func (src *AzureCluster) ConvertTo(dstRaw conversion.Hub) error {
// Restore list of virtual network peerings
dst.Spec.NetworkSpec.Vnet.Peerings = restored.Spec.NetworkSpec.Vnet.Peerings

dst.Spec.SecureBootstrapEnabled = restored.Spec.SecureBootstrapEnabled

return nil
}

Expand Down
1 change: 1 addition & 0 deletions api/v1alpha4/zz_generated.conversion.go

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

5 changes: 5 additions & 0 deletions api/v1beta1/azurecluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ type AzureClusterSpec struct {
// this when creating an AzureCluster as CAPZ will set this for you. However, if it is set, CAPZ will not change it.
// +optional
ControlPlaneEndpoint clusterv1.APIEndpoint `json:"controlPlaneEndpoint,omitempty"`

// SecureBootstrapEnabled controls if bootstrap data for cluster machines should be secured by Azure KeyVault. When enabled,
// CAPZ stores machine bootstrap data as a secret in KeyVault, configures a cloud init boot hook script to fetch the secrets
// from KeyVault, and delete them once complete. Disabled by default.
SecureBootstrapEnabled bool `json:"secureBootstrapEnabled,omitempty"`
}

// AzureClusterStatus defines the observed state of AzureCluster.
Expand Down
4 changes: 4 additions & 0 deletions api/v1beta1/conditions_consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ const (
RoleAssignmentReadyCondition clusterv1.ConditionType = "RoleAssignmentReady"
// DisksReadyCondition means the disks exist and are ready to be used.
DisksReadyCondition clusterv1.ConditionType = "DisksReady"
// VaultReadyCondition means the vault exists and is ready to be used.
VaultReadyCondition clusterv1.ConditionType = "VaultReady"
// SecretReadyCondition means the vault exists and is ready to be used.
SecretReadyCondition clusterv1.ConditionType = "SecretReady"
// NetworkInterfaceReadyCondition means the network interfaces exist and are ready to be used.
NetworkInterfaceReadyCondition clusterv1.ConditionType = "NetworkInterfacesReady"

Expand Down
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"
)
12 changes: 12 additions & 0 deletions azure/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ const (
DefaultImagePublisherID = "cncf-upstream"
// LatestVersion is the image version latest.
LatestVersion = "latest"
// DefaultChunkSize is the default chunk size for bootstrap secret data.
DefaultChunkSize = 15000
)

const (
Expand Down Expand Up @@ -188,6 +190,16 @@ func GenerateAvailabilitySetName(clusterName, nodeGroup string) string {
return fmt.Sprintf("%s_%s-as", clusterName, nodeGroup)
}

// GenerateVaultName generates vault name for the cluster.
func GenerateVaultName(clusterName string) string {
return fmt.Sprintf("%s-vault", clusterName)
}

// GenerateBootstrapSecretName generates the secret name for storing bootstrap secret.
func GenerateBootstrapSecretName(machineName string) string {
return fmt.Sprintf("%s-bootstrap-secret", machineName)
}

// WithIndex appends the index as suffix to a generated name.
func WithIndex(name string, n int) string {
return fmt.Sprintf("%s-%d", name, n)
Expand Down
10 changes: 10 additions & 0 deletions azure/defaults_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,13 @@ func TestMSCorrelationIDSendDecorator(t *testing.T) {
receivedReq.Header.Get(string(tele.CorrIDKeyVal)),
).To(Equal(string(corrID)))
}

func TestGenerateVaultName(t *testing.T) {
g := NewWithT(t)
g.Expect(GenerateVaultName("my-cluster")).Should(Equal("my-cluster-vault"))
}

func TestGenerateBootstrapSecretName(t *testing.T) {
g := NewWithT(t)
g.Expect(GenerateBootstrapSecretName("my-azure-machine")).Should(Equal("my-azure-machine-bootstrap-secret"))
}
6 changes: 6 additions & 0 deletions azure/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ type ClusterDescriber interface {
AvailabilitySetEnabled() bool
CloudProviderConfigOverrides() *infrav1.CloudProviderConfigOverrides
FailureDomains() []string
SecureBootstrapEnabled() bool
}

// AsyncStatusUpdater is an interface used to keep track of long running operations in Status that has Conditions and Futures.
Expand Down Expand Up @@ -111,3 +112,8 @@ type ResourceSpecGetter interface {
// If no update is needed on the resource, Parameters should return nil.
Parameters(existing interface{}) (params interface{}, err error)
}

// UserDataResolver is an interface for resolving user data for VMs.
type UserDataResolver interface {
ResolveUserData() (string, error)
}
66 changes: 66 additions & 0 deletions azure/mock_azure/azure_mock.go

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

21 changes: 21 additions & 0 deletions azure/scope/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"sigs.k8s.io/cluster-api-provider-azure/azure/services/routetables"
"sigs.k8s.io/cluster-api-provider-azure/azure/services/securitygroups"
"sigs.k8s.io/cluster-api-provider-azure/azure/services/subnets"
"sigs.k8s.io/cluster-api-provider-azure/azure/services/vault"
"sigs.k8s.io/cluster-api-provider-azure/azure/services/virtualnetworks"
"sigs.k8s.io/cluster-api-provider-azure/azure/services/vnetpeerings"
"sigs.k8s.io/cluster-api-provider-azure/util/futures"
Expand Down Expand Up @@ -936,3 +937,23 @@ func (s *ClusterScope) TagsSpecs() []azure.TagsSpec {
},
}
}

// SecureBootstrapEnabled returns if secure bootstrapping is enabled for the cluster.
func (s *ClusterScope) SecureBootstrapEnabled() bool {
return s.AzureCluster.Spec.SecureBootstrapEnabled
}

// VaultSpec returns the vault specs for the cluster.
func (s *ClusterScope) VaultSpec() azure.ResourceSpecGetter {
if !s.SecureBootstrapEnabled() {
return nil
}

return &vault.Spec{
Name: azure.GenerateVaultName(s.ClusterName()),
ResourceGroup: s.ResourceGroup(),
Location: s.Location(),
ClusterName: s.ClusterName(),
TenantID: s.TenantID(),
}
}
80 changes: 80 additions & 0 deletions azure/scope/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
"sigs.k8s.io/cluster-api-provider-azure/azure/services/vault"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)
Expand Down Expand Up @@ -356,3 +357,82 @@ func TestOutboundLBName(t *testing.T) {
})
}
}

func TestVaultSpec(t *testing.T) {
tests := []struct {
name string
azureCluster infrav1.AzureCluster
expected *vault.Spec
}{
{
name: "should return nil if secure bootstrap is disabled (disabled by default)",
azureCluster: infrav1.AzureCluster{},
expected: nil,
},
{
name: "should return vault spec if secure bootstrap is enabled",
azureCluster: infrav1.AzureCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster",
},
Spec: infrav1.AzureClusterSpec{
AzureClusterClassSpec: infrav1.AzureClusterClassSpec{
Location: "eastus",
},
ResourceGroup: "my-rg",
SecureBootstrapEnabled: true,
}},
expected: &vault.Spec{
Name: "my-cluster-vault",
ClusterName: "my-cluster",
ResourceGroup: "my-rg",
Location: "eastus",
},
},
}

for _, tc := range tests {
g := NewWithT(t)
scheme := runtime.NewScheme()
_ = infrav1.AddToScheme(scheme)
_ = clusterv1.AddToScheme(scheme)

cluster := &clusterv1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster",
Namespace: "default",
},
}

tc.azureCluster.ObjectMeta = metav1.ObjectMeta{
Name: cluster.Name,
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "cluster.x-k8s.io/v1beta1",
Kind: "Cluster",
Name: "my-cluster",
},
},
}
tc.azureCluster.Spec.SubscriptionID = "my-subscription"

initObjects := []runtime.Object{cluster, &tc.azureCluster}
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(initObjects...).Build()

clusterScope, err := NewClusterScope(context.TODO(), ClusterScopeParams{
AzureClients: AzureClients{
Authorizer: autorest.NullAuthorizer{},
},
Cluster: cluster,
AzureCluster: &tc.azureCluster,
Client: fakeClient,
})
g.Expect(err).ToNot(HaveOccurred())

if tc.expected == nil {
g.Expect(clusterScope.VaultSpec()).Should(BeNil())
} else {
g.Expect(clusterScope.VaultSpec()).Should(Equal(tc.expected))
}
}
}
Loading