Skip to content

Commit df75af2

Browse files
authored
Merge pull request #1640 from richardchen331/api_server_access_profile
Support APIServerAccessProfile in AzureManagedControlPlane
2 parents 354ddd1 + 2707d8d commit df75af2

11 files changed

Lines changed: 272 additions & 0 deletions

azure/scope/managedcontrolplane.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,15 @@ func (s *ManagedControlPlaneScope) ManagedClusterSpec() (azure.ManagedClusterSpe
437437
}
438438
}
439439

440+
if s.ControlPlane.Spec.APIServerAccessProfile != nil {
441+
managedClusterSpec.APIServerAccessProfile = &azure.APIServerAccessProfile{
442+
AuthorizedIPRanges: s.ControlPlane.Spec.APIServerAccessProfile.AuthorizedIPRanges,
443+
EnablePrivateCluster: s.ControlPlane.Spec.APIServerAccessProfile.EnablePrivateCluster,
444+
PrivateDNSZone: s.ControlPlane.Spec.APIServerAccessProfile.PrivateDNSZone,
445+
EnablePrivateClusterPublicFQDN: s.ControlPlane.Spec.APIServerAccessProfile.EnablePrivateClusterPublicFQDN,
446+
}
447+
}
448+
440449
return managedClusterSpec, nil
441450
}
442451

azure/services/managedclusters/managedclusters.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,18 @@ func computeDiffOfNormalizedClusters(managedCluster containerservice.ManagedClus
107107
existingMCPropertiesNormalized.NetworkProfile.LoadBalancerProfile = existingMC.NetworkProfile.LoadBalancerProfile
108108
}
109109

110+
if managedCluster.APIServerAccessProfile != nil {
111+
propertiesNormalized.APIServerAccessProfile = &containerservice.ManagedClusterAPIServerAccessProfile{
112+
AuthorizedIPRanges: managedCluster.APIServerAccessProfile.AuthorizedIPRanges,
113+
}
114+
}
115+
116+
if existingMC.APIServerAccessProfile != nil {
117+
existingMCPropertiesNormalized.APIServerAccessProfile = &containerservice.ManagedClusterAPIServerAccessProfile{
118+
AuthorizedIPRanges: existingMC.APIServerAccessProfile.AuthorizedIPRanges,
119+
}
120+
}
121+
110122
clusterNormalized := &containerservice.ManagedCluster{
111123
ManagedClusterProperties: propertiesNormalized,
112124
}
@@ -269,6 +281,15 @@ func (s *Service) Reconcile(ctx context.Context) error {
269281
}
270282
}
271283

