Skip to content

Commit

Permalink
Added azure bastion support
Browse files Browse the repository at this point in the history
  • Loading branch information
whites11 committed Jun 3, 2021
1 parent 8791fa9 commit f737685
Show file tree
Hide file tree
Showing 28 changed files with 1,123 additions and 204 deletions.
6 changes: 1 addition & 5 deletions api/v1alpha3/azurecluster_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func (src *AzureCluster) ConvertTo(dstRaw conversion.Hub) error { // nolint
dst.Spec.NetworkSpec.APIServerLB.FrontendIPsCount = restored.Spec.NetworkSpec.APIServerLB.FrontendIPsCount
dst.Spec.NetworkSpec.NodeOutboundLB = restored.Spec.NetworkSpec.NodeOutboundLB
dst.Spec.CloudProviderConfigOverrides = restored.Spec.CloudProviderConfigOverrides
dst.Spec.BastionSpec = restored.Spec.BastionSpec

// Here we manually restore outbound security rules. Since v1alpha3 only supports ingress ("Inbound") rules, all v1alpha4 outbound rules are dropped when an AzureCluster
// is converted to v1alpha3. We loop through all security group rules. For all previously existing outbound rules we restore the full rule.
Expand Down Expand Up @@ -95,11 +96,6 @@ func (dst *AzureCluster) ConvertFrom(srcRaw conversion.Hub) error { // nolint
return err
}

// Preserve Hub data on down-conversion.
if err := utilconversion.MarshalData(src, dst); err != nil {
return err
}

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.

38 changes: 38 additions & 0 deletions api/v1alpha4/azurecluster_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ const (
DefaultControlPlaneSubnetCIDR = "10.0.0.0/16"
// DefaultNodeSubnetCIDR is the default Node Subnet CIDR
DefaultNodeSubnetCIDR = "10.1.0.0/16"
// DefaultAzureBastionSubnetCIDR is the default Subnet CIDR for AzureBastion
DefaultAzureBastionSubnetCIDR = "10.255.255.224/27"
// DefaultAzureBastionSubnetName is the default Subnet Name for AzureBastion
DefaultAzureBastionSubnetName = "AzureBastionSubnet"
// DefaultInternalLBIPAddress is the default internal load balancer ip address
DefaultInternalLBIPAddress = "10.0.0.100"
// DefaultAzureCloud is the public cloud that will be used by most users
Expand All @@ -43,6 +47,7 @@ func (c *AzureCluster) setDefaults() {

func (c *AzureCluster) setNetworkSpecDefaults() {
c.setVnetDefaults()
c.setBastionDefaults()
c.setSubnetDefaults()
c.setAPIServerLBDefaults()
c.setNodeOutboundLBDefaults()
Expand Down Expand Up @@ -204,6 +209,29 @@ func (c *AzureCluster) setNodeOutboundLBDefaults() {
}
}

func (c *AzureCluster) setBastionDefaults() {
if c.Spec.BastionSpec.AzureBastion != nil {
if c.Spec.BastionSpec.AzureBastion.Name == "" {
c.Spec.BastionSpec.AzureBastion.Name = generateAzureBastionName(c.ObjectMeta.Name)
}
// Ensure defaults for the Subnet settings.
{
if c.Spec.BastionSpec.AzureBastion.Subnet.Name == "" {
c.Spec.BastionSpec.AzureBastion.Subnet.Name = DefaultAzureBastionSubnetName
}
if len(c.Spec.BastionSpec.AzureBastion.Subnet.CIDRBlocks) == 0 {
c.Spec.BastionSpec.AzureBastion.Subnet.CIDRBlocks = []string{DefaultAzureBastionSubnetCIDR}
}
}
// Ensure defaults for the PublicIP settings.
{
if c.Spec.BastionSpec.AzureBastion.PublicIP.Name == "" {
c.Spec.BastionSpec.AzureBastion.PublicIP.Name = generateAzureBastionPublicIPName(c.ObjectMeta.Name)
}
}
}
}

// generateVnetName generates a virtual network name, based on the cluster name.
func generateVnetName(clusterName string) string {
return fmt.Sprintf("%s-%s", clusterName, "vnet")
Expand All @@ -219,6 +247,16 @@ func generateNodeSubnetName(clusterName string) string {
return fmt.Sprintf("%s-%s", clusterName, "node-subnet")
}

// generateAzureBastionName generates an azure bastion name.
func generateAzureBastionName(clusterName string) string {
return fmt.Sprintf("%s-azure-bastion", clusterName)
}

// generateAzureBastionPublicIPName generates an azure bastion public ip name.
func generateAzureBastionPublicIPName(clusterName string) string {
return fmt.Sprintf("%s-azure-bastion-pip", clusterName)
}

// generateControlPlaneSecurityGroupName generates a control plane security group name, based on the cluster name.
func generateControlPlaneSecurityGroupName(clusterName string) string {
return fmt.Sprintf("%s-%s", clusterName, "controlplane-nsg")
Expand Down
203 changes: 203 additions & 0 deletions api/v1alpha4/azurecluster_default_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -910,3 +910,206 @@ func TestNodeOutboundLBDefaults(t *testing.T) {
})
}
}

func TestBastionDefault(t *testing.T) {
cases := map[string]struct {
cluster *AzureCluster
output *AzureCluster
}{
"no bastion set": {
cluster: &AzureCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: AzureClusterSpec{},
},
output: &AzureCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: AzureClusterSpec{},
},
},
"azure bastion enabled with no settings": {
cluster: &AzureCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: AzureClusterSpec{
BastionSpec: BastionSpec{
AzureBastion: &AzureBastion{},
},
},
},
output: &AzureCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: AzureClusterSpec{
BastionSpec: BastionSpec{
AzureBastion: &AzureBastion{
Name: "foo-azure-bastion",
Subnet: SubnetSpec{
Name: "AzureBastionSubnet",
CIDRBlocks: []string{DefaultAzureBastionSubnetCIDR},
},
PublicIP: PublicIPSpec{
Name: "foo-azure-bastion-pip",
},
},
},
},
},
},
"azure bastion enabled with name set": {
cluster: &AzureCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: AzureClusterSpec{
BastionSpec: BastionSpec{
AzureBastion: &AzureBastion{
Name: "my-fancy-name",
},
},
},
},
output: &AzureCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: AzureClusterSpec{
BastionSpec: BastionSpec{
AzureBastion: &AzureBastion{
Name: "my-fancy-name",
Subnet: SubnetSpec{
Name: "AzureBastionSubnet",
CIDRBlocks: []string{DefaultAzureBastionSubnetCIDR},
},
PublicIP: PublicIPSpec{
Name: "foo-azure-bastion-pip",
},
},
},
},
},
},
"azure bastion enabled with subnet partially set": {
cluster: &AzureCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: AzureClusterSpec{
BastionSpec: BastionSpec{
AzureBastion: &AzureBastion{
Subnet: SubnetSpec{},
},
},
},
},
output: &AzureCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: AzureClusterSpec{
BastionSpec: BastionSpec{
AzureBastion: &AzureBastion{
Name: "foo-azure-bastion",
Subnet: SubnetSpec{
Name: "AzureBastionSubnet",
CIDRBlocks: []string{DefaultAzureBastionSubnetCIDR},
},
PublicIP: PublicIPSpec{
Name: "foo-azure-bastion-pip",
},
},
},
},
},
},
"azure bastion enabled with subnet fully set": {
cluster: &AzureCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: AzureClusterSpec{
BastionSpec: BastionSpec{
AzureBastion: &AzureBastion{
Subnet: SubnetSpec{
Name: "my-superfancy-name",
CIDRBlocks: []string{"10.10.0.0/16"},
},
},
},
},
},
output: &AzureCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: AzureClusterSpec{
BastionSpec: BastionSpec{
AzureBastion: &AzureBastion{
Name: "foo-azure-bastion",
Subnet: SubnetSpec{
Name: "my-superfancy-name",
CIDRBlocks: []string{"10.10.0.0/16"},
},
PublicIP: PublicIPSpec{
Name: "foo-azure-bastion-pip",
},
},
},
},
},
},
"azure bastion enabled with public IP name set": {
cluster: &AzureCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: AzureClusterSpec{
BastionSpec: BastionSpec{
AzureBastion: &AzureBastion{
PublicIP: PublicIPSpec{
Name: "my-ultrafancy-pip-name",
},
},
},
},
},
output: &AzureCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: AzureClusterSpec{
BastionSpec: BastionSpec{
AzureBastion: &AzureBastion{
Name: "foo-azure-bastion",
Subnet: SubnetSpec{
Name: "AzureBastionSubnet",
CIDRBlocks: []string{DefaultAzureBastionSubnetCIDR},
},
PublicIP: PublicIPSpec{
Name: "my-ultrafancy-pip-name",
},
},
},
},
},
},
}

