Skip to content

Commit 66e4073

Browse files
authored
Merge pull request #1300 from whites11/azure-bastion
Added support for Azure Bastion.
2 parents 8791fa9 + f737685 commit 66e4073

28 files changed

+1123
-204
lines changed

api/v1alpha3/azurecluster_conversion.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ func (src *AzureCluster) ConvertTo(dstRaw conversion.Hub) error { // nolint
5454
dst.Spec.NetworkSpec.APIServerLB.FrontendIPsCount = restored.Spec.NetworkSpec.APIServerLB.FrontendIPsCount
5555
dst.Spec.NetworkSpec.NodeOutboundLB = restored.Spec.NetworkSpec.NodeOutboundLB
5656
dst.Spec.CloudProviderConfigOverrides = restored.Spec.CloudProviderConfigOverrides
57+
dst.Spec.BastionSpec = restored.Spec.BastionSpec
5758

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

98-
// Preserve Hub data on down-conversion.
99-
if err := utilconversion.MarshalData(src, dst); err != nil {
100-
return err
101-
}
102-
10399
return nil
104100
}
105101

api/v1alpha3/zz_generated.conversion.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1alpha4/azurecluster_default.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ const (
2929
DefaultControlPlaneSubnetCIDR = "10.0.0.0/16"
3030
// DefaultNodeSubnetCIDR is the default Node Subnet CIDR
3131
DefaultNodeSubnetCIDR = "10.1.0.0/16"
32+
// DefaultAzureBastionSubnetCIDR is the default Subnet CIDR for AzureBastion
33+
DefaultAzureBastionSubnetCIDR = "10.255.255.224/27"
34+
// DefaultAzureBastionSubnetName is the default Subnet Name for AzureBastion
35+
DefaultAzureBastionSubnetName = "AzureBastionSubnet"
3236
// DefaultInternalLBIPAddress is the default internal load balancer ip address
3337
DefaultInternalLBIPAddress = "10.0.0.100"
3438
// DefaultAzureCloud is the public cloud that will be used by most users
@@ -43,6 +47,7 @@ func (c *AzureCluster) setDefaults() {
4347

4448
func (c *AzureCluster) setNetworkSpecDefaults() {
4549
c.setVnetDefaults()
50+
c.setBastionDefaults()
4651
c.setSubnetDefaults()
4752
c.setAPIServerLBDefaults()
4853
c.setNodeOutboundLBDefaults()
@@ -204,6 +209,29 @@ func (c *AzureCluster) setNodeOutboundLBDefaults() {
204209
}
205210
}
206211

212+
func (c *AzureCluster) setBastionDefaults() {
213+
if c.Spec.BastionSpec.AzureBastion != nil {
214+
if c.Spec.BastionSpec.AzureBastion.Name == "" {
215+
c.Spec.BastionSpec.AzureBastion.Name = generateAzureBastionName(c.ObjectMeta.Name)
216+
}
217+
// Ensure defaults for the Subnet settings.
218+
{
219+
if c.Spec.BastionSpec.AzureBastion.Subnet.Name == "" {
220+
c.Spec.BastionSpec.AzureBastion.Subnet.Name = DefaultAzureBastionSubnetName
221+
}
222+
if len(c.Spec.BastionSpec.AzureBastion.Subnet.CIDRBlocks) == 0 {
223+
c.Spec.BastionSpec.AzureBastion.Subnet.CIDRBlocks = []string{DefaultAzureBastionSubnetCIDR}
224+
}
225+
}
226+
// Ensure defaults for the PublicIP settings.
227+
{
228+
if c.Spec.BastionSpec.AzureBastion.PublicIP.Name == "" {
229+
c.Spec.BastionSpec.AzureBastion.PublicIP.Name = generateAzureBastionPublicIPName(c.ObjectMeta.Name)
230+
}
231+
}
232+
}
233+
}
234+
207235
// generateVnetName generates a virtual network name, based on the cluster name.
208236
func generateVnetName(clusterName string) string {
209237
return fmt.Sprintf("%s-%s", clusterName, "vnet")
@@ -219,6 +247,16 @@ func generateNodeSubnetName(clusterName string) string {
219247
return fmt.Sprintf("%s-%s", clusterName, "node-subnet")
220248
}
221249

250+
// generateAzureBastionName generates an azure bastion name.
251+
func generateAzureBastionName(clusterName string) string {
252+
return fmt.Sprintf("%s-azure-bastion", clusterName)
253+
}
254+
255+
// generateAzureBastionPublicIPName generates an azure bastion public ip name.
256+
func generateAzureBastionPublicIPName(clusterName string) string {
257+
return fmt.Sprintf("%s-azure-bastion-pip", clusterName)
258+
}
259+
222260
// generateControlPlaneSecurityGroupName generates a control plane security group name, based on the cluster name.
223261
func generateControlPlaneSecurityGroupName(clusterName string) string {
224262
return fmt.Sprintf("%s-%s", clusterName, "controlplane-nsg")

api/v1alpha4/azurecluster_default_test.go

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,3 +910,206 @@ func TestNodeOutboundLBDefaults(t *testing.T) {
910910
})
911911
}
912912
}
913+
914+
func TestBastionDefault(t *testing.T) {
915+
cases := map[string]struct {
916+
cluster *AzureCluster
917+
output *AzureCluster
918+
}{
919+
"no bastion set": {
920+
cluster: &AzureCluster{
921+
ObjectMeta: metav1.ObjectMeta{
922+
Name: "foo",
923+
},
924+
Spec: AzureClusterSpec{},
925+
},
926+
output: &AzureCluster{
927+
ObjectMeta: metav1.ObjectMeta{
928+
Name: "foo",
929+
},
930+
Spec: AzureClusterSpec{},
931+
},
932+
},
933+
"azure bastion enabled with no settings": {
934+
cluster: &AzureCluster{
935+
ObjectMeta: metav1.ObjectMeta{
936+
Name: "foo",
937+
},
938+
Spec: AzureClusterSpec{
939+
BastionSpec: BastionSpec{
940+
AzureBastion: &AzureBastion{},
941+
},
942+
},
943+
},
944+
output: &AzureCluster{
945+
ObjectMeta: metav1.ObjectMeta{
946+
Name: "foo",
947+
},
948+
Spec: AzureClusterSpec{
949+
BastionSpec: BastionSpec{
950+
AzureBastion: &AzureBastion{
951+
Name: "foo-azure-bastion",
952+
Subnet: SubnetSpec{
953+
Name: "AzureBastionSubnet",
954+
CIDRBlocks: []string{DefaultAzureBastionSubnetCIDR},
955+
},
956+
PublicIP: PublicIPSpec{
957+
Name: "foo-azure-bastion-pip",
958+
},
959+
},
960+
},
961+
},
962+
},
963+
},
964+
"azure bastion enabled with name set": {
965+
cluster: &AzureCluster{
966+
ObjectMeta: metav1.ObjectMeta{
967+
Name: "foo",
968+
},
969+
Spec: AzureClusterSpec{
970+
BastionSpec: BastionSpec{
971+
AzureBastion: &AzureBastion{
972+
Name: "my-fancy-name",
973+
},
974+
},
975+
},
976+
},
977+
output: &AzureCluster{
978+
ObjectMeta: metav1.ObjectMeta{
979+
Name: "foo",
980+
},
981+
Spec: AzureClusterSpec{
982+
BastionSpec: BastionSpec{
983+
AzureBastion: &AzureBastion{
984+
Name: "my-fancy-name",
985+
Subnet: SubnetSpec{
986+
Name: "AzureBastionSubnet",
987+
CIDRBlocks: []string{DefaultAzureBastionSubnetCIDR},
988+
},
989+
PublicIP: PublicIPSpec{
990+
Name: "foo-azure-bastion-pip",
991+
},
992+
},
993+
},
994+
},
995+
},
996+
},
997+
"azure bastion enabled with subnet partially set": {
998+
cluster: &AzureCluster{
999+
ObjectMeta: metav1.ObjectMeta{
1000+
Name: "foo",
1001+
},
1002+
Spec: AzureClusterSpec{
1003+
BastionSpec: BastionSpec{
1004+
AzureBastion: &AzureBastion{
1005+
Subnet: SubnetSpec{},
1006+
},
1007+
},
1008+
},
1009+
},
1010+
output: &AzureCluster{
1011+
ObjectMeta: metav1.ObjectMeta{
1012+
Name: "foo",
1013+
},
1014+
Spec: AzureClusterSpec{
1015+
BastionSpec: BastionSpec{
1016+
AzureBastion: &AzureBastion{
1017+
Name: "foo-azure-bastion",
1018+
Subnet: SubnetSpec{
1019+
Name: "AzureBastionSubnet",
1020+
CIDRBlocks: []string{DefaultAzureBastionSubnetCIDR},
1021+
},
1022+
PublicIP: PublicIPSpec{
1023+
Name: "foo-azure-bastion-pip",
1024+
},
1025+
},
1026+
},
1027+
},
1028+
},
1029+
},
1030+
"azure bastion enabled with subnet fully set": {
1031+
cluster: &AzureCluster{
1032+
ObjectMeta: metav1.ObjectMeta{
1033+
Name: "foo",
1034+
},
1035+
Spec: AzureClusterSpec{
1036+
BastionSpec: BastionSpec{
1037+
AzureBastion: &AzureBastion{
1038+
Subnet: SubnetSpec{
1039+
Name: "my-superfancy-name",
1040+
CIDRBlocks: []string{"10.10.0.0/16"},
1041+
},
1042+
},
1043+
},
1044+
},
1045+
},
1046+
output: &AzureCluster{
1047+
ObjectMeta: metav1.ObjectMeta{
1048+
Name: "foo",
1049+
},
1050+
Spec: AzureClusterSpec{
1051+
BastionSpec: BastionSpec{
1052+
AzureBastion: &AzureBastion{
1053+
Name: "foo-azure-bastion",
1054+
Subnet: SubnetSpec{
1055+
Name: "my-superfancy-name",
1056+
CIDRBlocks: []string{"10.10.0.0/16"},
1057+
},
1058+
PublicIP: PublicIPSpec{
1059+
Name: "foo-azure-bastion-pip",
1060+
},
1061+
},
1062+
},
1063+
},
1064+
},
1065+
},
1066+
"azure bastion enabled with public IP name set": {
1067+
cluster: &AzureCluster{
1068+
ObjectMeta: metav1.ObjectMeta{
1069+
Name: "foo",
1070+
},
1071+
Spec: AzureClusterSpec{
1072+
BastionSpec: BastionSpec{
1073+
AzureBastion: &AzureBastion{
1074+
PublicIP: PublicIPSpec{
1075+
Name: "my-ultrafancy-pip-name",
1076+
},
1077+
},
1078+
},
1079+
},
1080+
},
1081+
output: &AzureCluster{
1082+
ObjectMeta: metav1.ObjectMeta{
1083+
Name: "foo",
1084+
},
1085+
Spec: AzureClusterSpec{
1086+
BastionSpec: BastionSpec{
1087+
AzureBastion: &AzureBastion{
1088+
Name: "foo-azure-bastion",
1089+
Subnet: SubnetSpec{
1090+
Name: "AzureBastionSubnet",
1091+
CIDRBlocks: []string{DefaultAzureBastionSubnetCIDR},
1092+
},
1093+
PublicIP: PublicIPSpec{
1094+
Name: "my-ultrafancy-pip-name",
1095+
},
1096+
},
1097+
},
1098+
},
1099+
},
1100+
},
1101+
}
1102+
1103+
for name := range cases {
1104+
c := cases[name]
1105+
t.Run(name, func(t *testing.T) {
1106+
t.Parallel()
1107+
c.cluster.setBastionDefaults()
1108+
if !reflect.DeepEqual(c.cluster, c.output) {
1109+
expected, _ := json.MarshalIndent(c.output, "", "\t")
1110+
actual, _ := json.MarshalIndent(c.cluster, "", "\t")
1111+
t.Errorf("Expected %s, got %s", string(expected), string(actual))
1112+
}
1113+
})
1114+
}
1115+
}

api/v1alpha4/azurecluster_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ type AzureClusterSpec struct {
6666
// +optional
6767
AzureEnvironment string `json:"azureEnvironment,omitempty"`
6868

69+
// BastionSpec encapsulates all things related to the Bastions in the cluster.
70+
// +optional
71+
BastionSpec BastionSpec `json:"bastionSpec,omitempty"`
72+
6973
// CloudProviderConfigOverrides is an optional set of configuration values that can be overridden in azure cloud provider config.
7074
// This is only a subset of options that are available in azure cloud provider config.
7175
// 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.

api/v1alpha4/azurecluster_webhook.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,14 @@ func (c *AzureCluster) ValidateUpdate(oldRaw runtime.Object) error {
9898
)
9999
}
100100

101+
// Allow enabling azure bastion but avoid disabling it.
102+
if old.Spec.BastionSpec.AzureBastion != nil && !reflect.DeepEqual(old.Spec.BastionSpec.AzureBastion, c.Spec.BastionSpec.AzureBastion) {
103+
allErrs = append(allErrs,
104+
field.Invalid(field.NewPath("spec", "BastionSpec", "AzureBastion"),
105+
c.Spec.BastionSpec.AzureBastion, "azure bastion cannot be removed from a cluster"),
106+
)
107+
}
108+
101109
if len(allErrs) == 0 {
102110
return c.validateCluster(old)
103111
}

api/v1alpha4/types.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,3 +571,19 @@ const (
571571
// AvailabilitySetRateLimit ...
572572
AvailabilitySetRateLimit = "availabilitySetRateLimit"
573573
)
574+
575+
// BastionSpec specifies how the Bastion feature should be set up for the cluster.
576+
type BastionSpec struct {
577+
// +optional
578+
AzureBastion *AzureBastion `json:"azureBastion,omitempty"`
579+
}
580+
581+
// AzureBastion specifies how the Azure Bastion cloud component should be configured.
582+
type AzureBastion struct {
583+
// +optional
584+
Name string `json:"name,omitempty"`
585+
// +optional
586+
Subnet SubnetSpec `json:"subnet,omitempty"`
587+
// +optional
588+
PublicIP PublicIPSpec `json:"publicIP,omitempty"`
589+
}

0 commit comments

Comments
 (0)