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  }