for name := range cases {
c := cases[name]
t.Run(name, func(t *testing.T) {
t.Parallel()
c.cluster.setBastionDefaults()
if !reflect.DeepEqual(c.cluster, c.output) {
expected, _ := json.MarshalIndent(c.output, "", "\t")
actual, _ := json.MarshalIndent(c.cluster, "", "\t")
t.Errorf("Expected %s, got %s", string(expected), string(actual))
}
})
}
}
4 changes: 4 additions & 0 deletions api/v1alpha4/azurecluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ type AzureClusterSpec struct {
// +optional
AzureEnvironment string `json:"azureEnvironment,omitempty"`

// BastionSpec encapsulates all things related to the Bastions in the cluster.
// +optional
BastionSpec BastionSpec `json:"bastionSpec,omitempty"`

// CloudProviderConfigOverrides is an optional set of configuration values that can be overridden in azure cloud provider config.
// This is only a subset of options that are available in azure cloud provider config.
// Some values for the cloud provider config are inferred from other parts of cluster api provider azure spec, and may not be available for overrides.
Expand Down
8 changes: 8 additions & 0 deletions api/v1alpha4/azurecluster_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ func (c *AzureCluster) ValidateUpdate(oldRaw runtime.Object) error {
)
}

// Allow enabling azure bastion but avoid disabling it.
if old.Spec.BastionSpec.AzureBastion != nil && !reflect.DeepEqual(old.Spec.BastionSpec.AzureBastion, c.Spec.BastionSpec.AzureBastion) {
allErrs = append(allErrs,
field.Invalid(field.NewPath("spec", "BastionSpec", "AzureBastion"),
c.Spec.BastionSpec.AzureBastion, "azure bastion cannot be removed from a cluster"),
)
}

if len(allErrs) == 0 {
return c.validateCluster(old)
}
Expand Down
16 changes: 16 additions & 0 deletions api/v1alpha4/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -571,3 +571,19 @@ const (
// AvailabilitySetRateLimit ...
AvailabilitySetRateLimit = "availabilitySetRateLimit"
)

// BastionSpec specifies how the Bastion feature should be set up for the cluster.
type BastionSpec struct {
// +optional
AzureBastion *AzureBastion `json:"azureBastion,omitempty"`
}

// AzureBastion specifies how the Azure Bastion cloud component should be configured.
type AzureBastion struct {
// +optional
Name string `json:"name,omitempty"`
// +optional
Subnet SubnetSpec `json:"subnet,omitempty"`
// +optional
PublicIP PublicIPSpec `json:"publicIP,omitempty"`
}
Loading

0 comments on commit f737685

Please sign in to comment.