Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

amazon-ssm-agent service fails connecting to SSM due to eventual consistency #503

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions builder/common/helper_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,10 @@ func DestroyAMIs(imageids []*string, ec2conn *ec2.EC2) error {
}
return nil
}

func AwsPartition(isRestricted bool) string {
if isRestricted {
return "aws-cn"
}
return "aws"
}
10 changes: 4 additions & 6 deletions builder/common/run_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package common

import (
"fmt"
"log"
"net"
"os"
"regexp"
Expand Down Expand Up @@ -783,10 +784,8 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
msg := fmt.Errorf(`session_manager connectivity is not supported with the "winrm" communicator; please use "ssh"`)
errs = append(errs, msg)
}

if c.IamInstanceProfile == "" && c.TemporaryIamInstanceProfilePolicyDocument == nil {
msg := fmt.Errorf(`no iam_instance_profile defined; session_manager connectivity requires a valid instance profile with AmazonSSMManagedInstanceCore permissions. Alternatively a temporary_iam_instance_profile_policy_document can be used.`)
errs = append(errs, msg)
if c.IamInstanceProfile != "" {
log.Printf("[WARNING] (aws): session_manager connectivity requires a valid instance profile with AmazonSSMManagedInstanceCore permissions. Please make sure iam_instance_profile has proper permissions.")
}
}

Expand Down Expand Up @@ -940,8 +939,7 @@ func (c *RunConfig) IsSpotInstance() bool {
}

func (c *RunConfig) SSMAgentEnabled() bool {
hasIamInstanceProfile := c.IamInstanceProfile != "" || c.TemporaryIamInstanceProfilePolicyDocument != nil
return c.SSHInterface == "session_manager" && hasIamInstanceProfile
return c.SSHInterface == "session_manager"
}

// IsBurstableInstanceType checks if the InstanceType for the config is one
Expand Down
176 changes: 108 additions & 68 deletions builder/common/step_iam_instance_profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,17 @@ import (
"github.com/hashicorp/packer-plugin-sdk/uuid"
)

const (
AmazonSSMManagedInstanceCorePolicyArnPart = "iam::aws:policy/AmazonSSMManagedInstanceCore"
)

type StepIamInstanceProfile struct {
PollingConfig *AWSPollingConfig
IamInstanceProfile string
SkipProfileValidation bool
TemporaryIamInstanceProfilePolicyDocument *PolicyDocument
SSMAgentEnabled bool
IsRestricted bool
createdInstanceProfileName string
createdRoleName string
createdPolicyName string
Expand All @@ -30,6 +36,17 @@ type StepIamInstanceProfile struct {
Ctx interpolate.Context
}

func handleError(state multistep.StateBag, err error, message ...string) multistep.StepAction {
log.Printf("[DEBUG] %s", err.Error())
state.Get("ui").(packersdk.Ui).Error(err.Error())
if len(message) > 0 {
state.Put("error", fmt.Errorf("%s: %s", message[0], err))
} else {
state.Put("error", err)
}
return multistep.ActionHalt
}

func (s *StepIamInstanceProfile) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
iamsvc := state.Get("iam").(*iam.IAM)
ui := state.Get("ui").(packersdk.Ui)
Expand All @@ -44,79 +61,55 @@ func (s *StepIamInstanceProfile) Run(ctx context.Context, state multistep.StateB
},
)
if err != nil {
err := fmt.Errorf("Couldn't find specified instance profile: %s", err)
log.Printf("[DEBUG] %s", err.Error())
state.Put("error", err)
return multistep.ActionHalt
return handleError(state, err, "Couldn't find specified instance profile: %s")
}
}
log.Printf("Using specified instance profile: %v", s.IamInstanceProfile)
state.Put("iamInstanceProfile", s.IamInstanceProfile)
return multistep.ActionContinue
}

