github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/openstack/legacy_firewaller.go (about)

     1  // Copyright 2015-2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package openstack
     5  
     6  import (
     7  	"fmt"
     8  	"regexp"
     9  
    10  	"github.com/juju/clock"
    11  	"github.com/juju/errors"
    12  	gooseerrors "gopkg.in/goose.v2/errors"
    13  	"gopkg.in/goose.v2/neutron"
    14  	"gopkg.in/goose.v2/nova"
    15  
    16  	corenetwork "github.com/juju/juju/core/network"
    17  	"github.com/juju/juju/environs/config"
    18  	"github.com/juju/juju/environs/context"
    19  	"github.com/juju/juju/environs/instances"
    20  	"github.com/juju/juju/network"
    21  	"github.com/juju/juju/provider/common"
    22  )
    23  
    24  type legacyNovaFirewaller struct {
    25  	firewallerBase
    26  }
    27  
    28  // SetUpGroups creates the security groups for the new machine, and
    29  // returns them.
    30  //
    31  // Instances are tagged with a group so they can be distinguished from
    32  // other instances that might be running on the same OpenStack account.
    33  // In addition, a specific machine security group is created for each
    34  // machine, so that its firewall rules can be configured per machine.
    35  func (c *legacyNovaFirewaller) SetUpGroups(ctx context.ProviderCallContext, controllerUUID, machineId string, apiPort int) ([]string, error) {
    36  	jujuGroup, err := c.setUpGlobalGroup(ctx, c.jujuGroupName(controllerUUID), apiPort)
    37  	if err != nil {
    38  		return nil, errors.Trace(err)
    39  	}
    40  	var machineGroup nova.SecurityGroup
    41  	switch c.environ.Config().FirewallMode() {
    42  	case config.FwInstance:
    43  		machineGroup, err = c.ensureGroup(ctx, c.machineGroupName(controllerUUID, machineId), nil)
    44  	case config.FwGlobal:
    45  		machineGroup, err = c.ensureGroup(ctx, c.globalGroupName(controllerUUID), nil)
    46  	}
    47  	if err != nil {
    48  		return nil, errors.Trace(err)
    49  	}
    50  	groupNames := []string{jujuGroup.Name, machineGroup.Name}
    51  	if c.environ.ecfg().useDefaultSecurityGroup() {
    52  		groupNames = append(groupNames, "default")
    53  	}
    54  	return groupNames, nil
    55  }
    56  
    57  func (c *legacyNovaFirewaller) setUpGlobalGroup(ctx context.ProviderCallContext, groupName string, apiPort int) (nova.SecurityGroup, error) {
    58  	return c.ensureGroup(ctx, groupName,
    59  		[]nova.RuleInfo{
    60  			{
    61  				IPProtocol: "tcp",
    62  				ToPort:     22,
    63  				FromPort:   22,
    64  				Cidr:       "0.0.0.0/0",
    65  			},
    66  			{
    67  				IPProtocol: "tcp",
    68  				ToPort:     apiPort,
    69  				FromPort:   apiPort,
    70  				Cidr:       "0.0.0.0/0",
    71  			},
    72  			{
    73  				IPProtocol: "tcp",
    74  				FromPort:   1,
    75  				ToPort:     65535,
    76  			},
    77  			{
    78  				IPProtocol: "udp",
    79  				FromPort:   1,
    80  				ToPort:     65535,
    81  			},
    82  			{
    83  				IPProtocol: "icmp",
    84  				FromPort:   -1,
    85  				ToPort:     -1,
    86  			},
    87  		})
    88  }
    89  
    90  // legacyZeroGroup holds the zero security group.
    91  var legacyZeroGroup nova.SecurityGroup
    92  
    93  // ensureGroup returns the security group with name and perms.
    94  // If a group with name does not exist, one will be created.
    95  // If it exists, its permissions are set to perms.
    96  func (c *legacyNovaFirewaller) ensureGroup(ctx context.ProviderCallContext, name string, rules []nova.RuleInfo) (nova.SecurityGroup, error) {
    97  	novaClient := c.environ.nova()
    98  	// First attempt to look up an existing group by name.
    99  	group, err := novaClient.SecurityGroupByName(name)
   100  	if err == nil {
   101  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   102  		// Group exists, so assume it is correctly set up and return it.
   103  		// TODO(jam): 2013-09-18 http://pad.lv/121795
   104  		// We really should verify the group is set up correctly,
   105  		// because deleting and re-creating environments can get us bad
   106  		// groups (especially if they were set up under Python)
   107  		return *group, nil
   108  	}
   109  	// Doesn't exist, so try and create it.
   110  	group, err = novaClient.CreateSecurityGroup(name, "juju group")
   111  	if err != nil {
   112  		if !gooseerrors.IsDuplicateValue(err) {
   113  			return legacyZeroGroup, err
   114  		} else {
   115  			// We just tried to create a duplicate group, so load the existing group.
   116  			group, err = novaClient.SecurityGroupByName(name)
   117  			if err != nil {
   118  				common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   119  				return legacyZeroGroup, err
   120  			}
   121  			return *group, nil
   122  		}
   123  	}
   124  	// The new group is created so now add the rules.
   125  	group.Rules = make([]nova.SecurityGroupRule, len(rules))
   126  	for i, rule := range rules {
   127  		rule.ParentGroupId = group.Id
   128  		if rule.Cidr == "" {
   129  			// http://pad.lv/1226996 Rules that don't have a CIDR
   130  			// are meant to apply only to this group. If you don't
   131  			// supply CIDR or GroupId then openstack assumes you
   132  			// mean CIDR=0.0.0.0/0
   133  			rule.GroupId = &group.Id
   134  		}
   135  		groupRule, err := novaClient.CreateSecurityGroupRule(rule)
   136  		if err != nil && !gooseerrors.IsDuplicateValue(err) {
   137  			common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   138  			return legacyZeroGroup, err
   139  		}
   140  		group.Rules[i] = *groupRule
   141  	}
   142  	return *group, nil
   143  }
   144  
   145  func (c *legacyNovaFirewaller) deleteSecurityGroups(ctx context.ProviderCallContext, match func(name string) bool) error {
   146  	novaclient := c.environ.nova()
   147  	securityGroups, err := novaclient.ListSecurityGroups()
   148  	if err != nil {
   149  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   150  		return errors.Annotate(err, "cannot list security groups")
   151  	}
   152  	for _, group := range securityGroups {
   153  		if match(group.Name) {
   154  			deleteSecurityGroup(ctx,
   155  				novaclient.DeleteSecurityGroup,
   156  				group.Name,
   157  				group.Id,
   158  				clock.WallClock,
   159  			)
   160  		}
   161  	}
   162  	return nil
   163  }
   164  
   165  // DeleteAllControllerGroups implements Firewaller interface.
   166  func (c *legacyNovaFirewaller) DeleteAllControllerGroups(ctx context.ProviderCallContext, controllerUUID string) error {
   167  	return deleteSecurityGroupsMatchingName(ctx, c.deleteSecurityGroups, c.jujuControllerGroupPrefix(controllerUUID))
   168  }
   169  
   170  // DeleteAllModelGroups implements Firewaller interface.
   171  func (c *legacyNovaFirewaller) DeleteAllModelGroups(ctx context.ProviderCallContext) error {
   172  	return deleteSecurityGroupsMatchingName(ctx, c.deleteSecurityGroups, c.jujuGroupRegexp())
   173  }
   174  
   175  // DeleteGroups implements Firewaller interface.
   176  func (c *legacyNovaFirewaller) DeleteGroups(ctx context.ProviderCallContext, names ...string) error {
   177  	return deleteSecurityGroupsOneOfNames(ctx, c.deleteSecurityGroups, names...)
   178  }
   179  
   180  // UpdateGroupController implements Firewaller interface.
   181  func (c *legacyNovaFirewaller) UpdateGroupController(ctx context.ProviderCallContext, controllerUUID string) error {
   182  	novaClient := c.environ.nova()
   183  	groups, err := novaClient.ListSecurityGroups()
   184  	if err != nil {
   185  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   186  		return errors.Trace(err)
   187  	}
   188  	re, err := regexp.Compile(c.jujuGroupRegexp())
   189  	if err != nil {
   190  		return errors.Trace(err)
   191  	}
   192  
   193  	var failed []string
   194  	for _, group := range groups {
   195  		if !re.MatchString(group.Name) {
   196  			continue
   197  		}
   198  		err := c.updateGroupControllerUUID(ctx, &group, controllerUUID)
   199  		if err != nil {
   200  			logger.Errorf("error updating controller for security group %s: %v", group.Id, err)
   201  			failed = append(failed, group.Id)
   202  			if denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denied {
   203  				// We will keep failing 100% once the credential is deemed invalid - no point in persisting.
   204  				break
   205  			}
   206  		}
   207  	}
   208  	if len(failed) != 0 {
   209  		return errors.Errorf("errors updating controller for security groups: %v", failed)
   210  	}
   211  	return nil
   212  }
   213  
   214  func (c *legacyNovaFirewaller) updateGroupControllerUUID(ctx context.ProviderCallContext, group *nova.SecurityGroup, controllerUUID string) error {
   215  	newName, err := replaceControllerUUID(group.Name, controllerUUID)
   216  	if err != nil {
   217  		return errors.Trace(err)
   218  	}
   219  	client := c.environ.nova()
   220  	_, err = client.UpdateSecurityGroup(group.Id, newName, group.Description)
   221  	common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   222  	return errors.Trace(err)
   223  }
   224  
   225  // OpenPorts implements Firewaller interface.
   226  func (c *legacyNovaFirewaller) OpenPorts(ctx context.ProviderCallContext, rules []network.IngressRule) error {
   227  	return c.openPorts(ctx, c.openPortsInGroup, rules)
   228  }
   229  
   230  // ClosePorts implements Firewaller interface.
   231  func (c *legacyNovaFirewaller) ClosePorts(ctx context.ProviderCallContext, rules []network.IngressRule) error {
   232  	return c.closePorts(ctx, c.closePortsInGroup, rules)
   233  }
   234  
   235  // IngressRules implements Firewaller interface.
   236  func (c *legacyNovaFirewaller) IngressRules(ctx context.ProviderCallContext) ([]network.IngressRule, error) {
   237  	return c.ingressRules(ctx, c.ingressRulesInGroup)
   238  }
   239  
   240  // OpenInstancePorts implements Firewaller interface.
   241  func (c *legacyNovaFirewaller) OpenInstancePorts(ctx context.ProviderCallContext, inst instances.Instance, machineId string, rules []network.IngressRule) error {
   242  	return c.openInstancePorts(ctx, c.openPortsInGroup, machineId, rules)
   243  }
   244  
   245  // CloseInstancePorts implements Firewaller interface.
   246  func (c *legacyNovaFirewaller) CloseInstancePorts(ctx context.ProviderCallContext, inst instances.Instance, machineId string, rules []network.IngressRule) error {
   247  	return c.closeInstancePorts(ctx, c.closePortsInGroup, machineId, rules)
   248  }
   249  
   250  // InstanceIngressRules implements Firewaller interface.
   251  func (c *legacyNovaFirewaller) InstanceIngressRules(ctx context.ProviderCallContext, inst instances.Instance, machineId string) ([]network.IngressRule, error) {
   252  	return c.instanceIngressRules(ctx, c.ingressRulesInGroup, machineId)
   253  }
   254  
   255  func (c *legacyNovaFirewaller) matchingGroup(ctx context.ProviderCallContext, nameRegExp string) (nova.SecurityGroup, error) {
   256  	re, err := regexp.Compile(nameRegExp)
   257  	if err != nil {
   258  		return nova.SecurityGroup{}, err
   259  	}
   260  	novaclient := c.environ.nova()
   261  	allGroups, err := novaclient.ListSecurityGroups()
   262  	if err != nil {
   263  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   264  		return nova.SecurityGroup{}, err
   265  	}
   266  	var matchingGroups []nova.SecurityGroup
   267  	for _, group := range allGroups {
   268  		if re.MatchString(group.Name) {
   269  			matchingGroups = append(matchingGroups, group)
   270  		}
   271  	}
   272  	numMatching := len(matchingGroups)
   273  	if numMatching == 0 {
   274  		return nova.SecurityGroup{}, errors.NotFoundf("security groups matching %q", nameRegExp)
   275  	} else if numMatching > 1 {
   276  		return nova.SecurityGroup{}, errors.New(fmt.Sprintf("%d security groups found matching %q, expected 1", numMatching, nameRegExp))
   277  	}
   278  	return matchingGroups[0], nil
   279  }
   280  
   281  func (c *legacyNovaFirewaller) openPortsInGroup(ctx context.ProviderCallContext, nameRegExp string, rules []network.IngressRule) error {
   282  	group, err := c.matchingGroup(ctx, nameRegExp)
   283  	if err != nil {
   284  		return errors.Trace(err)
   285  	}
   286  	novaclient := c.environ.nova()
   287  	ruleInfo := rulesToRuleInfo(group.Id, rules)
   288  	for _, rule := range ruleInfo {
   289  		_, err := novaclient.CreateSecurityGroupRule(legacyRuleInfo(rule))
   290  		if err != nil {
   291  			common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   292  			// TODO: if err is not rule already exists, raise?
   293  			logger.Debugf("error creating security group rule: %v", err.Error())
   294  		}
   295  	}
   296  	return nil
   297  }
   298  
   299  func legacyRuleInfo(in neutron.RuleInfoV2) nova.RuleInfo {
   300  	return nova.RuleInfo{
   301  		ParentGroupId: in.ParentGroupId,
   302  		FromPort:      in.PortRangeMin,
   303  		ToPort:        in.PortRangeMax,
   304  		IPProtocol:    in.IPProtocol,
   305  		Cidr:          in.RemoteIPPrefix,
   306  	}
   307  }
   308  
   309  // ruleMatchesPortRange checks if supplied nova security group rule matches the port range
   310  func legacyRuleMatchesPortRange(rule nova.SecurityGroupRule, portRange network.IngressRule) bool {
   311  	if rule.IPProtocol == nil || rule.FromPort == nil || rule.ToPort == nil {
   312  		return false
   313  	}
   314  	return *rule.IPProtocol == portRange.Protocol &&
   315  		*rule.FromPort == portRange.FromPort &&
   316  		*rule.ToPort == portRange.ToPort
   317  }
   318  
   319  func (c *legacyNovaFirewaller) closePortsInGroup(ctx context.ProviderCallContext, nameRegExp string, rules []network.IngressRule) error {
   320  	if len(rules) == 0 {
   321  		return nil
   322  	}
   323  	group, err := c.matchingGroup(ctx, nameRegExp)
   324  	if err != nil {
   325  		return errors.Trace(err)
   326  	}
   327  	novaclient := c.environ.nova()
   328  	for _, portRange := range rules {
   329  		for _, p := range group.Rules {
   330  			if !legacyRuleMatchesPortRange(p, portRange) {
   331  				continue
   332  			}
   333  			err := novaclient.DeleteSecurityGroupRule(p.Id)
   334  			if err != nil {
   335  				common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   336  				return errors.Trace(err)
   337  			}
   338  			break
   339  		}
   340  	}
   341  	return nil
   342  }
   343  
   344  func (c *legacyNovaFirewaller) ingressRulesInGroup(ctx context.ProviderCallContext, nameRegexp string) (rules []network.IngressRule, err error) {
   345  	group, err := c.matchingGroup(ctx, nameRegexp)
   346  	if err != nil {
   347  		return nil, errors.Trace(err)
   348  	}
   349  	// Keep track of all the RemoteIPPrefixes for each port range.
   350  	portSourceCIDRs := make(map[corenetwork.PortRange]*[]string)
   351  	for _, p := range group.Rules {
   352  		portRange := corenetwork.PortRange{*p.FromPort, *p.ToPort, *p.IPProtocol}
   353  		// Record the RemoteIPPrefix for the port range.
   354  		remotePrefix := p.IPRange["cidr"]
   355  		if remotePrefix == "" {
   356  			remotePrefix = "0.0.0.0/0"
   357  		}
   358  		sourceCIDRs, ok := portSourceCIDRs[portRange]
   359  		if !ok {
   360  			sourceCIDRs = &[]string{}
   361  			portSourceCIDRs[portRange] = sourceCIDRs
   362  		}
   363  		*sourceCIDRs = append(*sourceCIDRs, remotePrefix)
   364  	}
   365  	// Combine all the port ranges and remote prefixes.
   366  	for portRange, sourceCIDRs := range portSourceCIDRs {
   367  		rule, err := network.NewIngressRule(
   368  			portRange.Protocol,
   369  			portRange.FromPort,
   370  			portRange.ToPort,
   371  			*sourceCIDRs...)
   372  		if err != nil {
   373  			common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   374  			return nil, errors.Trace(err)
   375  		}
   376  		rules = append(rules, rule)
   377  	}
   378  	network.SortIngressRules(rules)
   379  	return rules, nil
   380  }