From 5ef3e8123443341f6d2c5edb0af323c08789b5a6 Mon Sep 17 00:00:00 2001 From: Marin Salinas Date: Fri, 1 Feb 2019 15:20:47 -0600 Subject: [PATCH] feature: bsusurrogate, add security group step --- builder/osc/bsusurrogate/builder.go | 6 + builder/osc/common/build_filters.go | 101 ++++++++++--- builder/osc/common/run_config.go | 2 +- builder/osc/common/state.go | 59 ++++++++ builder/osc/common/step_security_group.go | 175 ++++++++++++++++++++++ 5 files changed, 324 insertions(+), 19 deletions(-) create mode 100644 builder/osc/common/state.go create mode 100644 builder/osc/common/step_security_group.go diff --git a/builder/osc/bsusurrogate/builder.go b/builder/osc/bsusurrogate/builder.go index a49222184..945332962 100644 --- a/builder/osc/bsusurrogate/builder.go +++ b/builder/osc/bsusurrogate/builder.go @@ -158,6 +158,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Comm: &b.config.RunConfig.Comm, DebugKeyPath: fmt.Sprintf("oapi_%s.pem", b.config.PackerBuildName), }, + &osccommon.StepSecurityGroup{ + SecurityGroupFilter: b.config.SecurityGroupFilter, + SecurityGroupIds: b.config.SecurityGroupIds, + CommConfig: &b.config.RunConfig.Comm, + TemporarySGSourceCidr: b.config.TemporarySGSourceCidr, + }, } b.runner = common.NewRunner(steps, b.config.PackerConfig, ui) diff --git a/builder/osc/common/build_filters.go b/builder/osc/common/build_filters.go index 5ff387dfd..bec8a8fd1 100644 --- a/builder/osc/common/build_filters.go +++ b/builder/osc/common/build_filters.go @@ -12,19 +12,19 @@ func buildNetFilters(input map[string]string) oapi.FiltersNet { for k, v := range input { filterValue := []string{v} switch name := k; name { - case "ip_range": + case "ip-range": filters.IpRanges = filterValue - case "dhcp_options_set_id": + case "dhcp-options-set-id": filters.DhcpOptionsSetIds = filterValue - case "is_default": + case "is-default": if isDefault, err := strconv.ParseBool(v); err == nil { filters.IsDefault = isDefault } case "state": filters.States = filterValue - case "tag_key": + case "tag-key": filters.TagKeys = filterValue - case "tag_value": + case "tag-value": filters.TagValues = filterValue default: log.Printf("[Debug] Unknown Filter Name: %s.", name) @@ -38,19 +38,19 @@ func buildSubnetFilters(input map[string]string) oapi.FiltersSubnet { for k, v := range input { filterValue := []string{v} switch name := k; name { - case "available_ips_counts": + case "available-ips-counts": if ipCount, err := strconv.Atoi(v); err == nil { filters.AvailableIpsCounts = []int64{int64(ipCount)} } - case "ip_ranges": + case "ip-ranges": filters.IpRanges = filterValue - case "net_ids": + case "net-ids": filters.NetIds = filterValue case "states": filters.States = filterValue - case "subnet_ids": + case "subnet-ids": filters.SubnetIds = filterValue - case "sub_region_names": + case "sub-region-names": filters.SubregionNames = filterValue default: log.Printf("[Debug] Unknown Filter Name: %s.", name) @@ -65,23 +65,23 @@ func buildOMIFilters(input map[string]string) oapi.FiltersImage { filterValue := []string{v} switch name := k; name { - case "account_aliases": + case "account-aliases": filters.AccountAliases = filterValue - case "account_ids": + case "account-ids": filters.AccountIds = filterValue case "architectures": filters.Architectures = filterValue - case "image_ids": + case "image-ids": filters.ImageIds = filterValue - case "image_names": + case "image-names": filters.ImageNames = filterValue - case "image_types": + case "image-types": filters.ImageTypes = filterValue - case "virtualization_types": + case "virtualization-types": filters.VirtualizationTypes = filterValue - case "root_device_types": + case "root-device-types": filters.RootDeviceTypes = filterValue - case "block_device_mapping_volume_type": + case "block-device-mapping-volume-type": filters.BlockDeviceMappingVolumeType = filterValue //Some params are missing. default: @@ -90,3 +90,68 @@ func buildOMIFilters(input map[string]string) oapi.FiltersImage { } return filters } + +func buildSecurityGroupFilters(input map[string]string) oapi.FiltersSecurityGroup { + var filters oapi.FiltersSecurityGroup + for k, v := range input { + filterValue := []string{v} + + switch name := k; name { + case "account-ids": + filters.AccountIds = filterValue + case "descriptions": + filters.Descriptions = filterValue + case "inbound-rule-account-ids": + filters.InboundRuleAccountIds = filterValue + case "inbound-rule-from-port-ranges": + if val, err := strconv.Atoi(v); err == nil { + filters.InboundRuleFromPortRanges = []int64{int64(val)} + } + case "inbound-rule-ip-ranges": + filters.InboundRuleIpRanges = filterValue + case "inbound-rule-protocols": + filters.InboundRuleProtocols = filterValue + case "inbound-rule-security-group-ids": + filters.InboundRuleSecurityGroupIds = filterValue + case "inbound-rule-security-group-names": + filters.InboundRuleSecurityGroupNames = filterValue + case "inbound-rule-to-port-ranges": + if val, err := strconv.Atoi(v); err == nil { + filters.InboundRuleToPortRanges = []int64{int64(val)} + } + case "net-ids": + filters.NetIds = filterValue + + case "outbound-rule-account-ids": + filters.OutboundRuleAccountIds = filterValue + case "outbound-rule-from-port-ranges": + if val, err := strconv.Atoi(v); err == nil { + filters.OutboundRuleFromPortRanges = []int64{int64(val)} + } + case "outbound-rule-ip-ranges": + filters.OutboundRuleIpRanges = filterValue + case "outbound-rule-protocols": + filters.OutboundRuleProtocols = filterValue + case "outbound-rule-security-group-ids": + filters.OutboundRuleSecurityGroupIds = filterValue + case "outbound-rule-security-group-names": + filters.OutboundRuleSecurityGroupNames = filterValue + case "outbound-rule-to-port-ranges": + if val, err := strconv.Atoi(v); err == nil { + filters.OutboundRuleToPortRanges = []int64{int64(val)} + } + case "security-group-ids": + filters.SecurityGroupIds = filterValue + case "security-group-names": + filters.SecurityGroupNames = filterValue + case "tags-keys": + filters.TagKeys = filterValue + case "tags-values": + filters.TagValues = filterValue + //Some params are missing. + default: + log.Printf("[Debug] Unknown Filter Name: %s.", name) + } + } + return filters +} diff --git a/builder/osc/common/run_config.go b/builder/osc/common/run_config.go index cca3cf2b9..b781a1a4b 100644 --- a/builder/osc/common/run_config.go +++ b/builder/osc/common/run_config.go @@ -48,7 +48,7 @@ func (d *NetFilterOptions) Empty() bool { } type SecurityGroupFilterOptions struct { - Filters map[*string]*string + Filters map[string]string } func (d *SecurityGroupFilterOptions) Empty() bool { diff --git a/builder/osc/common/state.go b/builder/osc/common/state.go new file mode 100644 index 000000000..701a4b293 --- /dev/null +++ b/builder/osc/common/state.go @@ -0,0 +1,59 @@ +package common + +import ( + "fmt" + "log" + + "github.com/hashicorp/packer/common" + "github.com/outscale/osc-go/oapi" +) + +type stateRefreshFunc func() (string, error) + +func waitForSecurityGroup(conn *oapi.Client, securityGroupID string) error { + errCh := make(chan error, 1) + go waitForState(errCh, "exists", securityGroupWaitFunc(conn, securityGroupID)) + err := <-errCh + return err +} + +func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) error { + err := common.Retry(2, 2, 0, func(_ uint) (bool, error) { + state, err := refresh() + if err != nil { + return false, err + } else if state == target { + return true, nil + } + return false, nil + }) + errCh <- err + return err +} + +func securityGroupWaitFunc(conn *oapi.Client, id string) stateRefreshFunc { + return func() (string, error) { + log.Printf("[Debug] Check if SG with id %s exists", id) + resp, err := conn.POST_ReadSecurityGroups(oapi.ReadSecurityGroupsRequest{ + Filters: oapi.FiltersSecurityGroup{ + SecurityGroupIds: []string{id}, + }, + }) + + log.Printf("[Debug] Read Response %+v", resp.OK) + + if err != nil { + return "", err + } + + if resp.OK == nil { + return "", fmt.Errorf("Security Group with ID %s. Not Found", id) + } + + if len(resp.OK.SecurityGroups) == 0 { + return "waiting", nil + } + + return "exists", nil + } +} diff --git a/builder/osc/common/step_security_group.go b/builder/osc/common/step_security_group.go new file mode 100644 index 000000000..7531f03a1 --- /dev/null +++ b/builder/osc/common/step_security_group.go @@ -0,0 +1,175 @@ +package common + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "github.com/hashicorp/packer/common/uuid" + "github.com/hashicorp/packer/helper/communicator" + "github.com/hashicorp/packer/helper/multistep" + "github.com/hashicorp/packer/packer" + "github.com/outscale/osc-go/oapi" +) + +type StepSecurityGroup struct { + CommConfig *communicator.Config + SecurityGroupFilter SecurityGroupFilterOptions + SecurityGroupIds []string + TemporarySGSourceCidr string + + createdGroupId string +} + +func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { + oapiconn := state.Get("oapi").(*oapi.Client) + ui := state.Get("ui").(packer.Ui) + netId := state.Get("net_id").(string) + + if len(s.SecurityGroupIds) > 0 { + resp, err := oapiconn.POST_ReadSecurityGroups( + oapi.ReadSecurityGroupsRequest{ + Filters: oapi.FiltersSecurityGroup{ + SecurityGroupIds: s.SecurityGroupIds, + }, + }, + ) + if err != nil || resp.OK == nil || len(resp.OK.SecurityGroups) <= 0 { + err := fmt.Errorf("Couldn't find specified security group: %s", err) + log.Printf("[DEBUG] %s", err.Error()) + state.Put("error", err) + return multistep.ActionHalt + } + + log.Printf("Using specified security groups: %v", s.SecurityGroupIds) + state.Put("securityGroupIds", s.SecurityGroupIds) + return multistep.ActionContinue + } + + if !s.SecurityGroupFilter.Empty() { + + params := oapi.ReadSecurityGroupsRequest{} + if netId != "" { + s.SecurityGroupFilter.Filters["net-id"] = netId + } + params.Filters = buildSecurityGroupFilters(s.SecurityGroupFilter.Filters) + + log.Printf("Using SecurityGroup Filters %v", params) + + sgResp, err := oapiconn.POST_ReadSecurityGroups(params) + if err != nil || sgResp.OK == nil { + err := fmt.Errorf("Couldn't find security groups for filter: %s", err) + log.Printf("[DEBUG] %s", err.Error()) + state.Put("error", err) + return multistep.ActionHalt + } + + securityGroupIds := []string{} + for _, sg := range sgResp.OK.SecurityGroups { + securityGroupIds = append(securityGroupIds, sg.SecurityGroupId) + } + + ui.Message(fmt.Sprintf("Found Security Group(s): %s", strings.Join(securityGroupIds, ", "))) + state.Put("securityGroupIds", securityGroupIds) + + return multistep.ActionContinue + } + + port := s.CommConfig.Port() + if port == 0 { + if s.CommConfig.Type != "none" { + panic("port must be set to a non-zero value.") + } + } + + // Create the group + groupName := fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID()) + ui.Say(fmt.Sprintf("Creating temporary security group for this instance: %s", groupName)) + group := oapi.CreateSecurityGroupRequest{ + SecurityGroupName: groupName, + Description: "Temporary group for Packer", + } + + group.NetId = netId + + groupResp, err := oapiconn.POST_CreateSecurityGroup(group) + if err != nil { + ui.Error(err.Error()) + state.Put("error", err) + return multistep.ActionHalt + } + + // Set the group ID so we can delete it later + s.createdGroupId = groupResp.OK.SecurityGroup.SecurityGroupId + + // Wait for the security group become available for authorizing + log.Printf("[DEBUG] Waiting for temporary security group: %s", s.createdGroupId) + err = waitForSecurityGroup(oapiconn, s.createdGroupId) + 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) + log.Printf("[DEBUG] %s", err.Error()) + state.Put("error", err) + return multistep.ActionHalt + } + + // Authorize the SSH access for the security group + groupRules := oapi.CreateSecurityGroupRuleRequest{ + SecurityGroupId: groupResp.OK.SecurityGroup.SecurityGroupId, + Flow: "Inbound", + Rules: []oapi.SecurityGroupRule{ + oapi.SecurityGroupRule{ + FromPortRange: int64(port), + ToPortRange: int64(port), + IpRanges: []string{s.TemporarySGSourceCidr}, + IpProtocol: "tcp", + }, + }, + } + + ui.Say(fmt.Sprintf( + "Authorizing access to port %d from %s in the temporary security group...", + port, s.TemporarySGSourceCidr)) + _, err = oapiconn.POST_CreateSecurityGroupRule(groupRules) + if err != nil { + err := fmt.Errorf("Error authorizing temporary security group: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Set some state data for use in future steps + state.Put("securityGroupIds", []string{s.createdGroupId}) + + return multistep.ActionContinue +} + +func (s *StepSecurityGroup) Cleanup(state multistep.StateBag) { + if s.createdGroupId == "" { + return + } + + oapiconn := state.Get("oapi").(*oapi.Client) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Deleting temporary security group...") + + var err error + for i := 0; i < 5; i++ { + _, err = oapiconn.POST_DeleteSecurityGroup(oapi.DeleteSecurityGroupRequest{SecurityGroupId: s.createdGroupId}) + if err == nil { + break + } + + log.Printf("Error deleting security group: %s", err) + time.Sleep(5 * time.Second) + } + + if err != nil { + ui.Error(fmt.Sprintf( + "Error cleaning up security group. Please delete the group manually: %s", s.createdGroupId)) + } +}