if s.TemporaryIamInstanceProfilePolicyDocument != nil {
if s.SSMAgentEnabled || s.TemporaryIamInstanceProfilePolicyDocument != nil {
// Create the profile
profileName := fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())

policy, err := json.Marshal(s.TemporaryIamInstanceProfilePolicyDocument)
if err != nil {
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
}

ui.Say(fmt.Sprintf("Creating temporary instance profile for this instance: %s", profileName))
ui.Sayf("Creating temporary instance profile for this instance: %s", profileName)

region := state.Get("region").(*string)
iamProfileTags, err := TagMap(s.Tags).IamTags(s.Ctx, *region, state)
if err != nil {
err := fmt.Errorf("Error creating IAM tags: %s", err)
state.Put("error", err)
return multistep.ActionHalt
}
profileResp, err := iamsvc.CreateInstanceProfile(&iam.CreateInstanceProfileInput{
InstanceProfileName: aws.String(profileName),
Tags: iamProfileTags,
})
if err != nil {
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
return handleError(state, err, "Error creating IAM tags")
}
s.createdInstanceProfileName = aws.StringValue(profileResp.InstanceProfile.InstanceProfileName)

log.Printf("[DEBUG] Waiting for temporary instance profile: %s", s.createdInstanceProfileName)
err = iamsvc.WaitUntilInstanceProfileExists(&iam.GetInstanceProfileInput{
InstanceProfileName: aws.String(s.createdInstanceProfileName),
})

if err == nil {
log.Printf("[DEBUG] Found instance profile %s", s.createdInstanceProfileName)
} else {
err := fmt.Errorf("Timed out waiting for instance profile %s: %s", s.createdInstanceProfileName, err)
log.Printf("[DEBUG] %s", err.Error())
state.Put("error", err)
return multistep.ActionHalt
ui.Sayf("Creating temporary role for this instance: %s", profileName)
service := "ec2.amazonaws.com"
if s.IsRestricted {
service = "ec2.amazonaws.com.cn"
}

ui.Say(fmt.Sprintf("Creating temporary role for this instance: %s", profileName))

trustPolicy := fmt.Sprintf(`{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "%s"
},
"Action": "sts:AssumeRole"
}
]
}`, service)
roleResp, err := iamsvc.CreateRole(&iam.CreateRoleInput{
RoleName: aws.String(profileName),
Description: aws.String("Temporary role for Packer"),
AssumeRolePolicyDocument: aws.String("{\"Version\": \"2012-10-17\",\"Statement\": [{\"Effect\": \"Allow\",\"Principal\": {\"Service\": \"ec2.amazonaws.com\"},\"Action\": \"sts:AssumeRole\"}]}"),
AssumeRolePolicyDocument: aws.String(trustPolicy),
Tags: iamProfileTags,
})
if err != nil {
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
return handleError(state, err, "Error creating role")
}
s.createdRoleName = *roleResp.Role.RoleName

s.createdRoleName = aws.StringValue(roleResp.Role.RoleName)

log.Printf("[DEBUG] Waiting for temporary role: %s", s.createdInstanceProfileName)
log.Printf("[DEBUG] Waiting for temporary role: %s", s.createdRoleName)
err = iamsvc.WaitUntilRoleExistsWithContext(
aws.BackgroundContext(),
&iam.GetRoleInput{
Expand All @@ -127,39 +120,80 @@ func (s *StepIamInstanceProfile) Run(ctx context.Context, state multistep.StateB
if err == nil {
log.Printf("[DEBUG] Found temporary role %s", s.createdRoleName)
} else {
err := fmt.Errorf("Timed out waiting for temporary role %s: %s", s.createdRoleName, err)
log.Printf("[DEBUG] %s", err.Error())
state.Put("error", err)
return multistep.ActionHalt
return handleError(state, err, fmt.Sprintf("Timed out waiting for temporary role %s", s.createdRoleName))
}

ui.Say(fmt.Sprintf("Attaching policy to the temporary role: %s", profileName))
ui.Sayf("Attaching policy to the temporary role: %s", profileName)

if s.TemporaryIamInstanceProfilePolicyDocument != nil {
inlinePolicyJSON, err := json.Marshal(s.TemporaryIamInstanceProfilePolicyDocument)
if err != nil {
return handleError(state, err, "Error parsing policy document")
}
_, err = iamsvc.PutRolePolicy(&iam.PutRolePolicyInput{
RoleName: aws.String(s.createdRoleName),
PolicyName: aws.String(profileName),
PolicyDocument: aws.String(string(inlinePolicyJSON)),
})
if err != nil {
return handleError(state, err, "Error attaching policy to role")
}
s.createdPolicyName = profileName
}
if s.SSMAgentEnabled {
ssmPolicyArn := aws.String(fmt.Sprintf("arn:%s:%s", AwsPartition(s.IsRestricted), AmazonSSMManagedInstanceCorePolicyArnPart))
_, err = iamsvc.AttachRolePolicy(&iam.AttachRolePolicyInput{
PolicyArn: ssmPolicyArn,
RoleName: aws.String(s.createdRoleName),
})
if err != nil {
return handleError(state, err, "Error attaching AmazonSSMManagedInstanceCore policy to role")
}
log.Printf("[DEBUG] Waiting for AmazonSSMManagedInstanceCore attached policy ready")
err = iamsvc.WaitUntilPolicyExistsWithContext(
aws.BackgroundContext(),
&iam.GetPolicyInput{
PolicyArn: ssmPolicyArn,
},
s.PollingConfig.getWaiterOptions()...,
)
if err == nil {
log.Printf("[DEBUG] Found AmazonSSMManagedInstanceCore attached policy in %s", s.createdRoleName)
} else {
return handleError(state, err, fmt.Sprintf("Timed out waiting for AmazonSSMManagedInstanceCore attached policy in %s", s.createdRoleName))
}
}

_, err = iamsvc.PutRolePolicy(&iam.PutRolePolicyInput{
RoleName: roleResp.Role.RoleName,
PolicyName: aws.String(profileName),
PolicyDocument: aws.String(string(policy)),
profileResp, err := iamsvc.CreateInstanceProfile(&iam.CreateInstanceProfileInput{
InstanceProfileName: aws.String(profileName),
Tags: iamProfileTags,
})
if err != nil {
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
return handleError(state, err, "Error creating instance profile")
}
s.createdInstanceProfileName = *profileResp.InstanceProfile.InstanceProfileName

s.createdPolicyName = aws.StringValue(roleResp.Role.RoleName)
log.Printf("[DEBUG] Waiting for temporary instance profile: %s", s.createdInstanceProfileName)
err = iamsvc.WaitUntilInstanceProfileExists(&iam.GetInstanceProfileInput{
InstanceProfileName: aws.String(s.createdInstanceProfileName),
})

if err == nil {
log.Printf("[DEBUG] Found instance profile %s", s.createdInstanceProfileName)
} else {
return handleError(state, err, fmt.Sprintf("Timed out waiting for instance profile %s", s.createdInstanceProfileName))
}

_, err = iamsvc.AddRoleToInstanceProfile(&iam.AddRoleToInstanceProfileInput{
RoleName: roleResp.Role.RoleName,
InstanceProfileName: profileResp.InstanceProfile.InstanceProfileName,
InstanceProfileName: aws.String(s.createdInstanceProfileName),
RoleName: aws.String(s.createdRoleName),
})
if err != nil {
ui.Error(err.Error())
state.Put("error", err)
return multistep.ActionHalt
return handleError(state, err, "Error attaching role to instance profile")
}

s.roleIsAttached = true
state.Put("iamInstanceProfile", aws.StringValue(profileResp.InstanceProfile.InstanceProfileName))
state.Put("iamInstanceProfile", s.createdInstanceProfileName)
}

return multistep.ActionContinue
Expand All @@ -170,9 +204,15 @@ func (s *StepIamInstanceProfile) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packersdk.Ui)
var err error

if s.roleIsAttached == true {
if s.roleIsAttached {
ui.Say("Detaching temporary role from instance profile...")

if s.SSMAgentEnabled {
_, _ = iamsvc.DetachRolePolicy(&iam.DetachRolePolicyInput{
PolicyArn: aws.String(fmt.Sprintf("arn:%s:%s", AwsPartition(s.IsRestricted), AmazonSSMManagedInstanceCorePolicyArnPart)),
RoleName: aws.String(s.createdRoleName),
})
}
_, err := iamsvc.RemoveRoleFromInstanceProfile(&iam.RemoveRoleFromInstanceProfileInput{
InstanceProfileName: aws.String(s.createdInstanceProfileName),
RoleName: aws.String(s.createdRoleName),
Expand Down
8 changes: 5 additions & 3 deletions builder/ebs/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,9 +340,11 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
Ctx: b.config.ctx,
},
&awscommon.StepIamInstanceProfile{
PollingConfig: b.config.PollingConfig,
IamInstanceProfile: b.config.IamInstanceProfile,
SkipProfileValidation: b.config.SkipProfileValidation,
PollingConfig: b.config.PollingConfig,
IamInstanceProfile: b.config.IamInstanceProfile,
SkipProfileValidation: b.config.SkipProfileValidation,
SSMAgentEnabled: b.config.SSMAgentEnabled(),
IsRestricted: b.config.IsChinaCloud(),
TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument,
Tags: b.config.RunTags,
Ctx: b.config.ctx,
Expand Down
8 changes: 5 additions & 3 deletions builder/ebssurrogate/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,9 +424,11 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
Ctx: b.config.ctx,
},
&awscommon.StepIamInstanceProfile{
PollingConfig: b.config.PollingConfig,
IamInstanceProfile: b.config.IamInstanceProfile,
SkipProfileValidation: b.config.SkipProfileValidation,
PollingConfig: b.config.PollingConfig,
IamInstanceProfile: b.config.IamInstanceProfile,
SkipProfileValidation: b.config.SkipProfileValidation,
SSMAgentEnabled: b.config.SSMAgentEnabled(),
IsRestricted: b.config.IsChinaCloud(),
TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument,
},
&awscommon.StepCleanupVolumes{
Expand Down
8 changes: 5 additions & 3 deletions builder/ebsvolume/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,9 +312,11 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
Ctx: b.config.ctx,
},
&awscommon.StepIamInstanceProfile{
PollingConfig: b.config.PollingConfig,
IamInstanceProfile: b.config.IamInstanceProfile,
SkipProfileValidation: b.config.SkipProfileValidation,
PollingConfig: b.config.PollingConfig,
IamInstanceProfile: b.config.IamInstanceProfile,
SkipProfileValidation: b.config.SkipProfileValidation,
SSMAgentEnabled: b.config.SSMAgentEnabled(),
IsRestricted: b.config.IsChinaCloud(),
TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument,
},
instanceStep,
Expand Down
8 changes: 5 additions & 3 deletions builder/instance/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,9 +383,11 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
Ctx: b.config.ctx,
},
&awscommon.StepIamInstanceProfile{
PollingConfig: b.config.PollingConfig,
IamInstanceProfile: b.config.IamInstanceProfile,
SkipProfileValidation: b.config.SkipProfileValidation,
PollingConfig: b.config.PollingConfig,
IamInstanceProfile: b.config.IamInstanceProfile,
SkipProfileValidation: b.config.SkipProfileValidation,
SSMAgentEnabled: b.config.SSMAgentEnabled(),
IsRestricted: b.config.IsChinaCloud(),
TemporaryIamInstanceProfilePolicyDocument: b.config.TemporaryIamInstanceProfilePolicyDocument,
},
instanceStep,
Expand Down
Loading