284+
if managedClusterSpec.APIServerAccessProfile != nil {
285+
managedCluster.APIServerAccessProfile = &containerservice.ManagedClusterAPIServerAccessProfile{
286+
AuthorizedIPRanges: &managedClusterSpec.APIServerAccessProfile.AuthorizedIPRanges,
287+
EnablePrivateCluster: managedClusterSpec.APIServerAccessProfile.EnablePrivateCluster,
288+
PrivateDNSZone: managedClusterSpec.APIServerAccessProfile.PrivateDNSZone,
289+
EnablePrivateClusterPublicFQDN: managedClusterSpec.APIServerAccessProfile.EnablePrivateClusterPublicFQDN,
290+
}
291+
}
292+
272293
if isCreate {
273294
managedCluster, err = s.Client.CreateOrUpdate(ctx, managedClusterSpec.ResourceGroupName, managedClusterSpec.Name, managedCluster)
274295
if err != nil {

azure/types.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,9 @@ type ManagedClusterSpec struct {
355355

356356
// LoadBalancerProfile is the profile of the cluster load balancer.
357357
LoadBalancerProfile *LoadBalancerProfile
358+
359+
// APIServerAccessProfile is the access profile for AKS API server.
360+
APIServerAccessProfile *APIServerAccessProfile
358361
}
359362

360363
// AADProfile is Azure Active Directory configuration to integrate with AKS, for aad authentication.
@@ -398,6 +401,18 @@ type LoadBalancerProfile struct {
398401
IdleTimeoutInMinutes *int32
399402
}
400403

404+
// APIServerAccessProfile is the access profile for AKS API server.
405+
type APIServerAccessProfile struct {
406+
// AuthorizedIPRanges - Authorized IP Ranges to kubernetes API server.
407+
AuthorizedIPRanges []string
408+
// EnablePrivateCluster - Whether to create the cluster as a private cluster or not.
409+
EnablePrivateCluster *bool
410+
// PrivateDNSZone - Private dns zone mode for private cluster.
411+
PrivateDNSZone *string
412+
// EnablePrivateClusterPublicFQDN - Whether to create additional public FQDN for private cluster or not.
413+
EnablePrivateClusterPublicFQDN *bool
414+
}
415+
401416
// AgentPoolSpec contains agent pool specification details.
402417
type AgentPoolSpec struct {
403418
// Name is the name of agent pool.

config/crd/bases/infrastructure.cluster.x-k8s.io_azuremanagedcontrolplanes.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,32 @@ spec:
226226
resources managed by the Azure provider, in addition to the ones
227227
added by default.
228228
type: object
229+
apiServerAccessProfile:
230+
description: APIServerAccessProfile is the access profile for AKS
231+
API server.
232+
properties:
233+
authorizedIPRanges:
234+
description: AuthorizedIPRanges - Authorized IP Ranges to kubernetes
235+
API server.
236+
items:
237+
type: string
238+
type: array
239+
enablePrivateCluster:
240+
description: EnablePrivateCluster - Whether to create the cluster
241+
as a private cluster or not.
242+
type: boolean
243+
enablePrivateClusterPublicFQDN:
244+
description: EnablePrivateClusterPublicFQDN - Whether to create
245+
additional public FQDN for private cluster or not.
246+
type: boolean
247+
privateDNSZone:
248+
description: PrivateDNSZone - Private dns zone mode for private
249+
cluster.
250+
enum:
251+
- System
252+
- None
253+
type: string
254+
type: object
229255
controlPlaneEndpoint:
230256
description: ControlPlaneEndpoint represents the endpoint used to
231257
communicate with the control plane.

docs/book/src/topics/managedcluster.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,31 @@ spec:
274274
idleTimeoutInMinutes: 10 # 4-120
275275
```
276276

277+
### Secure access to the API server using authorized IP address ranges
278+
279+
In Kubernetes, the API server receives requests to perform actions in the cluster such as to create resources or scale the number of nodes. The API server is the central way to interact with and manage a cluster. To improve cluster security and minimize attacks, the API server should only be accessible from a limited set of IP address ranges.
280+
281+
For more documentation about authorized IP address ranges refer [AKS Doc](https://docs.microsoft.com/en-us/azure/aks/api-server-authorized-ip-ranges) and [AKS REST API Doc](https://docs.microsoft.com/en-us/rest/api/aks/managed-clusters/create-or-update)
282+
283+
```yaml
284+
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha4
285+
kind: AzureManagedControlPlane
286+
metadata:
287+
name: my-cluster-control-plane
288+
spec:
289+
location: southcentralus
290+
resourceGroupName: foo-bar
291+
sshPublicKey: ${AZURE_SSH_PUBLIC_KEY_B64:=""}
292+
subscriptionID: 00000000-0000-0000-0000-000000000000 # fake uuid
293+
version: v1.21.2
294+
apiServerAccessProfile:
295+
authorizedIPRanges:
296+
- 12.34.56.78/32
297+
enablePrivateCluster: false
298+
privateDNSZone: None # System, None. Allowed only when enablePrivateCluster is true
299+
enablePrivateClusterPublicFQDN: false # Allowed only when enablePrivateCluster is true
300+
```
301+
277302
## Features
278303

279304
AKS clusters deployed from CAPZ currently only support a limited,

exp/api/v1alpha3/azuremanagedcontrolplane_conversion.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func (src *AzureManagedControlPlane) ConvertTo(dstRaw conversion.Hub) error { //
4040
dst.Spec.IdentityRef = restored.Spec.IdentityRef
4141
dst.Spec.SKU = restored.Spec.SKU
4242
dst.Spec.LoadBalancerProfile = restored.Spec.LoadBalancerProfile
43+
dst.Spec.APIServerAccessProfile = restored.Spec.APIServerAccessProfile
4344

4445
dst.Status.LongRunningOperationStates = restored.Status.LongRunningOperationStates
4546

exp/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.

exp/api/v1alpha4/azuremanagedcontrolplane_types.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ import (
2424
infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1alpha4"
2525
)
2626

27+
const (
28+
// PrivateDNSZoneModeSystem represents mode System for azuremanagedcontrolplane.
29+
PrivateDNSZoneModeSystem string = "System"
30+
31+
// PrivateDNSZoneModeNone represents mode None for azuremanagedcontrolplane.
32+
PrivateDNSZoneModeNone string = "None"
33+
)
34+
2735
// AzureManagedControlPlaneSpec defines the desired state of AzureManagedControlPlane.
2836
type AzureManagedControlPlaneSpec struct {
2937
// Version defines the desired Kubernetes version.
@@ -95,6 +103,10 @@ type AzureManagedControlPlaneSpec struct {
95103
// LoadBalancerProfile is the profile of the cluster load balancer.
96104
// +optional
97105
LoadBalancerProfile *LoadBalancerProfile `json:"loadBalancerProfile,omitempty"`
106+
107+
// APIServerAccessProfile is the access profile for AKS API server.
108+
// +optional
109+
APIServerAccessProfile *APIServerAccessProfile `json:"apiServerAccessProfile,omitempty"`
98110
}
99111

100112
// AADProfile - AAD integration managed by AKS.
@@ -143,6 +155,23 @@ type LoadBalancerProfile struct {
143155
IdleTimeoutInMinutes *int32 `json:"idleTimeoutInMinutes,omitempty"`
144156
}
145157

158+
// APIServerAccessProfile - access profile for AKS API server.
159+
type APIServerAccessProfile struct {
160+
// AuthorizedIPRanges - Authorized IP Ranges to kubernetes API server.
161+
// +optional
162+
AuthorizedIPRanges []string `json:"authorizedIPRanges,omitempty"`
163+
// EnablePrivateCluster - Whether to create the cluster as a private cluster or not.
164+
// +optional
165+
EnablePrivateCluster *bool `json:"enablePrivateCluster,omitempty"`
166+
// PrivateDNSZone - Private dns zone mode for private cluster.
167+
// +kubebuilder:validation:Enum=System;None
168+
// +optional
169+
PrivateDNSZone *string `json:"privateDNSZone,omitempty"`
170+
// EnablePrivateClusterPublicFQDN - Whether to create additional public FQDN for private cluster or not.
171+
// +optional
172+
EnablePrivateClusterPublicFQDN *bool `json:"enablePrivateClusterPublicFQDN,omitempty"`
173+
}
174+
146175
// ManagedControlPlaneVirtualNetwork describes a virtual network required to provision AKS clusters.
147176
type ManagedControlPlaneVirtualNetwork struct {
148177
Name string `json:"name"`

exp/api/v1alpha4/azuremanagedcontrolplane_webhook.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package v1alpha4
1919
import (
2020
"errors"
2121
"net"
22+
"reflect"
2223
"regexp"
2324
"strings"
2425

@@ -242,6 +243,10 @@ func (r *AzureManagedControlPlane) ValidateUpdate(oldRaw runtime.Object) error {
242243
}
243244
}
244245

246+
if errs := r.validateAPIServerAccessProfileUpdate(old); len(errs) > 0 {
247+
allErrs = append(allErrs, errs...)
248+
}
249+
245250
if len(allErrs) == 0 {
246251
return r.Validate()
247252
}
@@ -263,6 +268,7 @@ func (r *AzureManagedControlPlane) Validate() error {
263268
r.validateDNSServiceIP,
264269
r.validateSSHKey,
265270
r.validateLoadBalancerProfile,
271+
r.validateAPIServerAccessProfile,
266272
}
267273

268274
var errs []error
@@ -357,3 +363,52 @@ func (r *AzureManagedControlPlane) validateLoadBalancerProfile() error {
357363

358364
return nil
359365
}
366+
367+
// validateAPIServerAccessProfile validates an APIServerAccessProfile.
368+
func (r *AzureManagedControlPlane) validateAPIServerAccessProfile() error {
369+
if r.Spec.APIServerAccessProfile != nil {
370+
var allErrs field.ErrorList
371+
for _, ipRange := range r.Spec.APIServerAccessProfile.AuthorizedIPRanges {
372+
if _, _, err := net.ParseCIDR(ipRange); err != nil {
373+
allErrs = append(allErrs, field.Invalid(field.NewPath("Spec", "APIServerAccessProfile", "AuthorizedIPRanges"), ipRange, "invalid CIDR format"))
374+
}
375+
}
376+
if len(allErrs) > 0 {
377+
agg := kerrors.NewAggregate(allErrs.ToAggregate().Errors())
378+
azuremanagedcontrolplanelog.Info("Invalid apiServerAccessProfile: %s", agg.Error())
379+
return agg
380+
}
381+
}
382+
return nil
383+
}
384+
385+
// validateAPIServerAccessProfileUpdate validates update to APIServerAccessProfile.
386+
func (r *AzureManagedControlPlane) validateAPIServerAccessProfileUpdate(old *AzureManagedControlPlane) field.ErrorList {
387+
var allErrs field.ErrorList
388+
389+
newAPIServerAccessProfileNormalized := &APIServerAccessProfile{}
390+
oldAPIServerAccessProfileNormalized := &APIServerAccessProfile{}
391+
if r.Spec.APIServerAccessProfile != nil {
392+
newAPIServerAccessProfileNormalized = &APIServerAccessProfile{
393+
EnablePrivateCluster: r.Spec.APIServerAccessProfile.EnablePrivateCluster,
394+
PrivateDNSZone: r.Spec.APIServerAccessProfile.PrivateDNSZone,
395+
EnablePrivateClusterPublicFQDN: r.Spec.APIServerAccessProfile.EnablePrivateClusterPublicFQDN,
396+
}
397+
}
398+
if old.Spec.APIServerAccessProfile != nil {
399+
oldAPIServerAccessProfileNormalized = &APIServerAccessProfile{
400+
EnablePrivateCluster: old.Spec.APIServerAccessProfile.EnablePrivateCluster,
401+
PrivateDNSZone: old.Spec.APIServerAccessProfile.PrivateDNSZone,
402+
EnablePrivateClusterPublicFQDN: old.Spec.APIServerAccessProfile.EnablePrivateClusterPublicFQDN,
403+
}
404+
}
405+
406+
if !reflect.DeepEqual(newAPIServerAccessProfileNormalized, oldAPIServerAccessProfileNormalized) {
407+
allErrs = append(allErrs,
408+
field.Invalid(field.NewPath("Spec", "APIServerAccessProfile"),
409+
r.Spec.APIServerAccessProfile, "fields (except for AuthorizedIPRanges) are immutable"),
410+
)
411+
}
412+
413+
return allErrs
414+
}

exp/api/v1alpha4/azuremanagedcontrolplane_webhook_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,18 @@ func TestValidatingWebhook(t *testing.T) {
219219
},
220220
expectErr: true,
221221
},
222+
{
223+
name: "Invalid CIDR for AuthorizedIPRanges",
224+
amcp: AzureManagedControlPlane{
225+
Spec: AzureManagedControlPlaneSpec{
226+
Version: "v1.21.2",
227+
APIServerAccessProfile: &APIServerAccessProfile{
228+
AuthorizedIPRanges: []string{"1.2.3.400/32"},
229+
},
230+
},
231+
},
232+
expectErr: true,
233+
},
222234
}
223235

224236
for _, tt := range tests {
@@ -671,6 +683,44 @@ func TestAzureManagedControlPlane_ValidateUpdate(t *testing.T) {
671683
},
672684
wantErr: true,
673685
},
686+
{
687+
name: "AzureManagedControlPlane EnablePrivateCluster is immutable",
688+
oldAMCP: &AzureManagedControlPlane{
689+
Spec: AzureManagedControlPlaneSpec{
690+
DNSServiceIP: to.StringPtr("192.168.0.0"),
691+
Version: "v1.18.0",
692+
},
693+
},
694+
amcp: &AzureManagedControlPlane{
695+
Spec: AzureManagedControlPlaneSpec{
696+
DNSServiceIP: to.StringPtr("192.168.0.0"),
697+
Version: "v1.18.0",
698+
APIServerAccessProfile: &APIServerAccessProfile{
699+
EnablePrivateCluster: to.BoolPtr(true),
700+
},
701+
},
702+
},
703+
wantErr: true,
704+
},
705+
{
706+
name: "AzureManagedControlPlane AuthorizedIPRanges is mutable",
707+
oldAMCP: &AzureManagedControlPlane{
708+
Spec: AzureManagedControlPlaneSpec{
709+
DNSServiceIP: to.StringPtr("192.168.0.0"),
710+
Version: "v1.18.0",
711+
},
712+
},
713+
amcp: &AzureManagedControlPlane{
714+
Spec: AzureManagedControlPlaneSpec{
715+
DNSServiceIP: to.StringPtr("192.168.0.0"),
716+
Version: "v1.18.0",
717+
APIServerAccessProfile: &APIServerAccessProfile{
718+
AuthorizedIPRanges: []string{"192.168.0.1/32"},
719+
},
720+
},
721+
},
722+
wantErr: false,
723+
},
674724
}
675725
for _, tc := range tests {
676726
t.Run(tc.name, func(t *testing.T) {

0 commit comments

Comments
 (0)