builder/amazon: Update Session Manger connectivity

* Update security group creation step skip ingress rules if using session manager
* Update create ssm tunnel step to dynamically set a session port
* Add SSHPort function to common to return session-manager tunnel port
* Update SSHHost to return proper host for session-manager
This commit is contained in:
Wilken Rivera 2020-03-30 07:47:31 -04:00
parent 3dd46eb5f4
commit 520061dee6
5 changed files with 76 additions and 49 deletions

View File

@ -441,6 +441,7 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
c.SSHInterface != "private_ip" && c.SSHInterface != "private_ip" &&
c.SSHInterface != "public_dns" && c.SSHInterface != "public_dns" &&
c.SSHInterface != "private_dns" && c.SSHInterface != "private_dns" &&
c.SSHInterface != "session_manager" &&
c.SSHInterface != "" { c.SSHInterface != "" {
errs = append(errs, fmt.Errorf("Unknown interface type: %s", c.SSHInterface)) errs = append(errs, fmt.Errorf("Unknown interface type: %s", c.SSHInterface))
} }

View File

@ -28,6 +28,10 @@ func SSHHost(e ec2Describer, sshInterface string, host string) func(multistep.St
return host, nil return host, nil
} }
if sshInterface == "session_manager" {
return "127.0.0.1", nil
}
const tries = 2 const tries = 2
// <= with current structure to check result of describing `tries` times // <= with current structure to check result of describing `tries` times
for j := 0; j <= tries; j++ { for j := 0; j <= tries; j++ {
@ -85,3 +89,20 @@ func SSHHost(e ec2Describer, sshInterface string, host string) func(multistep.St
return "", errors.New("couldn't determine address for instance") return "", errors.New("couldn't determine address for instance")
} }
} }
// SSHPort returns a function that can be given to the SSH communicator
// for determining the SSH port to use when connecting to an instance.
func SSHPort(sshInterface string, port int) func(multistep.StateBag) (int, error) {
return func(state multistep.StateBag) (int, error) {
if sshInterface != "session_manager" {
return port, nil
}
port, ok := state.GetOk("sessionPort")
if !ok {
return 0, fmt.Errorf("no local port defined for session-manager")
}
return port.(int), nil
}
}

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net"
"strconv" "strconv"
"time" "time"
@ -12,6 +11,7 @@ import (
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ssm" "github.com/aws/aws-sdk-go/service/ssm"
"github.com/hashicorp/packer/common/net"
"github.com/hashicorp/packer/common/retry" "github.com/hashicorp/packer/common/retry"
"github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/helper/multistep"
@ -22,37 +22,34 @@ type StepCreateSSMTunnel struct {
CommConfig *communicator.Config CommConfig *communicator.Config
AWSSession *session.Session AWSSession *session.Session
InstanceID string InstanceID string
DstPort string DstPort int
SrcPort string
ssmSession *ssm.StartSessionOutput ssmSession *ssm.StartSessionOutput
tunnel net.Listener
} }
func (s *StepCreateSSMTunnel) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { func (s *StepCreateSSMTunnel) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
/* // Find an available TCP port for our HTTP server
p, _ := strconv.Atoi(s.SrcPort) l, err := net.ListenRangeConfig{
//TODO dynamically setup local port Min: 8000,
// Find an available TCP port for our HTTP server Max: 9000,
l, err := packernet.ListenRangeConfig{ Addr: "0.0.0.0",
Min: p, Network: "tcp",
Max: p, }.Listen(ctx)
Addr: "0.0.0.0", if err != nil {
Network: "tcp", err := fmt.Errorf("error finding an available port to initiate a session tunnel: %s", err)
}.Listen(ctx) state.Put("error", err)
ui.Error(err.Error())
if err != nil { return multistep.ActionHalt
err := fmt.Errorf("Error finding port: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
*/
params := map[string][]*string{
"portNumber": []*string{aws.String(s.DstPort)},
"localPortNumber": []*string{aws.String(strconv.Itoa(8081))},
} }
dst, src := strconv.Itoa(s.DstPort), strconv.Itoa(l.Port)
params := map[string][]*string{
"portNumber": []*string{aws.String(dst)},
"localPortNumber": []*string{aws.String(src)},
}
l.Close()
instance, ok := state.Get("instance").(*ec2.Instance) instance, ok := state.Get("instance").(*ec2.Instance)
if !ok { if !ok {
err := fmt.Errorf("error encountered in obtaining target instance id for SSM tunnel") err := fmt.Errorf("error encountered in obtaining target instance id for SSM tunnel")
@ -62,15 +59,14 @@ func (s *StepCreateSSMTunnel) Run(ctx context.Context, state multistep.StateBag)
} }
s.InstanceID = aws.StringValue(instance.InstanceId) s.InstanceID = aws.StringValue(instance.InstanceId)
ssmconn := ssm.New(s.AWSSession) ssmconn := ssm.New(s.AWSSession)
input := ssm.StartSessionInput{ input := ssm.StartSessionInput{
DocumentName: aws.String("AWS-StartPortForwardingSession"), DocumentName: aws.String("AWS-StartPortForwardingSession"),
Parameters: params, Parameters: params,
Target: aws.String(s.InstanceID), Target: aws.String(s.InstanceID),
} }
var output *ssm.StartSessionOutput var output *ssm.StartSessionOutput
var err error
err = retry.Config{ err = retry.Config{
Tries: 11, Tries: 11,
ShouldRetry: func(err error) bool { return isAWSErr(err, "TargetNotConnected", "") }, ShouldRetry: func(err error) bool { return isAWSErr(err, "TargetNotConnected", "") },
@ -81,36 +77,41 @@ func (s *StepCreateSSMTunnel) Run(ctx context.Context, state multistep.StateBag)
}) })
if err != nil { if err != nil {
err = fmt.Errorf("error encountered in starting session: %s", err) err = fmt.Errorf("error encountered in starting session for instance %q: %s", s.InstanceID, err)
ui.Error(err.Error()) ui.Error(err.Error())
state.Put("error", err) state.Put("error", err)
return multistep.ActionHalt return multistep.ActionHalt
} }
s.ssmSession = output s.ssmSession = output
sessJson, err := json.Marshal(s.ssmSession) // AWS session-manager-plugin requires a valid session be passed in JSON
sessionDetails, err := json.Marshal(s.ssmSession)
if err != nil { if err != nil {
ui.Error(err.Error()) ui.Error(err.Error())
state.Put("error", err) state.Put("error encountered in reading session details", err)
return multistep.ActionHalt return multistep.ActionHalt
} }
paramsJson, err := json.Marshal(input) sessionParameters, err := json.Marshal(input)
if err != nil { if err != nil {
ui.Error(err.Error()) ui.Error(err.Error())
state.Put("error", err) state.Put("error encountered in reading session parameter details", err)
return multistep.ActionHalt return multistep.ActionHalt
} }
driver := SSMDriver{Ui: ui} driver := SSMDriver{Ui: ui}
// sessJson, region, "StartSession", profile, paramJson, endpoint // sessionDetails, region, "StartSession", profile, paramJson, endpoint
if err := driver.StartSession(string(sessJson), "us-east-1", "packer", string(paramsJson), ssmconn.Endpoint); err != nil { region := aws.StringValue(s.AWSSession.Config.Region)
err = fmt.Errorf("error encountered in creating a connection to the SSM agent: %s", err) // how to best get Profile name
if err := driver.StartSession(string(sessionDetails), region, "default", string(sessionParameters), ssmconn.Endpoint); err != nil {
err = fmt.Errorf("error encountered in establishing a tunnel with the session-manager-plugin: %s", err)
ui.Error(err.Error()) ui.Error(err.Error())
state.Put("error", err) state.Put("error", err)
return multistep.ActionHalt return multistep.ActionHalt
} }
state.Put("sessionPort", l.Port)
return multistep.ActionContinue return multistep.ActionContinue
} }

