github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/ec2/iam.go (about)

     1  // Copyright 2021 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package ec2
     5  
     6  import (
     7  	stdcontext "context"
     8  	stderrors "errors"
     9  	"fmt"
    10  	"net/http"
    11  	"time"
    12  
    13  	"github.com/aws/aws-sdk-go-v2/aws"
    14  	awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
    15  	"github.com/aws/aws-sdk-go-v2/service/ec2"
    16  	ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
    17  	"github.com/aws/aws-sdk-go-v2/service/iam"
    18  	iamtypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
    19  	"github.com/juju/clock"
    20  	"github.com/juju/errors"
    21  	"github.com/juju/retry"
    22  
    23  	"github.com/juju/juju/core/instance"
    24  	"github.com/juju/juju/core/status"
    25  	"github.com/juju/juju/environs"
    26  	"github.com/juju/juju/environs/cloudspec"
    27  	"github.com/juju/juju/environs/context"
    28  	"github.com/juju/juju/environs/instances"
    29  	"github.com/juju/juju/environs/tags"
    30  )
    31  
    32  // instanceProfileClient is a subset interface of the ec2 client for attaching
    33  // Instance Profiles to ec2 machines.
    34  type instanceProfileClient interface {
    35  	AssociateIamInstanceProfile(stdcontext.Context, *ec2.AssociateIamInstanceProfileInput, ...func(*ec2.Options)) (*ec2.AssociateIamInstanceProfileOutput, error)
    36  	DescribeIamInstanceProfileAssociations(stdcontext.Context, *ec2.DescribeIamInstanceProfileAssociationsInput, ...func(*ec2.Options)) (*ec2.DescribeIamInstanceProfileAssociationsOutput, error)
    37  }
    38  
    39  // IAMClient is a subset interface of the AWS IAM client. This interface aims
    40  // to define the small set of what Juju's needs from the larger client.
    41  type IAMClient interface {
    42  	// STOP!!
    43  	// Are you about to add a new function to this interface?
    44  	// If so please make sure you update Juju permission policy on discourse
    45  	// here https://discourse.charmhub.io/t/juju-aws-permissions/5307
    46  	// We must keep this policy inline with our usage for operators that are
    47  	// using very strict permissions for Juju.
    48  	//
    49  	// You must also update the controllerRolePolicy document found in
    50  	// iam_docs.go.
    51  	AddRoleToInstanceProfile(stdcontext.Context, *iam.AddRoleToInstanceProfileInput, ...func(*iam.Options)) (*iam.AddRoleToInstanceProfileOutput, error)
    52  	CreateInstanceProfile(stdcontext.Context, *iam.CreateInstanceProfileInput, ...func(*iam.Options)) (*iam.CreateInstanceProfileOutput, error)
    53  	CreateRole(stdcontext.Context, *iam.CreateRoleInput, ...func(*iam.Options)) (*iam.CreateRoleOutput, error)
    54  	DeleteInstanceProfile(stdcontext.Context, *iam.DeleteInstanceProfileInput, ...func(*iam.Options)) (*iam.DeleteInstanceProfileOutput, error)
    55  	DeleteRole(stdcontext.Context, *iam.DeleteRoleInput, ...func(*iam.Options)) (*iam.DeleteRoleOutput, error)
    56  	DeleteRolePolicy(stdcontext.Context, *iam.DeleteRolePolicyInput, ...func(*iam.Options)) (*iam.DeleteRolePolicyOutput, error)
    57  	GetInstanceProfile(stdcontext.Context, *iam.GetInstanceProfileInput, ...func(*iam.Options)) (*iam.GetInstanceProfileOutput, error)
    58  	GetRole(stdcontext.Context, *iam.GetRoleInput, ...func(*iam.Options)) (*iam.GetRoleOutput, error)
    59  	ListInstanceProfiles(stdcontext.Context, *iam.ListInstanceProfilesInput, ...func(*iam.Options)) (*iam.ListInstanceProfilesOutput, error)
    60  	ListRolePolicies(stdcontext.Context, *iam.ListRolePoliciesInput, ...func(*iam.Options)) (*iam.ListRolePoliciesOutput, error)
    61  	ListRoles(stdcontext.Context, *iam.ListRolesInput, ...func(*iam.Options)) (*iam.ListRolesOutput, error)
    62  	PutRolePolicy(stdcontext.Context, *iam.PutRolePolicyInput, ...func(*iam.Options)) (*iam.PutRolePolicyOutput, error)
    63  	RemoveRoleFromInstanceProfile(stdcontext.Context, *iam.RemoveRoleFromInstanceProfileInput, ...func(*iam.Options)) (*iam.RemoveRoleFromInstanceProfileOutput, error)
    64  }
    65  
    66  // IAMClientFunc defines a type that can generate an AWS IAMClient from a
    67  // provided cloudspec.
    68  type IAMClientFunc = func(stdcontext.Context, cloudspec.CloudSpec, ...ClientOption) (IAMClient, error)
    69  
    70  const (
    71  	// setProfileAssociationDelay is the delay between retry attempts when.
    72  	setProfileAssociationDelay = time.Second * 15
    73  
    74  	// setProfileAssociationMaxAttempt is the maxium number of attempts before
    75  	// giving up on iam profile association.
    76  	setProfileAssociationMaxAttempt = 5
    77  
    78  	// setProfileDelay is the delay between retry attempts when setting an
    79  	// instances iam profile.
    80  	setProfileDelay = time.Second * 5
    81  
    82  	// setProfileMaxAttempt is the maxium number of attempts before giving up
    83  	// on setting an instances iam profile.
    84  	setProfileMaxAttempt = 5
    85  )
    86  
    87  // iamClientFunc implements the IAMClientFunc type and is used internally by
    88  // Juju for creating an IAM client.
    89  func iamClientFunc(
    90  	ctx stdcontext.Context,
    91  	spec cloudspec.CloudSpec,
    92  	clientOptions ...ClientOption,
    93  ) (IAMClient, error) {
    94  	cfg, err := configFromCloudSpec(ctx, spec, clientOptions...)
    95  	if err != nil {
    96  		return nil, errors.Annotate(err, "building aws config from cloudspec")
    97  	}
    98  	return iam.NewFromConfig(cfg), nil
    99  }
   100  
   101  // controllerPath returns an AWS path to use for IAM resources based on the
   102  // controller UUID
   103  func controllerPath(controllerUUID string) string {
   104  	return fmt.Sprintf("/juju/controller/%s/", controllerUUID)
   105  }
   106  
   107  // deleteInstanceProfile is a convience method for removing instance profile by
   108  // first detaching all roles from the profile then deleting.
   109  func deleteInstanceProfile(
   110  	ctx stdcontext.Context,
   111  	client IAMClient,
   112  	instanceProfile iamtypes.InstanceProfile,
   113  ) error {
   114  	for _, role := range instanceProfile.Roles {
   115  		_, err := client.RemoveRoleFromInstanceProfile(ctx, &iam.RemoveRoleFromInstanceProfileInput{
   116  			InstanceProfileName: instanceProfile.InstanceProfileName,
   117  			RoleName:            role.RoleName,
   118  		})
   119  
   120  		if err != nil {
   121  			return errors.Annotatef(err, "removing role %q from instance profile", *role.RoleName)
   122  		}
   123  	}
   124  
   125  	_, err := client.DeleteInstanceProfile(ctx, &iam.DeleteInstanceProfileInput{
   126  		InstanceProfileName: instanceProfile.InstanceProfileName,
   127  	})
   128  
   129  	return errors.Trace(err)
   130  }
   131  
   132  // deleteRole is a convience method for delete a role and it's associated
   133  // inline policies.
   134  func deleteRole(
   135  	ctx stdcontext.Context,
   136  	client IAMClient,
   137  	roleName string,
   138  ) error {
   139  
   140  	var (
   141  		inlinePolicyNames = []string{}
   142  		marker            *string
   143  	)
   144  	for {
   145  		res, err := client.ListRolePolicies(ctx, &iam.ListRolePoliciesInput{
   146  			Marker:   marker,
   147  			RoleName: aws.String(roleName),
   148  		})
   149  
   150  		if err != nil {
   151  			return errors.Annotatef(err, "listing inline policies for role %q", roleName)
   152  		}
   153  
   154  		inlinePolicyNames = append(inlinePolicyNames, res.PolicyNames...)
   155  		if !res.IsTruncated {
   156  			break
   157  		}
   158  		marker = res.Marker
   159  	}
   160  
   161  	for _, policyName := range inlinePolicyNames {
   162  		_, err := client.DeleteRolePolicy(ctx, &iam.DeleteRolePolicyInput{
   163  			PolicyName: aws.String(policyName),
   164  			RoleName:   aws.String(roleName),
   165  		})
   166  		if err != nil {
   167  			return errors.Annotatef(err, "delete inline policy %q", policyName)
   168  		}
   169  	}
   170  
   171  	_, err := client.DeleteRole(ctx, &iam.DeleteRoleInput{
   172  		RoleName: aws.String(roleName),
   173  	})
   174  
   175  	return errors.Trace(err)
   176  }
   177  
   178  // ensureControllerInstanceProfile ensures that a controller Instance Profile
   179  // has been created for the supplied controller name in the specified AWS cloud.
   180  func ensureControllerInstanceProfile(
   181  	ctx stdcontext.Context,
   182  	client IAMClient,
   183  	controllerName,
   184  	controllerUUID string,
   185  ) (*iamtypes.InstanceProfile, []func(), error) {
   186  	role, cleanups, err := ensureControllerInstanceRole(ctx, client, controllerName, controllerUUID)
   187  	if err != nil {
   188  		return nil, cleanups, err
   189  	}
   190  
   191  	profileName := fmt.Sprintf("juju-controller-%s", controllerName)
   192  	res, err := client.CreateInstanceProfile(ctx, &iam.CreateInstanceProfileInput{
   193  		InstanceProfileName: aws.String(profileName),
   194  		Tags: []iamtypes.Tag{
   195  			{
   196  				Key:   aws.String(tags.JujuController),
   197  				Value: aws.String(controllerUUID),
   198  			},
   199  		},
   200  		Path: aws.String(controllerPath(controllerUUID)),
   201  	})
   202  	if err != nil {
   203  		var alreadyExistsErr *iamtypes.EntityAlreadyExistsException
   204  		if stderrors.As(err, &alreadyExistsErr) {
   205  			// Instance Profile already exists so we don't need todo anything. Let just find it
   206  			ip, err := findInstanceProfileFromName(ctx, client, profileName)
   207  			return ip, cleanups, err
   208  		}
   209  		// Some other error that we can't recover from.
   210  		return nil, cleanups, errors.Annotate(err, "creating controller instance profile")
   211  	}
   212  
   213  	cleanups = append([]func(){func() {
   214  		_, err := client.DeleteInstanceProfile(ctx, &iam.DeleteInstanceProfileInput{
   215  			InstanceProfileName: res.InstanceProfile.InstanceProfileName,
   216  		})
   217  		if err != nil {
   218  			logger.Errorf("cleanup delete instance profile %q: %v",
   219  				*res.InstanceProfile.InstanceProfileName,
   220  				err)
   221  		}
   222  	}}, cleanups...)
   223  
   224  	_, err = client.AddRoleToInstanceProfile(ctx, &iam.AddRoleToInstanceProfileInput{
   225  		InstanceProfileName: res.InstanceProfile.InstanceProfileName,
   226  		RoleName:            role.RoleName,
   227  	})
   228  
   229  	if err != nil {
   230  		return nil, cleanups, errors.Annotatef(
   231  			err,
   232  			"attaching role %s to instance profile %s",
   233  			*role.RoleName,
   234  			*res.InstanceProfile.InstanceProfileName,
   235  		)
   236  	}
   237  
   238  	cleanups = append([]func(){func() {
   239  		_, err := client.RemoveRoleFromInstanceProfile(ctx, &iam.RemoveRoleFromInstanceProfileInput{
   240  			InstanceProfileName: res.InstanceProfile.InstanceProfileName,
   241  			RoleName:            role.RoleName,
   242  		})
   243  		if err != nil {
   244  			logger.Errorf("cleanup remove role %q from instance profile %q: %v",
   245  				*role.RoleName,
   246  				*res.InstanceProfile.InstanceProfileName,
   247  				err)
   248  		}
   249  	}}, cleanups...)
   250  
   251  	return res.InstanceProfile, cleanups, nil
   252  }
   253  
   254  func ensureControllerInstanceRole(
   255  	ctx stdcontext.Context,
   256  	client IAMClient,
   257  	controllerName,
   258  	controllerUUID string,
   259  ) (*iamtypes.Role, []func(), error) {
   260  	roleName := fmt.Sprintf("juju-controller-%s", controllerName)
   261  	cleanups := []func(){}
   262  	res, err := client.CreateRole(ctx, &iam.CreateRoleInput{
   263  		AssumeRolePolicyDocument: aws.String(controllerRoleAssumePolicy),
   264  		RoleName:                 aws.String(roleName),
   265  		Description:              aws.String(fmt.Sprintf("juju role for controller %s", controllerName)),
   266  		Path:                     aws.String(controllerPath(controllerUUID)),
   267  		Tags: []iamtypes.Tag{
   268  			{
   269  				Key:   aws.String(tags.JujuController),
   270  				Value: aws.String(controllerUUID),
   271  			},
   272  		},
   273  	})
   274  
   275  	if err != nil {
   276  		var alreadyExistsErr *iamtypes.EntityAlreadyExistsException
   277  		if stderrors.As(err, &alreadyExistsErr) {
   278  			// Role already exists so we don't need todo anything. Let just find it
   279  			r, err := findRoleFromName(ctx, client, roleName)
   280  			return r, cleanups, err
   281  		}
   282  		// Some other error that we can't recover from.
   283  		return nil, cleanups, errors.Annotate(err, "creating controller instance role")
   284  	}
   285  
   286  	cleanups = append(cleanups, func() {
   287  		_, err := client.DeleteRole(ctx, &iam.DeleteRoleInput{
   288  			RoleName: res.Role.RoleName,
   289  		})
   290  		if err != nil {
   291  			logger.Errorf("cleanup delete role %q: %v",
   292  				*res.Role.RoleName,
   293  				err)
   294  		}
   295  	})
   296  
   297  	_, err = client.PutRolePolicy(ctx, &iam.PutRolePolicyInput{
   298  		PolicyDocument: aws.String(controllerRolePolicy),
   299  		PolicyName:     aws.String(roleName),
   300  		RoleName:       res.Role.RoleName,
   301  	})
   302  
   303  	if err != nil {
   304  		return nil, cleanups, errors.Annotatef(err, "attaching role %s policy", *res.Role.RoleName)
   305  	}
   306  
   307  	cleanups = append([]func(){func() {
   308  		_, err := client.DeleteRolePolicy(ctx, &iam.DeleteRolePolicyInput{
   309  			PolicyName: aws.String(roleName),
   310  			RoleName:   res.Role.RoleName,
   311  		})
   312  		if err != nil {
   313  			logger.Errorf("cleanup delete role %q policy %q: %v",
   314  				*res.Role.RoleName,
   315  				roleName,
   316  				err)
   317  		}
   318  	}}, cleanups...)
   319  
   320  	return res.Role, cleanups, nil
   321  }
   322  
   323  // findInstanceProfileForName is responsible for finding the concrete instance
   324  // profile for a supplied name. This is used to subsequently fetch the ARN of
   325  // the InstanceProfile.
   326  func findInstanceProfileFromName(
   327  	ctx stdcontext.Context,
   328  	client IAMClient,
   329  	name string,
   330  ) (*iamtypes.InstanceProfile, error) {
   331  	res, err := client.GetInstanceProfile(ctx, &iam.GetInstanceProfileInput{
   332  		InstanceProfileName: &name,
   333  	})
   334  
   335  	if err != nil {
   336  		if isAWSHTTPErrorCode(err, http.StatusNotFound) {
   337  			return nil, errors.NotFoundf("instance profile %q not found", name)
   338  		}
   339  		return nil, errors.Annotatef(err, "finding instance profile for name %s", name)
   340  	}
   341  
   342  	return res.InstanceProfile, nil
   343  }
   344  
   345  func listInstanceProfilesForController(
   346  	ctx stdcontext.Context,
   347  	client IAMClient,
   348  	controllerUUID string,
   349  ) ([]iamtypes.InstanceProfile, error) {
   350  	var (
   351  		marker *string
   352  		rval   = []iamtypes.InstanceProfile{}
   353  	)
   354  	for {
   355  		res, err := client.ListInstanceProfiles(ctx, &iam.ListInstanceProfilesInput{
   356  			Marker:     marker,
   357  			PathPrefix: aws.String(controllerPath(controllerUUID)),
   358  		})
   359  
   360  		if err != nil {
   361  			if isAWSHTTPErrorCode(err, http.StatusForbidden) {
   362  				return rval, errors.Unauthorizedf("listing instance profiles for controllerUUID %s", controllerUUID)
   363  			}
   364  			return rval, errors.Annotatef(
   365  				err,
   366  				"listing roles with controller prefix %q",
   367  				controllerPath(controllerUUID))
   368  		}
   369  
   370  		rval = append(rval, res.InstanceProfiles...)
   371  		if !res.IsTruncated {
   372  			break
   373  		}
   374  		marker = res.Marker
   375  	}
   376  	return rval, nil
   377  }
   378  
   379  func listRolesForController(
   380  	ctx stdcontext.Context,
   381  	client IAMClient,
   382  	controllerUUID string,
   383  ) ([]iamtypes.Role, error) {
   384  	var (
   385  		marker *string
   386  		rval   = []iamtypes.Role{}
   387  	)
   388  	for {
   389  		res, err := client.ListRoles(ctx, &iam.ListRolesInput{
   390  			Marker:     marker,
   391  			PathPrefix: aws.String(controllerPath(controllerUUID)),
   392  		})
   393  
   394  		if err != nil {
   395  			if isAWSHTTPErrorCode(err, http.StatusForbidden) {
   396  				return rval, errors.Unauthorizedf(
   397  					"listing roles for controllerUUID %s",
   398  					controllerUUID)
   399  			}
   400  			return rval, errors.Annotatef(
   401  				err,
   402  				"listing roles with controller prefix %q",
   403  				controllerPath(controllerUUID))
   404  		}
   405  
   406  		rval = append(rval, res.Roles...)
   407  		if !res.IsTruncated {
   408  			break
   409  		}
   410  		marker = res.Marker
   411  	}
   412  	return rval, nil
   413  }
   414  
   415  func isAWSHTTPErrorCode(err error, statusCode int) bool {
   416  	var opHTTPErr *awshttp.ResponseError
   417  	if stderrors.As(err, &opHTTPErr) && opHTTPErr.HTTPStatusCode() == statusCode {
   418  		return true
   419  	}
   420  	return false
   421  }
   422  
   423  func findRoleFromName(
   424  	ctx stdcontext.Context,
   425  	client IAMClient,
   426  	name string,
   427  ) (*iamtypes.Role, error) {
   428  	res, err := client.GetRole(ctx, &iam.GetRoleInput{
   429  		RoleName: aws.String(name),
   430  	})
   431  
   432  	if err != nil {
   433  		if isAWSHTTPErrorCode(err, http.StatusNotFound) {
   434  			return nil, errors.NotFoundf("role %q not found", name)
   435  		}
   436  		return nil, errors.Annotatef(err, "finding role for name %s", name)
   437  	}
   438  
   439  	return res.Role, nil
   440  }
   441  
   442  // setInstanceProfileWithWait sets the instnace profile for a given instance
   443  // blocking until the instance is in a running state where the profile can be
   444  // applied. This function also waits for the instance profile to be associated
   445  // with the instance.
   446  func setInstanceProfileWithWait(
   447  	ctx context.ProviderCallContext,
   448  	client instanceProfileClient,
   449  	profile *iamtypes.InstanceProfile,
   450  	inst instances.Instance,
   451  	instLister environs.InstanceLister,
   452  ) error {
   453  	var association *ec2.AssociateIamInstanceProfileOutput
   454  
   455  	err := retry.Call(retry.CallArgs{
   456  		Attempts: setProfileMaxAttempt,
   457  		Delay:    setProfileDelay,
   458  		Func: func() (err error) {
   459  			association, err = setInstanceProfile(ctx, client, profile, inst, instLister)
   460  			return err
   461  		},
   462  		IsFatalError: func(err error) bool {
   463  			return !errors.IsNotProvisioned(err)
   464  		},
   465  		BackoffFunc: retry.DoubleDelay,
   466  		Clock:       clock.WallClock,
   467  		Stop:        ctx.Done(),
   468  	})
   469  
   470  	if err != nil {
   471  		return errors.Annotatef(
   472  			err,
   473  			"setting instance profile %s for instance %s",
   474  			*profile.InstanceProfileName,
   475  			inst.Id(),
   476  		)
   477  	}
   478  
   479  	// We need to wait here till the instance profile is associated to the
   480  	// instance.
   481  	return retry.Call(retry.CallArgs{
   482  		Attempts: setProfileAssociationMaxAttempt,
   483  		Delay:    setProfileAssociationDelay,
   484  		Func: func() error {
   485  			return IsInstanceProfileAssociated(
   486  				ctx,
   487  				client,
   488  				*association.IamInstanceProfileAssociation.AssociationId,
   489  				*association.IamInstanceProfileAssociation.InstanceId,
   490  			)
   491  		},
   492  		IsFatalError: func(err error) bool {
   493  			return !errors.IsNotProvisioned(err)
   494  		},
   495  		BackoffFunc: retry.DoubleDelay,
   496  		Clock:       clock.WallClock,
   497  		Stop:        ctx.Done(),
   498  	})
   499  }
   500  
   501  func IsInstanceProfileAssociated(
   502  	ctx context.ProviderCallContext,
   503  	client instanceProfileClient,
   504  	associationId,
   505  	instanceId string,
   506  ) error {
   507  	rval, err := client.DescribeIamInstanceProfileAssociations(
   508  		ctx,
   509  		&ec2.DescribeIamInstanceProfileAssociationsInput{
   510  			AssociationIds: []string{
   511  				associationId,
   512  			},
   513  			Filters: []ec2types.Filter{
   514  				{
   515  					Name: aws.String("instance-id"),
   516  					Values: []string{
   517  						instanceId,
   518  					},
   519  				},
   520  			},
   521  		},
   522  	)
   523  
   524  	if err != nil {
   525  		return errors.Annotatef(
   526  			err,
   527  			"describing Instance Profile association %s",
   528  			associationId,
   529  		)
   530  	}
   531  
   532  	// We have only asked for one association from aws so getting back
   533  	// more then one result doesn't make sense here so lets error. This
   534  	// condition should never be hit.
   535  	if len(rval.IamInstanceProfileAssociations) != 1 {
   536  		return errors.Errorf("expected 1 IAM Instance Profile association, got %d", len(rval.IamInstanceProfileAssociations))
   537  	}
   538  
   539  	switch rval.IamInstanceProfileAssociations[0].State {
   540  	case ec2types.IamInstanceProfileAssociationStateAssociated:
   541  		return nil
   542  	case ec2types.IamInstanceProfileAssociationStateAssociating:
   543  		return errors.NotProvisionedf("IAM Instance Profile association %s", associationId)
   544  	// This should only ever be hit if the association is being
   545  	// Disassociated. This should never happen.
   546  	default:
   547  		return errors.NotSupportedf(" IAM Instance Profile association %s state %s",
   548  			associationId,
   549  			rval.IamInstanceProfileAssociations[0].State,
   550  		)
   551  	}
   552  }
   553  
   554  // setInstanceProfile sets the instance profile for a given instance. This
   555  // function first checks to see that the supplied instance is in a running
   556  // state first otherwise a Juju NotProvisioned error returned. Use
   557  // setInstanceProfileWithWait to block on the instance status being running.
   558  func setInstanceProfile(
   559  	ctx context.ProviderCallContext,
   560  	client instanceProfileClient,
   561  	profile *iamtypes.InstanceProfile,
   562  	inst instances.Instance,
   563  	instLister environs.InstanceLister,
   564  ) (*ec2.AssociateIamInstanceProfileOutput, error) {
   565  	rInst, err := instLister.Instances(ctx, []instance.Id{inst.Id()})
   566  	if err != nil {
   567  		return nil, errors.Annotatef(err, "listing instance with id %s", inst.Id())
   568  	}
   569  	if len(rInst) != 1 {
   570  		return nil, errors.Errorf("expected 1 instance for id %s got %d", inst.Id(), len(rInst))
   571  	}
   572  
   573  	if rInst[0].Status(ctx).Status != status.Running {
   574  		return nil, errors.NotProvisionedf("instance %s is not running", inst.Id())
   575  	}
   576  
   577  	instanceProfileInput := ec2.AssociateIamInstanceProfileInput{
   578  		IamInstanceProfile: &ec2types.IamInstanceProfileSpecification{
   579  			Arn:  profile.Arn,
   580  			Name: profile.InstanceProfileName,
   581  		},
   582  		InstanceId: aws.String(string(inst.Id())),
   583  	}
   584  
   585  	rval, err := client.AssociateIamInstanceProfile(ctx, &instanceProfileInput)
   586  	if err != nil {
   587  		return nil, errors.Annotatef(
   588  			err,
   589  			"attaching instance profile %s to instance %s",
   590  			*profile.InstanceProfileName,
   591  			inst.Id())
   592  	}
   593  
   594  	return rval, nil
   595  }