sigs.k8s.io/cluster-api-provider-azure@v1.14.3/azure/services/roleassignments/roleassignments.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package roleassignments 18 19 import ( 20 "context" 21 22 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2" 23 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5" 24 "github.com/pkg/errors" 25 "sigs.k8s.io/cluster-api-provider-azure/azure" 26 "sigs.k8s.io/cluster-api-provider-azure/azure/services/async" 27 "sigs.k8s.io/cluster-api-provider-azure/azure/services/scalesets" 28 "sigs.k8s.io/cluster-api-provider-azure/azure/services/virtualmachines" 29 "sigs.k8s.io/cluster-api-provider-azure/util/tele" 30 ) 31 32 const serviceName = "roleassignments" 33 34 // RoleAssignmentScope defines the scope interface for a role assignment service. 35 type RoleAssignmentScope interface { 36 azure.AsyncStatusUpdater 37 azure.Authorizer 38 RoleAssignmentSpecs(principalID *string) []azure.ResourceSpecGetter 39 HasSystemAssignedIdentity() bool 40 RoleAssignmentResourceType() string 41 Name() string 42 ResourceGroup() string 43 } 44 45 // Service provides operations on Azure resources. 46 type Service struct { 47 Scope RoleAssignmentScope 48 virtualMachinesGetter async.Getter 49 async.Reconciler 50 virtualMachineScaleSetGetter async.Getter 51 } 52 53 // New creates a new service. 54 func New(scope RoleAssignmentScope) (*Service, error) { 55 client, err := newClient(scope) 56 if err != nil { 57 return nil, errors.Wrap(err, "failed to create roleassignments service") 58 } 59 scaleSetsClient, err := scalesets.NewClient(scope, scope.DefaultedAzureCallTimeout()) 60 if err != nil { 61 return nil, errors.Wrap(err, "failed to create scalesets service") 62 } 63 virtualMachinesClient, err := virtualmachines.NewClient(scope, scope.DefaultedAzureCallTimeout()) 64 if err != nil { 65 return nil, errors.Wrap(err, "failed to create virtualmachines service") 66 } 67 return &Service{ 68 Scope: scope, 69 virtualMachinesGetter: virtualMachinesClient, 70 virtualMachineScaleSetGetter: scaleSetsClient, 71 Reconciler: async.New[armauthorization.RoleAssignmentsClientCreateResponse, 72 armauthorization.RoleAssignmentsClientDeleteResponse](scope, client, nil), 73 }, nil 74 } 75 76 // Name returns the service name. 77 func (s *Service) Name() string { 78 return serviceName 79 } 80 81 // Reconcile idempotently creates or updates a role assignment. 82 func (s *Service) Reconcile(ctx context.Context) error { 83 ctx, log, done := tele.StartSpanWithLogger(ctx, "roleassignments.Service.Reconcile") 84 defer done() 85 86 ctx, cancel := context.WithTimeout(ctx, s.Scope.DefaultedAzureServiceReconcileTimeout()) 87 defer cancel() 88 89 log.V(2).Info("reconciling role assignment") 90 91 // Return early if the identity is not system assigned as there will be no 92 // role assignment spec in this case. 93 if !s.Scope.HasSystemAssignedIdentity() { 94 log.V(2).Info("no role assignment spec to reconcile") 95 return nil 96 } 97 98 var principalID *string 99 resourceType := s.Scope.RoleAssignmentResourceType() 100 switch resourceType { 101 case azure.VirtualMachine: 102 ID, err := s.getVMPrincipalID(ctx) 103 if err != nil { 104 return errors.Wrap(err, "failed to assign role to system assigned identity") 105 } 106 principalID = ID 107 case azure.VirtualMachineScaleSet: 108 ID, err := s.getVMSSPrincipalID(ctx) 109 if err != nil { 110 return errors.Wrap(err, "failed to assign role to system assigned identity") 111 } 112 principalID = ID 113 default: 114 return errors.Errorf("unexpected resource type %q. Expected one of [%s, %s]", resourceType, 115 azure.VirtualMachine, azure.VirtualMachineScaleSet) 116 } 117 118 for _, roleAssignmentSpec := range s.Scope.RoleAssignmentSpecs(principalID) { 119 log.V(2).Info("Creating role assignment") 120 if roleAssignmentSpec.ResourceName() == "" { 121 log.V(2).Info("RoleAssignmentName is empty. This is not expected and will cause this System Assigned Identity to have no permissions.") 122 } 123 _, err := s.CreateOrUpdateResource(ctx, roleAssignmentSpec, serviceName) 124 if err != nil { 125 return errors.Wrapf(err, "cannot assign role to %s system assigned identity", resourceType) 126 } 127 } 128 129 return nil 130 } 131 132 // getVMPrincipalID returns the VM principal ID. 133 func (s *Service) getVMPrincipalID(ctx context.Context) (*string, error) { 134 ctx, log, done := tele.StartSpanWithLogger(ctx, "roleassignments.Service.getVMPrincipalID") 135 defer done() 136 log.V(2).Info("fetching principal ID for VM") 137 spec := &virtualmachines.VMSpec{ 138 Name: s.Scope.Name(), 139 ResourceGroup: s.Scope.ResourceGroup(), 140 } 141 142 resultVMIface, err := s.virtualMachinesGetter.Get(ctx, spec) 143 if err != nil { 144 return nil, errors.Wrap(err, "failed to get principal ID for VM") 145 } 146 resultVM, ok := resultVMIface.(armcompute.VirtualMachine) 147 if !ok { 148 return nil, errors.Errorf("%T is not an armcompute.VirtualMachine", resultVMIface) 149 } 150 return resultVM.Identity.PrincipalID, nil 151 } 152 153 // getVMSSPrincipalID returns the VMSS principal ID. 154 func (s *Service) getVMSSPrincipalID(ctx context.Context) (*string, error) { 155 ctx, log, done := tele.StartSpanWithLogger(ctx, "roleassignments.Service.getVMPrincipalID") 156 defer done() 157 log.V(2).Info("fetching principal ID for VMSS") 158 spec := &scalesets.ScaleSetSpec{ 159 Name: s.Scope.Name(), 160 ResourceGroup: s.Scope.ResourceGroup(), 161 } 162 163 resultVMSSIface, err := s.virtualMachineScaleSetGetter.Get(ctx, spec) 164 if err != nil { 165 return nil, errors.Wrap(err, "failed to get principal ID for VMSS") 166 } 167 resultVMSS, ok := resultVMSSIface.(armcompute.VirtualMachineScaleSet) 168 if !ok { 169 return nil, errors.Errorf("%T is not an armcompute.VirtualMachineScaleSet", resultVMSSIface) 170 } 171 172 return resultVMSS.Identity.PrincipalID, nil 173 } 174 175 // Delete is a no-op as the role assignments get deleted as part of VM deletion. 176 func (s *Service) Delete(ctx context.Context) error { 177 _, _, done := tele.StartSpanWithLogger(ctx, "roleassignments.Service.Delete") 178 defer done() 179 return nil 180 } 181 182 // IsManaged returns always returns true as CAPZ does not support BYO role assignments. 183 func (s *Service) IsManaged(ctx context.Context) (bool, error) { 184 return true, nil 185 }