View File

@ -21,7 +21,7 @@ type StepSecurityGroup struct {
SecurityGroupFilter SecurityGroupFilterOptions SecurityGroupFilter SecurityGroupFilterOptions
SecurityGroupIds []string SecurityGroupIds []string
TemporarySGSourceCidrs []string TemporarySGSourceCidrs []string
SkipSSHGroupCreation bool SkipSSHRuleCreation bool
createdGroupId string createdGroupId string
} }
@ -77,10 +77,7 @@ func (s *StepSecurityGroup) Run(ctx context.Context, state multistep.StateBag) m
return multistep.ActionContinue return multistep.ActionContinue
} }
if s.SkipSSHGroupCreation { // TODO move to some prevalidation step for
return multistep.ActionContinue
}
port := s.CommConfig.Port() port := s.CommConfig.Port()
if port == 0 { if port == 0 {
if s.CommConfig.Type != "none" { if s.CommConfig.Type != "none" {
@ -115,15 +112,15 @@ func (s *StepSecurityGroup) Run(ctx context.Context, state multistep.StateBag) m
GroupIds: []*string{aws.String(s.createdGroupId)}, GroupIds: []*string{aws.String(s.createdGroupId)},
}, },
) )
if err == nil { if err != nil {
log.Printf("[DEBUG] Found security group %s", s.createdGroupId)
} else {
err := fmt.Errorf("Timed out waiting for security group %s: %s", s.createdGroupId, err) err := fmt.Errorf("Timed out waiting for security group %s: %s", s.createdGroupId, err)
log.Printf("[DEBUG] %s", err.Error()) log.Printf("[DEBUG] %s", err.Error())
state.Put("error", err) state.Put("error", err)
return multistep.ActionHalt return multistep.ActionHalt
} }
log.Printf("[DEBUG] Found security group %s", s.createdGroupId)
// map the list of temporary security group CIDRs bundled with config to // map the list of temporary security group CIDRs bundled with config to
// types expected by EC2. // types expected by EC2.
groupIpRanges := []*ec2.IpRange{} groupIpRanges := []*ec2.IpRange{}
@ -134,6 +131,13 @@ func (s *StepSecurityGroup) Run(ctx context.Context, state multistep.StateBag) m
groupIpRanges = append(groupIpRanges, &ipRange) groupIpRanges = append(groupIpRanges, &ipRange)
} }
// Set some state data for use in future steps
state.Put("securityGroupIds", []string{s.createdGroupId})
if s.SkipSSHRuleCreation {
return multistep.ActionContinue
}
// Authorize the SSH access for the security group // Authorize the SSH access for the security group
groupRules := &ec2.AuthorizeSecurityGroupIngressInput{ groupRules := &ec2.AuthorizeSecurityGroupIngressInput{
GroupId: groupResp.GroupId, GroupId: groupResp.GroupId,
@ -159,9 +163,6 @@ func (s *StepSecurityGroup) Run(ctx context.Context, state multistep.StateBag) m
return multistep.ActionHalt return multistep.ActionHalt
} }
// Set some state data for use in future steps
state.Put("securityGroupIds", []string{s.createdGroupId})
return multistep.ActionContinue return multistep.ActionContinue
} }

