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

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package openstack
     5  
     6  import (
     7  	"fmt"
     8  	"regexp"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/juju/clock"
    14  	"github.com/juju/errors"
    15  	"github.com/juju/retry"
    16  	"gopkg.in/goose.v2/neutron"
    17  
    18  	"github.com/juju/juju/core/instance"
    19  	corenetwork "github.com/juju/juju/core/network"
    20  	"github.com/juju/juju/environs"
    21  	"github.com/juju/juju/environs/config"
    22  	"github.com/juju/juju/environs/context"
    23  	"github.com/juju/juju/environs/instances"
    24  	"github.com/juju/juju/network"
    25  	"github.com/juju/juju/provider/common"
    26  )
    27  
    28  const (
    29  	validUUID              = `[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}`
    30  	GroupControllerPattern = `^(?P<prefix>juju-)(?P<controllerUUID>` + validUUID + `)(?P<suffix>-.*)$`
    31  )
    32  
    33  var extractControllerRe = regexp.MustCompile(GroupControllerPattern)
    34  
    35  //factory for obtaining firawaller object.
    36  type FirewallerFactory interface {
    37  	GetFirewaller(env environs.Environ) Firewaller
    38  }
    39  
    40  // Firewaller allows custom openstack provider behaviour.
    41  // This is used in other providers that embed the openstack provider.
    42  type Firewaller interface {
    43  	// OpenPorts opens the given port ranges for the whole environment.
    44  	OpenPorts(ctx context.ProviderCallContext, rules []network.IngressRule) error
    45  
    46  	// ClosePorts closes the given port ranges for the whole environment.
    47  	ClosePorts(ctx context.ProviderCallContext, rules []network.IngressRule) error
    48  
    49  	// IngressRules returns the ingress rules applied to the whole environment.
    50  	// It is expected that there be only one ingress rule result for a given
    51  	// port range - the rule's SourceCIDRs will contain all applicable source
    52  	// address rules for that port range.
    53  	IngressRules(ctx context.ProviderCallContext) ([]network.IngressRule, error)
    54  
    55  	// DeleteAllModelGroups deletes all security groups for the
    56  	// model.
    57  	DeleteAllModelGroups(ctx context.ProviderCallContext) error
    58  
    59  	// DeleteAllControllerGroups deletes all security groups for the
    60  	// controller, ie those for all hosted models.
    61  	DeleteAllControllerGroups(ctx context.ProviderCallContext, controllerUUID string) error
    62  
    63  	// DeleteGroups deletes the security groups with the specified names.
    64  	DeleteGroups(ctx context.ProviderCallContext, names ...string) error
    65  
    66  	// UpdateGroupController updates all of the security groups for
    67  	// this model to refer to the specified controller, such that
    68  	// DeleteAllControllerGroups will remove them only when called
    69  	// with the specified controller ID.
    70  	UpdateGroupController(ctx context.ProviderCallContext, controllerUUID string) error
    71  
    72  	// GetSecurityGroups returns a list of the security groups that
    73  	// belong to given instances.
    74  	GetSecurityGroups(ctx context.ProviderCallContext, ids ...instance.Id) ([]string, error)
    75  
    76  	// SetUpGroups sets up initial security groups, if any, and returns
    77  	// their names.
    78  	SetUpGroups(ctx context.ProviderCallContext, controllerUUID, machineId string, apiPort int) ([]string, error)
    79  
    80  	// OpenInstancePorts opens the given port ranges for the specified  instance.
    81  	OpenInstancePorts(ctx context.ProviderCallContext, inst instances.Instance, machineId string, rules []network.IngressRule) error
    82  
    83  	// CloseInstancePorts closes the given port ranges for the specified  instance.
    84  	CloseInstancePorts(ctx context.ProviderCallContext, inst instances.Instance, machineId string, rules []network.IngressRule) error
    85  
    86  	// InstanceIngressRules returns the ingress rules applied to the specified  instance.
    87  	InstanceIngressRules(ctx context.ProviderCallContext, inst instances.Instance, machineId string) ([]network.IngressRule, error)
    88  }
    89  
    90  type firewallerFactory struct {
    91  }
    92  
    93  // GetFirewaller implements FirewallerFactory
    94  func (f *firewallerFactory) GetFirewaller(env environs.Environ) Firewaller {
    95  	return &switchingFirewaller{env: env.(*Environ)}
    96  }
    97  
    98  type switchingFirewaller struct {
    99  	env *Environ
   100  
   101  	mu sync.Mutex
   102  	fw Firewaller
   103  }
   104  
   105  func (f *switchingFirewaller) initFirewaller(ctx context.ProviderCallContext) error {
   106  	f.mu.Lock()
   107  	defer f.mu.Unlock()
   108  	if f.fw != nil {
   109  		return nil
   110  	}
   111  
   112  	client := f.env.client()
   113  	if !client.IsAuthenticated() {
   114  		if err := authenticateClient(client); err != nil {
   115  			common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   116  			return errors.Trace(err)
   117  		}
   118  	}
   119  
   120  	if f.env.supportsNeutron() {
   121  		f.fw = &neutronFirewaller{firewallerBase{environ: f.env}}
   122  	} else {
   123  		f.fw = &legacyNovaFirewaller{firewallerBase{environ: f.env}}
   124  	}
   125  	return nil
   126  }
   127  
   128  func (f *switchingFirewaller) OpenPorts(ctx context.ProviderCallContext, rules []network.IngressRule) error {
   129  	if err := f.initFirewaller(ctx); err != nil {
   130  		return errors.Trace(err)
   131  	}
   132  	return f.fw.OpenPorts(ctx, rules)
   133  }
   134  
   135  func (f *switchingFirewaller) ClosePorts(ctx context.ProviderCallContext, rules []network.IngressRule) error {
   136  	if err := f.initFirewaller(ctx); err != nil {
   137  		return errors.Trace(err)
   138  	}
   139  	return f.fw.ClosePorts(ctx, rules)
   140  }
   141  
   142  func (f *switchingFirewaller) IngressRules(ctx context.ProviderCallContext) ([]network.IngressRule, error) {
   143  	if err := f.initFirewaller(ctx); err != nil {
   144  		return nil, errors.Trace(err)
   145  	}
   146  	return f.fw.IngressRules(ctx)
   147  }
   148  
   149  func (f *switchingFirewaller) DeleteAllModelGroups(ctx context.ProviderCallContext) error {
   150  	if err := f.initFirewaller(ctx); err != nil {
   151  		return errors.Trace(err)
   152  	}
   153  	return f.fw.DeleteAllModelGroups(ctx)
   154  }
   155  
   156  func (f *switchingFirewaller) DeleteAllControllerGroups(ctx context.ProviderCallContext, controllerUUID string) error {
   157  	if err := f.initFirewaller(ctx); err != nil {
   158  		return errors.Trace(err)
   159  	}
   160  	return f.fw.DeleteAllControllerGroups(ctx, controllerUUID)
   161  }
   162  
   163  func (f *switchingFirewaller) DeleteGroups(ctx context.ProviderCallContext, names ...string) error {
   164  	if err := f.initFirewaller(ctx); err != nil {
   165  		return errors.Trace(err)
   166  	}
   167  	return f.fw.DeleteGroups(ctx, names...)
   168  }
   169  
   170  func (f *switchingFirewaller) UpdateGroupController(ctx context.ProviderCallContext, controllerUUID string) error {
   171  	if err := f.initFirewaller(ctx); err != nil {
   172  		return errors.Trace(err)
   173  	}
   174  	return f.fw.UpdateGroupController(ctx, controllerUUID)
   175  }
   176  
   177  func (f *switchingFirewaller) GetSecurityGroups(ctx context.ProviderCallContext, ids ...instance.Id) ([]string, error) {
   178  	if err := f.initFirewaller(ctx); err != nil {
   179  		return nil, errors.Trace(err)
   180  	}
   181  	return f.fw.GetSecurityGroups(ctx, ids...)
   182  }
   183  
   184  func (f *switchingFirewaller) SetUpGroups(ctx context.ProviderCallContext, controllerUUID, machineId string, apiPort int) ([]string, error) {
   185  	if err := f.initFirewaller(ctx); err != nil {
   186  		return nil, errors.Trace(err)
   187  	}
   188  	return f.fw.SetUpGroups(ctx, controllerUUID, machineId, apiPort)
   189  }
   190  
   191  func (f *switchingFirewaller) OpenInstancePorts(ctx context.ProviderCallContext, inst instances.Instance, machineId string, rules []network.IngressRule) error {
   192  	if err := f.initFirewaller(ctx); err != nil {
   193  		return errors.Trace(err)
   194  	}
   195  	return f.fw.OpenInstancePorts(ctx, inst, machineId, rules)
   196  }
   197  
   198  func (f *switchingFirewaller) CloseInstancePorts(ctx context.ProviderCallContext, inst instances.Instance, machineId string, rules []network.IngressRule) error {
   199  	if err := f.initFirewaller(ctx); err != nil {
   200  		return errors.Trace(err)
   201  	}
   202  	return f.fw.CloseInstancePorts(ctx, inst, machineId, rules)
   203  }
   204  
   205  func (f *switchingFirewaller) InstanceIngressRules(ctx context.ProviderCallContext, inst instances.Instance, machineId string) ([]network.IngressRule, error) {
   206  	if err := f.initFirewaller(ctx); err != nil {
   207  		return nil, errors.Trace(err)
   208  	}
   209  	return f.fw.InstanceIngressRules(ctx, inst, machineId)
   210  }
   211  
   212  type firewallerBase struct {
   213  	environ          *Environ
   214  	ensureGroupMutex sync.Mutex
   215  }
   216  
   217  // GetSecurityGroups implements Firewaller interface.
   218  func (c *firewallerBase) GetSecurityGroups(ctx context.ProviderCallContext, ids ...instance.Id) ([]string, error) {
   219  	var securityGroupNames []string
   220  	if c.environ.Config().FirewallMode() == config.FwInstance {
   221  		instances, err := c.environ.Instances(ctx, ids)
   222  		if err != nil {
   223  			return nil, errors.Trace(err)
   224  		}
   225  		novaClient := c.environ.nova()
   226  		securityGroupNames = make([]string, 0, len(ids))
   227  		for _, inst := range instances {
   228  			if inst == nil {
   229  				continue
   230  			}
   231  			serverId, err := instServerId(inst)
   232  			if err != nil {
   233  				return nil, errors.Trace(err)
   234  			}
   235  			groups, err := novaClient.GetServerSecurityGroups(string(inst.Id()))
   236  			if err != nil {
   237  				common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   238  				return nil, errors.Trace(err)
   239  			}
   240  			for _, group := range groups {
   241  				// We only include the group specifically tied to the instance, not
   242  				// any group global to the model itself.
   243  				suffix := fmt.Sprintf("%s-%s", c.environ.Config().UUID(), serverId)
   244  				if strings.HasSuffix(group.Name, suffix) {
   245  					securityGroupNames = append(securityGroupNames, group.Name)
   246  				}
   247  			}
   248  		}
   249  	}
   250  	return securityGroupNames, nil
   251  }
   252  
   253  func instServerId(inst instances.Instance) (string, error) {
   254  	openstackName := inst.(*openstackInstance).getServerDetail().Name
   255  	lastDashPos := strings.LastIndex(openstackName, "-")
   256  	if lastDashPos == -1 {
   257  		return "", errors.Errorf("cannot identify machine ID in openstack server name %q", openstackName)
   258  	}
   259  	serverId := openstackName[lastDashPos+1:]
   260  	return serverId, nil
   261  }
   262  
   263  func deleteSecurityGroupsMatchingName(
   264  	ctx context.ProviderCallContext,
   265  	deleteSecurityGroups func(ctx context.ProviderCallContext, match func(name string) bool) error,
   266  	prefix string,
   267  ) error {
   268  	re, err := regexp.Compile("^" + prefix)
   269  	if err != nil {
   270  		return errors.Trace(err)
   271  	}
   272  	return deleteSecurityGroups(ctx, re.MatchString)
   273  }
   274  
   275  func deleteSecurityGroupsOneOfNames(
   276  	ctx context.ProviderCallContext,
   277  	deleteSecurityGroups func(ctx context.ProviderCallContext, match func(name string) bool) error,
   278  	names ...string,
   279  ) error {
   280  	match := func(check string) bool {
   281  		for _, name := range names {
   282  			if check == name {
   283  				return true
   284  			}
   285  		}
   286  		return false
   287  	}
   288  	return deleteSecurityGroups(ctx, match)
   289  }
   290  
   291  // deleteSecurityGroup attempts to delete the security group. Should it fail,
   292  // the deletion is retried due to timing issues in openstack. A security group
   293  // cannot be deleted while it is in use. Theoretically we terminate all the
   294  // instances before we attempt to delete the associated security groups, but
   295  // in practice neutron hasn't always finished with the instance before it
   296  // returns, so there is a race condition where we think the instance is
   297  // terminated and hence attempt to delete the security groups but nova still
   298  // has it around internally. To attempt to catch this timing issue, deletion
   299  // of the groups is tried multiple times.
   300  func deleteSecurityGroup(
   301  	ctx context.ProviderCallContext,
   302  	deleteSecurityGroupById func(string) error,
   303  	name, id string,
   304  	clock clock.Clock,
   305  ) {
   306  	logger.Debugf("deleting security group %q", name)
   307  	err := retry.Call(retry.CallArgs{
   308  		Func: func() error {
   309  			if err := deleteSecurityGroupById(id); err != nil {
   310  				common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   311  				return errors.Trace(err)
   312  			}
   313  			return nil
   314  		},
   315  		NotifyFunc: func(err error, attempt int) {
   316  			if attempt%4 == 0 {
   317  				message := fmt.Sprintf("waiting to delete security group %q", name)
   318  				if attempt != 4 {
   319  					message = "still " + message
   320  				}
   321  				logger.Debugf(message)
   322  			}
   323  		},
   324  		Attempts: 30,
   325  		Delay:    time.Second,
   326  		Clock:    clock,
   327  	})
   328  	if err != nil {
   329  		logger.Warningf("cannot delete security group %q. Used by another model?", name)
   330  	}
   331  }
   332  
   333  func (c *firewallerBase) openPorts(
   334  	ctx context.ProviderCallContext,
   335  	openPortsInGroup func(context.ProviderCallContext, string, []network.IngressRule) error,
   336  	rules []network.IngressRule,
   337  ) error {
   338  	if c.environ.Config().FirewallMode() != config.FwGlobal {
   339  		return errors.Errorf("invalid firewall mode %q for opening ports on model",
   340  			c.environ.Config().FirewallMode())
   341  	}
   342  	if err := openPortsInGroup(ctx, c.globalGroupRegexp(), rules); err != nil {
   343  		return errors.Trace(err)
   344  	}
   345  	logger.Infof("opened ports in global group: %v", rules)
   346  	return nil
   347  }
   348  
   349  func (c *firewallerBase) closePorts(
   350  	ctx context.ProviderCallContext,
   351  	closePortsInGroup func(context.ProviderCallContext, string, []network.IngressRule) error,
   352  	rules []network.IngressRule,
   353  ) error {
   354  	if c.environ.Config().FirewallMode() != config.FwGlobal {
   355  		return errors.Errorf("invalid firewall mode %q for closing ports on model",
   356  			c.environ.Config().FirewallMode())
   357  	}
   358  	if err := closePortsInGroup(ctx, c.globalGroupRegexp(), rules); err != nil {
   359  		return errors.Trace(err)
   360  	}
   361  	logger.Infof("closed ports in global group: %v", rules)
   362  	return nil
   363  }
   364  
   365  func (c *firewallerBase) ingressRules(
   366  	ctx context.ProviderCallContext,
   367  	ingressRulesInGroup func(context.ProviderCallContext, string) ([]network.IngressRule, error),
   368  ) ([]network.IngressRule, error) {
   369  	if c.environ.Config().FirewallMode() != config.FwGlobal {
   370  		return nil, errors.Errorf("invalid firewall mode %q for retrieving ingress rules from model",
   371  			c.environ.Config().FirewallMode())
   372  	}
   373  	return ingressRulesInGroup(ctx, c.globalGroupRegexp())
   374  }
   375  
   376  func (c *firewallerBase) openInstancePorts(
   377  	ctx context.ProviderCallContext,
   378  	openPortsInGroup func(context.ProviderCallContext, string, []network.IngressRule) error,
   379  	machineId string,
   380  	rules []network.IngressRule,
   381  ) error {
   382  	nameRegexp := c.machineGroupRegexp(machineId)
   383  	if err := openPortsInGroup(ctx, nameRegexp, rules); err != nil {
   384  		return errors.Trace(err)
   385  	}
   386  	logger.Infof("opened ports in security group %s-%s: %v", c.environ.Config().UUID(), machineId, rules)
   387  	return nil
   388  }
   389  
   390  func (c *firewallerBase) closeInstancePorts(
   391  	ctx context.ProviderCallContext,
   392  	closePortsInGroup func(context.ProviderCallContext, string, []network.IngressRule) error,
   393  	machineId string,
   394  	rules []network.IngressRule,
   395  ) error {
   396  	nameRegexp := c.machineGroupRegexp(machineId)
   397  	if err := closePortsInGroup(ctx, nameRegexp, rules); err != nil {
   398  		return errors.Trace(err)
   399  	}
   400  	logger.Infof("closed ports in security group %s-%s: %v", c.environ.Config().UUID(), machineId, rules)
   401  	return nil
   402  }
   403  
   404  func (c *firewallerBase) instanceIngressRules(
   405  	ctx context.ProviderCallContext,
   406  	ingressRulesInGroup func(context.ProviderCallContext, string) ([]network.IngressRule, error),
   407  	machineId string,
   408  ) ([]network.IngressRule, error) {
   409  	nameRegexp := c.machineGroupRegexp(machineId)
   410  	portRanges, err := ingressRulesInGroup(ctx, nameRegexp)
   411  	if err != nil {
   412  		return nil, errors.Trace(err)
   413  	}
   414  	return portRanges, nil
   415  }
   416  
   417  func (c *firewallerBase) globalGroupName(controllerUUID string) string {
   418  	return fmt.Sprintf("%s-global", c.jujuGroupName(controllerUUID))
   419  }
   420  
   421  func (c *firewallerBase) machineGroupName(controllerUUID, machineId string) string {
   422  	return fmt.Sprintf("%s-%s", c.jujuGroupName(controllerUUID), machineId)
   423  }
   424  
   425  func (c *firewallerBase) jujuGroupName(controllerUUID string) string {
   426  	cfg := c.environ.Config()
   427  	return fmt.Sprintf("juju-%v-%v", controllerUUID, cfg.UUID())
   428  }
   429  
   430  func (c *firewallerBase) jujuControllerGroupPrefix(controllerUUID string) string {
   431  	return fmt.Sprintf("juju-%v-", controllerUUID)
   432  }
   433  
   434  func (c *firewallerBase) jujuGroupRegexp() string {
   435  	cfg := c.environ.Config()
   436  	return fmt.Sprintf("juju-.*-%v", cfg.UUID())
   437  }
   438  
   439  func (c *firewallerBase) globalGroupRegexp() string {
   440  	return fmt.Sprintf("%s-global", c.jujuGroupRegexp())
   441  }
   442  
   443  func (c *firewallerBase) machineGroupRegexp(machineId string) string {
   444  	// we are only looking to match 1 machine
   445  	return fmt.Sprintf("%s-%s$", c.jujuGroupRegexp(), machineId)
   446  }
   447  
   448  type neutronFirewaller struct {
   449  	firewallerBase
   450  }
   451  
   452  // SetUpGroups creates the security groups for the new machine, and
   453  // returns them.
   454  //
   455  // Instances are tagged with a group so they can be distinguished from
   456  // other instances that might be running on the same OpenStack account.
   457  // In addition, a specific machine security group is created for each
   458  // machine, so that its firewall rules can be configured per machine.
   459  //
   460  // Note: ideally we'd have a better way to determine group membership so that 2
   461  // people that happen to share an openstack account and name their environment
   462  // "openstack" don't end up destroying each other's machines.
   463  func (c *neutronFirewaller) SetUpGroups(ctx context.ProviderCallContext, controllerUUID, machineId string, apiPort int) ([]string, error) {
   464  	jujuGroup, err := c.setUpGlobalGroup(c.jujuGroupName(controllerUUID), apiPort)
   465  	if err != nil {
   466  		return nil, errors.Trace(err)
   467  	}
   468  	var machineGroup neutron.SecurityGroupV2
   469  	switch c.environ.Config().FirewallMode() {
   470  	case config.FwInstance:
   471  		machineGroup, err = c.ensureGroup(c.machineGroupName(controllerUUID, machineId), nil)
   472  	case config.FwGlobal:
   473  		machineGroup, err = c.ensureGroup(c.globalGroupName(controllerUUID), nil)
   474  	}
   475  	if err != nil {
   476  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   477  		return nil, errors.Trace(err)
   478  	}
   479  	groups := []string{jujuGroup.Name, machineGroup.Name}
   480  	if c.environ.ecfg().useDefaultSecurityGroup() {
   481  		groups = append(groups, "default")
   482  	}
   483  	return groups, nil
   484  }
   485  
   486  func (c *neutronFirewaller) setUpGlobalGroup(groupName string, apiPort int) (neutron.SecurityGroupV2, error) {
   487  	return c.ensureGroup(groupName,
   488  		[]neutron.RuleInfoV2{
   489  			{
   490  				Direction:      "ingress",
   491  				IPProtocol:     "tcp",
   492  				PortRangeMax:   22,
   493  				PortRangeMin:   22,
   494  				RemoteIPPrefix: "::/0",
   495  				EthernetType:   "IPv6",
   496  			},
   497  			{
   498  				Direction:      "ingress",
   499  				IPProtocol:     "tcp",
   500  				PortRangeMax:   22,
   501  				PortRangeMin:   22,
   502  				RemoteIPPrefix: "0.0.0.0/0",
   503  			},
   504  			{
   505  				Direction:      "ingress",
   506  				IPProtocol:     "tcp",
   507  				PortRangeMax:   apiPort,
   508  				PortRangeMin:   apiPort,
   509  				RemoteIPPrefix: "::/0",
   510  				EthernetType:   "IPv6",
   511  			},
   512  			{
   513  				Direction:      "ingress",
   514  				IPProtocol:     "tcp",
   515  				PortRangeMax:   apiPort,
   516  				PortRangeMin:   apiPort,
   517  				RemoteIPPrefix: "0.0.0.0/0",
   518  			},
   519  			{
   520  				Direction:    "ingress",
   521  				IPProtocol:   "tcp",
   522  				PortRangeMin: 1,
   523  				PortRangeMax: 65535,
   524  				EthernetType: "IPv6",
   525  			},
   526  			{
   527  				Direction:    "ingress",
   528  				IPProtocol:   "tcp",
   529  				PortRangeMin: 1,
   530  				PortRangeMax: 65535,
   531  			},
   532  			{
   533  				Direction:    "ingress",
   534  				IPProtocol:   "udp",
   535  				PortRangeMin: 1,
   536  				PortRangeMax: 65535,
   537  				EthernetType: "IPv6",
   538  			},
   539  			{
   540  				Direction:    "ingress",
   541  				IPProtocol:   "udp",
   542  				PortRangeMin: 1,
   543  				PortRangeMax: 65535,
   544  			},
   545  			{
   546  				Direction:    "ingress",
   547  				IPProtocol:   "icmp",
   548  				EthernetType: "IPv6",
   549  			},
   550  			{
   551  				Direction:  "ingress",
   552  				IPProtocol: "icmp",
   553  			},
   554  		})
   555  }
   556  
   557  // zeroGroup holds the zero security group.
   558  var zeroGroup neutron.SecurityGroupV2
   559  
   560  // ensureGroup returns the security group with name and rules.
   561  // If a group with name does not exist, one will be created.
   562  // If it exists, its permissions are set to rules.
   563  func (c *neutronFirewaller) ensureGroup(name string, rules []neutron.RuleInfoV2) (neutron.SecurityGroupV2, error) {
   564  	neutronClient := c.environ.neutron()
   565  	var group neutron.SecurityGroupV2
   566  
   567  	// Due to parallelization of the provisioner, it's possible that we try
   568  	// to create the model security group a second time before the first time
   569  	// is complete causing failures.
   570  	c.ensureGroupMutex.Lock()
   571  	defer c.ensureGroupMutex.Unlock()
   572  	// First attempt to look up an existing group by name.
   573  	groupsFound, err := neutronClient.SecurityGroupByNameV2(name)
   574  	// a list is returned, but there should be only one
   575  	if err == nil && len(groupsFound) == 1 {
   576  		group = groupsFound[0]
   577  	} else if err != nil && strings.Contains(err.Error(), "failed to find security group") {
   578  		// TODO(hml): We should use a typed error here.  SecurityGroupByNameV2
   579  		// doesn't currently return one for this case.
   580  		g, err := neutronClient.CreateSecurityGroupV2(name, "juju group")
   581  		if err != nil {
   582  			return zeroGroup, err
   583  		}
   584  		group = *g
   585  	} else if err == nil && len(groupsFound) > 1 {
   586  		// TODO(hml): Add unit test for this case
   587  		return zeroGroup, errors.New(fmt.Sprintf("More than one security group named %s was found", name))
   588  	} else {
   589  		return zeroGroup, err
   590  	}
   591  
   592  	have := newRuleInfoSetFromRules(group.Rules)
   593  	want := newRuleInfoSetFromRuleInfo(rules)
   594  
   595  	// Find rules we want to delete, that we have but don't want, and
   596  	// delete them.
   597  	remove := make(ruleInfoSet)
   598  	for k := range have {
   599  		// Neutron creates 2 egress rules with any new Security Group.
   600  		// Keep them.
   601  		if _, ok := want[k]; !ok && k.Direction != "egress" {
   602  			remove[k] = have[k]
   603  		}
   604  	}
   605  	for _, ruleId := range remove {
   606  		if err = neutronClient.DeleteSecurityGroupRuleV2(ruleId); err != nil {
   607  			return zeroGroup, err
   608  		}
   609  	}
   610  
   611  	// Find rules we want to add, that we want but don't have, and add
   612  	// them.
   613  	add := make(ruleInfoSet)
   614  	for k := range want {
   615  		if _, ok := have[k]; !ok {
   616  			add[k] = want[k]
   617  		}
   618  	}
   619  	for rule := range add {
   620  		rule.ParentGroupId = group.Id
   621  		// Neutron translates empty RemoteIPPrefix into
   622  		// 0.0.0.0/0 or ::/0 instead of ParentGroupId
   623  		// when EthernetType is set
   624  		if rule.RemoteIPPrefix == "" {
   625  			rule.RemoteGroupId = group.Id
   626  		}
   627  		if _, err := neutronClient.CreateSecurityGroupRuleV2(rule); err != nil {
   628  			return zeroGroup, err
   629  		}
   630  	}
   631  
   632  	// Since we may have done a few add or delete rules, get a new
   633  	// copy of the security group to return containing the end
   634  	// list of rules.
   635  	groupsFound, err = neutronClient.SecurityGroupByNameV2(name)
   636  	if err != nil {
   637  		return zeroGroup, err
   638  	} else if len(groupsFound) > 1 {
   639  		// TODO(hml): Add unit test for this case
   640  		return zeroGroup, errors.New(fmt.Sprintf("More than one security group named %s was found after group was ensured", name))
   641  	}
   642  	return groupsFound[0], nil
   643  }
   644  
   645  // ruleInfoSet represents a Security Group Rule created for a Security Group.
   646  // The string will be the Security Group Rule Id, if the rule has previously been
   647  // created.
   648  type ruleInfoSet map[neutron.RuleInfoV2]string
   649  
   650  // newRuleSetForGroup returns a set of all of the permissions in a given
   651  // slice of SecurityGroupRules.  It ignores the group id, the
   652  // remove group id, and tenant id.  Keep the rule id to delete the rule if
   653  // necessary.
   654  func newRuleInfoSetFromRules(rules []neutron.SecurityGroupRuleV2) ruleInfoSet {
   655  	m := make(ruleInfoSet)
   656  	for _, r := range rules {
   657  		k := neutron.RuleInfoV2{
   658  			Direction:      r.Direction,
   659  			EthernetType:   r.EthernetType,
   660  			RemoteIPPrefix: r.RemoteIPPrefix,
   661  		}
   662  		if r.IPProtocol != nil {
   663  			k.IPProtocol = *r.IPProtocol
   664  		}
   665  		if r.PortRangeMax != nil {
   666  			k.PortRangeMax = *r.PortRangeMax
   667  		}
   668  		if r.PortRangeMin != nil {
   669  			k.PortRangeMin = *r.PortRangeMin
   670  		}
   671  		m[k] = r.Id
   672  	}
   673  	return m
   674  }
   675  
   676  // newRuleSetForGroup returns a set of all of the permissions in a given
   677  // slice of RuleInfo.  It ignores the rule id, the group id, the
   678  // remove group id, and tenant id.
   679  func newRuleInfoSetFromRuleInfo(rules []neutron.RuleInfoV2) ruleInfoSet {
   680  	m := make(ruleInfoSet)
   681  	for _, r := range rules {
   682  		k := neutron.RuleInfoV2{
   683  			Direction:      r.Direction,
   684  			IPProtocol:     r.IPProtocol,
   685  			PortRangeMin:   r.PortRangeMin,
   686  			PortRangeMax:   r.PortRangeMax,
   687  			EthernetType:   r.EthernetType,
   688  			RemoteIPPrefix: r.RemoteIPPrefix,
   689  		}
   690  		m[k] = ""
   691  	}
   692  	return m
   693  }
   694  
   695  func (c *neutronFirewaller) deleteSecurityGroups(ctx context.ProviderCallContext, match func(name string) bool) error {
   696  	neutronClient := c.environ.neutron()
   697  	securityGroups, err := neutronClient.ListSecurityGroupsV2()
   698  	if err != nil {
   699  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   700  		return errors.Annotate(err, "cannot list security groups")
   701  	}
   702  	for _, group := range securityGroups {
   703  		if match(group.Name) {
   704  			deleteSecurityGroup(
   705  				ctx,
   706  				neutronClient.DeleteSecurityGroupV2,
   707  				group.Name,
   708  				group.Id,
   709  				clock.WallClock,
   710  			)
   711  		}
   712  	}
   713  	return nil
   714  }
   715  
   716  // DeleteGroups implements Firewaller interface.
   717  func (c *neutronFirewaller) DeleteGroups(ctx context.ProviderCallContext, names ...string) error {
   718  	return deleteSecurityGroupsOneOfNames(ctx, c.deleteSecurityGroups, names...)
   719  }
   720  
   721  // DeleteAllControllerGroups implements Firewaller interface.
   722  func (c *neutronFirewaller) DeleteAllControllerGroups(ctx context.ProviderCallContext, controllerUUID string) error {
   723  	return deleteSecurityGroupsMatchingName(ctx, c.deleteSecurityGroups, c.jujuControllerGroupPrefix(controllerUUID))
   724  }
   725  
   726  // DeleteAllModelGroups implements Firewaller interface.
   727  func (c *neutronFirewaller) DeleteAllModelGroups(ctx context.ProviderCallContext) error {
   728  	return deleteSecurityGroupsMatchingName(ctx, c.deleteSecurityGroups, c.jujuGroupRegexp())
   729  }
   730  
   731  // UpdateGroupController implements Firewaller interface.
   732  func (c *neutronFirewaller) UpdateGroupController(ctx context.ProviderCallContext, controllerUUID string) error {
   733  	neutronClient := c.environ.neutron()
   734  	groups, err := neutronClient.ListSecurityGroupsV2()
   735  	if err != nil {
   736  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   737  		return errors.Trace(err)
   738  	}
   739  	re, err := regexp.Compile(c.jujuGroupRegexp())
   740  	if err != nil {
   741  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   742  		return errors.Trace(err)
   743  	}
   744  
   745  	var failed []string
   746  	for _, group := range groups {
   747  		if !re.MatchString(group.Name) {
   748  			continue
   749  		}
   750  		err := c.updateGroupControllerUUID(&group, controllerUUID)
   751  		if err != nil {
   752  			logger.Errorf("error updating controller for security group %s: %v", group.Id, err)
   753  			failed = append(failed, group.Id)
   754  			if common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx) {
   755  				// No need to continue here since we will 100% fail with an invalid credential.
   756  				break
   757  			}
   758  
   759  		}
   760  	}
   761  	if len(failed) != 0 {
   762  		return errors.Errorf("errors updating controller for security groups: %v", failed)
   763  	}
   764  	return nil
   765  }
   766  
   767  func (c *neutronFirewaller) updateGroupControllerUUID(group *neutron.SecurityGroupV2, controllerUUID string) error {
   768  	newName, err := replaceControllerUUID(group.Name, controllerUUID)
   769  	if err != nil {
   770  		return errors.Trace(err)
   771  	}
   772  	client := c.environ.neutron()
   773  	_, err = client.UpdateSecurityGroupV2(group.Id, newName, group.Description)
   774  	return errors.Trace(err)
   775  }
   776  
   777  // OpenPorts implements Firewaller interface.
   778  func (c *neutronFirewaller) OpenPorts(ctx context.ProviderCallContext, rules []network.IngressRule) error {
   779  	err := c.openPorts(ctx, c.openPortsInGroup, rules)
   780  	if err != nil {
   781  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   782  		return errors.Trace(err)
   783  	}
   784  	return nil
   785  }
   786  
   787  // ClosePorts implements Firewaller interface.
   788  func (c *neutronFirewaller) ClosePorts(ctx context.ProviderCallContext, rules []network.IngressRule) error {
   789  	err := c.closePorts(ctx, c.closePortsInGroup, rules)
   790  	if err != nil {
   791  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   792  		return errors.Trace(err)
   793  	}
   794  	return nil
   795  }
   796  
   797  // IngressRules implements Firewaller interface.
   798  func (c *neutronFirewaller) IngressRules(ctx context.ProviderCallContext) ([]network.IngressRule, error) {
   799  	rules, err := c.ingressRules(ctx, c.ingressRulesInGroup)
   800  	if err != nil {
   801  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   802  		return rules, errors.Trace(err)
   803  	}
   804  	return rules, nil
   805  }
   806  
   807  // OpenInstancePorts implements Firewaller interface.
   808  func (c *neutronFirewaller) OpenInstancePorts(ctx context.ProviderCallContext, inst instances.Instance, machineId string, ports []network.IngressRule) error {
   809  	if c.environ.Config().FirewallMode() != config.FwInstance {
   810  		return errors.Errorf("invalid firewall mode %q for opening ports on instance",
   811  			c.environ.Config().FirewallMode())
   812  	}
   813  	// For bug 1680787
   814  	// No security groups exist if the network used to boot the instance has
   815  	// PortSecurityEnabled set to false.  To avoid filling up the log files,
   816  	// skip trying to open ports in this cases.
   817  	if securityGroups := inst.(*openstackInstance).getServerDetail().Groups; securityGroups == nil {
   818  		return nil
   819  	}
   820  	err := c.openInstancePorts(ctx, c.openPortsInGroup, machineId, ports)
   821  	if err != nil {
   822  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   823  		return errors.Trace(err)
   824  	}
   825  	return nil
   826  }
   827  
   828  // CloseInstancePorts implements Firewaller interface.
   829  func (c *neutronFirewaller) CloseInstancePorts(ctx context.ProviderCallContext, inst instances.Instance, machineId string, ports []network.IngressRule) error {
   830  	if c.environ.Config().FirewallMode() != config.FwInstance {
   831  		return errors.Errorf("invalid firewall mode %q for closing ports on instance",
   832  			c.environ.Config().FirewallMode())
   833  	}
   834  	// For bug 1680787
   835  	// No security groups exist if the network used to boot the instance has
   836  	// PortSecurityEnabled set to false.  To avoid filling up the log files,
   837  	// skip trying to open ports in this cases.
   838  	if securityGroups := inst.(*openstackInstance).getServerDetail().Groups; securityGroups == nil {
   839  		return nil
   840  	}
   841  	err := c.closeInstancePorts(ctx, c.closePortsInGroup, machineId, ports)
   842  	if err != nil {
   843  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   844  		return errors.Trace(err)
   845  	}
   846  	return nil
   847  }
   848  
   849  // InstanceIngressRules implements Firewaller interface.
   850  func (c *neutronFirewaller) InstanceIngressRules(ctx context.ProviderCallContext, inst instances.Instance, machineId string) ([]network.IngressRule, error) {
   851  	if c.environ.Config().FirewallMode() != config.FwInstance {
   852  		return nil, errors.Errorf("invalid firewall mode %q for retrieving ingress rules from instance",
   853  			c.environ.Config().FirewallMode())
   854  	}
   855  	// For bug 1680787
   856  	// No security groups exist if the network used to boot the instance has
   857  	// PortSecurityEnabled set to false.  To avoid filling up the log files,
   858  	// skip trying to open ports in this cases.
   859  	if securityGroups := inst.(*openstackInstance).getServerDetail().Groups; securityGroups == nil {
   860  		return []network.IngressRule{}, nil
   861  	}
   862  	rules, err := c.instanceIngressRules(ctx, c.ingressRulesInGroup, machineId)
   863  	if err != nil {
   864  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   865  		return rules, errors.Trace(err)
   866  	}
   867  	return rules, err
   868  }
   869  
   870  // Matching a security group by name only works if each name is unqiue.  Neutron
   871  // security groups are not required to have unique names.  Juju constructs unique
   872  // names, but there are frequently multiple matches to 'default'
   873  func (c *neutronFirewaller) matchingGroup(ctx context.ProviderCallContext, nameRegExp string) (neutron.SecurityGroupV2, error) {
   874  	re, err := regexp.Compile(nameRegExp)
   875  	if err != nil {
   876  		return neutron.SecurityGroupV2{}, err
   877  	}
   878  	neutronClient := c.environ.neutron()
   879  	allGroups, err := neutronClient.ListSecurityGroupsV2()
   880  	if err != nil {
   881  		common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   882  		return neutron.SecurityGroupV2{}, err
   883  	}
   884  	var matchingGroups []neutron.SecurityGroupV2
   885  	for _, group := range allGroups {
   886  		if re.MatchString(group.Name) {
   887  			matchingGroups = append(matchingGroups, group)
   888  		}
   889  	}
   890  	numMatching := len(matchingGroups)
   891  	if numMatching == 0 {
   892  		return neutron.SecurityGroupV2{}, errors.NotFoundf("security groups matching %q", nameRegExp)
   893  	} else if numMatching > 1 {
   894  		return neutron.SecurityGroupV2{}, errors.New(fmt.Sprintf("%d security groups found matching %q, expected 1", numMatching, nameRegExp))
   895  	}
   896  	return matchingGroups[0], nil
   897  }
   898  
   899  func (c *neutronFirewaller) openPortsInGroup(ctx context.ProviderCallContext, nameRegExp string, rules []network.IngressRule) error {
   900  	group, err := c.matchingGroup(ctx, nameRegExp)
   901  	if err != nil {
   902  		return errors.Trace(err)
   903  	}
   904  	neutronClient := c.environ.neutron()
   905  	ruleInfo := rulesToRuleInfo(group.Id, rules)
   906  	for _, rule := range ruleInfo {
   907  		_, err := neutronClient.CreateSecurityGroupRuleV2(rule)
   908  		if err != nil {
   909  			common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   910  			// TODO: if err is not rule already exists, raise?
   911  			logger.Debugf("error creating security group rule: %v", err.Error())
   912  		}
   913  	}
   914  	return nil
   915  }
   916  
   917  // secGroupMatchesIngressRule checks if supplied nova security group rule matches the ingress rule
   918  func secGroupMatchesIngressRule(secGroupRule neutron.SecurityGroupRuleV2, rule network.IngressRule) bool {
   919  	if secGroupRule.IPProtocol == nil ||
   920  		secGroupRule.PortRangeMax == nil || *secGroupRule.PortRangeMax == 0 ||
   921  		secGroupRule.PortRangeMin == nil || *secGroupRule.PortRangeMin == 0 {
   922  		return false
   923  	}
   924  	portsMatch := *secGroupRule.IPProtocol == rule.Protocol &&
   925  		*secGroupRule.PortRangeMin == rule.FromPort &&
   926  		*secGroupRule.PortRangeMax == rule.ToPort
   927  	if !portsMatch {
   928  		return false
   929  	}
   930  	// The ports match, so if the security group RemoteIPPrefix matches *any* of the
   931  	// rule's source ranges, then that's a match.
   932  	if len(rule.SourceCIDRs) == 0 {
   933  		return secGroupRule.RemoteIPPrefix == "" || secGroupRule.RemoteIPPrefix == "0.0.0.0/0"
   934  	}
   935  	for _, r := range rule.SourceCIDRs {
   936  		if r == secGroupRule.RemoteIPPrefix {
   937  			return true
   938  		}
   939  	}
   940  	return false
   941  }
   942  
   943  func (c *neutronFirewaller) closePortsInGroup(ctx context.ProviderCallContext, nameRegExp string, rules []network.IngressRule) error {
   944  	if len(rules) == 0 {
   945  		return nil
   946  	}
   947  	group, err := c.matchingGroup(ctx, nameRegExp)
   948  	if err != nil {
   949  		return errors.Trace(err)
   950  	}
   951  	neutronClient := c.environ.neutron()
   952  	// TODO: Hey look ma, it's quadratic
   953  	for _, rule := range rules {
   954  		for _, p := range group.Rules {
   955  			if !secGroupMatchesIngressRule(p, rule) {
   956  				continue
   957  			}
   958  			err := neutronClient.DeleteSecurityGroupRuleV2(p.Id)
   959  			if err != nil {
   960  				common.HandleCredentialError(IsAuthorisationFailure, err, ctx)
   961  				return errors.Trace(err)
   962  			}
   963  			break
   964  		}
   965  	}
   966  	return nil
   967  }
   968  
   969  func (c *neutronFirewaller) ingressRulesInGroup(ctx context.ProviderCallContext, nameRegexp string) (rules []network.IngressRule, err error) {
   970  	group, err := c.matchingGroup(ctx, nameRegexp)
   971  	if err != nil {
   972  		return nil, errors.Trace(err)
   973  	}
   974  	// Keep track of all the RemoteIPPrefixes for each port range.
   975  	portSourceCIDRs := make(map[corenetwork.PortRange]*[]string)
   976  	for _, p := range group.Rules {
   977  		// Skip the default Security Group Rules created by Neutron
   978  		if p.Direction == "egress" {
   979  			continue
   980  		}
   981  		portRange := corenetwork.PortRange{
   982  			Protocol: *p.IPProtocol,
   983  		}
   984  		if p.PortRangeMin != nil {
   985  			portRange.FromPort = *p.PortRangeMin
   986  		}
   987  		if p.PortRangeMax != nil {
   988  			portRange.ToPort = *p.PortRangeMax
   989  		}
   990  		// Record the RemoteIPPrefix for the port range.
   991  		remotePrefix := p.RemoteIPPrefix
   992  		if remotePrefix == "" {
   993  			remotePrefix = "0.0.0.0/0"
   994  		}
   995  		sourceCIDRs, ok := portSourceCIDRs[portRange]
   996  		if !ok {
   997  			sourceCIDRs = &[]string{}
   998  			portSourceCIDRs[portRange] = sourceCIDRs
   999  		}
  1000  		*sourceCIDRs = append(*sourceCIDRs, remotePrefix)
  1001  	}
  1002  	// Combine all the port ranges and remote prefixes.
  1003  	for portRange, sourceCIDRs := range portSourceCIDRs {
  1004  		rule, err := network.NewIngressRule(
  1005  			portRange.Protocol,
  1006  			portRange.FromPort,
  1007  			portRange.ToPort,
  1008  			*sourceCIDRs...)
  1009  		if err != nil {
  1010  			return nil, errors.Trace(err)
  1011  		}
  1012  		rules = append(rules, rule)
  1013  	}
  1014  	network.SortIngressRules(rules)
  1015  	return rules, nil
  1016  }
  1017  
  1018  func replaceControllerUUID(oldName, controllerUUID string) (string, error) {
  1019  	if !extractControllerRe.MatchString(oldName) {
  1020  		return "", errors.Errorf("unexpected security group name format for %q", oldName)
  1021  	}
  1022  	newName := extractControllerRe.ReplaceAllString(
  1023  		oldName,
  1024  		"${prefix}"+controllerUUID+"${suffix}",
  1025  	)
  1026  	return newName, nil
  1027  }