View File

@ -11,7 +11,6 @@ package ebs
import ( import (
"context" "context"
"fmt" "fmt"
"strconv"
"github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/iam" "github.com/aws/aws-sdk-go/service/iam"
@ -240,6 +239,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
SecurityGroupIds: b.config.SecurityGroupIds, SecurityGroupIds: b.config.SecurityGroupIds,
CommConfig: &b.config.RunConfig.Comm, CommConfig: &b.config.RunConfig.Comm,
TemporarySGSourceCidrs: b.config.TemporarySGSourceCidrs, TemporarySGSourceCidrs: b.config.TemporarySGSourceCidrs,
SkipSSHRuleCreation: b.config.SSHInterface == "session_manager",
}, },
&awscommon.StepIamInstanceProfile{ &awscommon.StepIamInstanceProfile{
IamInstanceProfile: b.config.IamInstanceProfile, IamInstanceProfile: b.config.IamInstanceProfile,
@ -258,8 +258,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
}, },
&awscommon.StepCreateSSMTunnel{ &awscommon.StepCreateSSMTunnel{
AWSSession: session, AWSSession: session,
DstPort: strconv.Itoa(22), DstPort: b.config.Comm.SSHPort,
SrcPort: "8081",
}, },
&communicator.StepConnect{ &communicator.StepConnect{
Config: &b.config.RunConfig.Comm, Config: &b.config.RunConfig.Comm,
@ -268,6 +267,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
b.config.SSHInterface, b.config.SSHInterface,
b.config.Comm.Host(), b.config.Comm.Host(),
), ),
SSHPort: awscommon.SSHPort(
b.config.SSHInterface,
b.config.Comm.Port(),
),
SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(), SSHConfig: b.config.RunConfig.Comm.SSHConfigFunc(),
}, },
&common.StepProvision{}, &common.StepProvision{},