github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/gce/google/conn_network.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package google
     5  
     6  import (
     7  	"fmt"
     8  	"math/rand"
     9  	"sort"
    10  
    11  	"github.com/juju/collections/set"
    12  	"github.com/juju/errors"
    13  	"google.golang.org/api/compute/v1"
    14  
    15  	"github.com/juju/juju/network"
    16  )
    17  
    18  // FirewallRules collects the firewall rules for the given name
    19  // (within the Connection's project) and returns them as a RuleSet. If
    20  // no rules match the name the RuleSet will be empty and no error is
    21  // returned.
    22  func (gce Connection) firewallRules(fwname string) (ruleSet, error) {
    23  	firewalls, err := gce.raw.GetFirewalls(gce.projectID, fwname)
    24  	if errors.IsNotFound(err) {
    25  		return make(ruleSet), nil
    26  	}
    27  	if err != nil {
    28  		return nil, errors.Annotate(err, "while getting firewall rules from GCE")
    29  	}
    30  
    31  	return newRuleSetFromFirewalls(firewalls...)
    32  }
    33  
    34  // IngressRules build a list of all open port ranges for a given firewall name
    35  // (within the Connection's project) and returns it. If the firewall
    36  // does not exist then the list will be empty and no error is returned.
    37  func (gce Connection) IngressRules(fwname string) ([]network.IngressRule, error) {
    38  	ruleset, err := gce.firewallRules(fwname)
    39  	if err != nil {
    40  		return nil, errors.Trace(err)
    41  	}
    42  	return ruleset.toIngressRules()
    43  }
    44  
    45  // OpenPorts adds or updates GCE firewall rules so that traffic to the
    46  // target ports is allowed from the source ranges specified by the
    47  // ingress rules. If a rule matching a set of source ranges doesn't
    48  // already exist, it will be created - the name will be made unique
    49  // using a random suffix.
    50  func (gce Connection) OpenPorts(target string, rules ...network.IngressRule) error {
    51  	return errors.Trace(gce.OpenPortsWithNamer(target, RandomSuffixNamer, rules...))
    52  }
    53  
    54  // FirewallNamer generates a unique name for a firewall given the firewall, a
    55  // prefix and a set of current firewall rule names.
    56  type FirewallNamer func(fw *firewall, prefix string, existingNames set.Strings) (string, error)
    57  
    58  // OpenPortsWithNamer adds or creates firewall rules in the same way
    59  // as OpenPorts, but uses the FirewallNamer passed in to generate the
    60  // firewall name - this is mostly useful for getting predictable
    61  // results in tests.
    62  func (gce Connection) OpenPortsWithNamer(target string, namer FirewallNamer, rules ...network.IngressRule) error {
    63  	if len(rules) == 0 {
    64  		return nil
    65  	}
    66  
    67  	// First gather the current ingress rules.
    68  	currentRuleSet, err := gce.firewallRules(target)
    69  	if err != nil {
    70  		return errors.Trace(err)
    71  	}
    72  	// From the input rules, compose the firewall specs we want to add.
    73  	inputRuleSet := newRuleSetFromRules(rules...)
    74  
    75  	// For each input rule, either create a new firewall or update
    76  	// an existing one depending on what exists already.
    77  	// The rules are keyed by a hash of the source CIDRs.
    78  	var sortedKeys []string
    79  	for key := range inputRuleSet {
    80  		sortedKeys = append(sortedKeys, key)
    81  	}
    82  	sort.Strings(sortedKeys)
    83  
    84  	allNames := currentRuleSet.allNames()
    85  
    86  	// Get the rules by sorted key for deterministic testing.
    87  	for _, key := range sortedKeys {
    88  		inputFirewall := inputRuleSet[key]
    89  
    90  		// First check to see if there's any existing firewall with the same ports as what we want.
    91  		existingFirewall, ok := currentRuleSet.matchProtocolPorts(inputFirewall.AllowedPorts)
    92  		if !ok {
    93  			// If not, look for any existing firewall with the same source CIDRs.
    94  			existingFirewall, ok = currentRuleSet.matchSourceCIDRs(inputFirewall.SourceCIDRs)
    95  		}
    96  
    97  		if !ok {
    98  			// Create a new firewall.
    99  			name, err := namer(inputFirewall, target, allNames)
   100  			if err != nil {
   101  				return errors.Trace(err)
   102  			}
   103  			allNames.Add(name)
   104  			spec := firewallSpec(name, target, inputFirewall.SourceCIDRs, inputFirewall.AllowedPorts)
   105  			if err := gce.raw.AddFirewall(gce.projectID, spec); err != nil {
   106  				return errors.Annotatef(err, "opening port(s) %+v", rules)
   107  			}
   108  			continue
   109  		}
   110  
   111  		// An existing firewall exists with either same same ports or the same source
   112  		// CIDRs as what we have been asked to open. Either way, we just need to update
   113  		// the existing firewall.
   114  
   115  		// Merge the ports.
   116  		allowedPorts := existingFirewall.AllowedPorts.union(inputFirewall.AllowedPorts)
   117  
   118  		// Merge the CIDRs
   119  		cidrs := set.NewStrings(existingFirewall.SourceCIDRs...)
   120  		combinedCIDRs := cidrs.Union(set.NewStrings(inputFirewall.SourceCIDRs...)).SortedValues()
   121  
   122  		// Copy new firewall details into required firewall spec.
   123  		spec := firewallSpec(existingFirewall.Name, target, combinedCIDRs, allowedPorts)
   124  		if err := gce.raw.UpdateFirewall(gce.projectID, existingFirewall.Name, spec); err != nil {
   125  			return errors.Annotatef(err, "opening port(s) %+v", rules)
   126  		}
   127  	}
   128  	return nil
   129  }
   130  
   131  // RandomSuffixNamer tries to find a unique name for the firewall by
   132  // appending a random suffix.
   133  func RandomSuffixNamer(fw *firewall, prefix string, existingNames set.Strings) (string, error) {
   134  	// For backwards compatibility, open rules for "0.0.0.0/0"
   135  	// do not use any suffix in the name.
   136  	if len(fw.SourceCIDRs) == 0 || len(fw.SourceCIDRs) == 1 && fw.SourceCIDRs[0] == "0.0.0.0/0" {
   137  		return prefix, nil
   138  	}
   139  	data := make([]byte, 4)
   140  	for i := 0; i < 10; i++ {
   141  		_, err := rand.Read(data)
   142  		if err != nil {
   143  			return "", errors.Trace(err)
   144  		}
   145  		name := fmt.Sprintf("%s-%x", prefix, data)
   146  		if !existingNames.Contains(name) {
   147  			return name, nil
   148  		}
   149  	}
   150  	return "", errors.New("couldn't pick unique name after 10 attempts")
   151  }
   152  
   153  // ClosePorts sends a request to the GCE API to close the provided port
   154  // ranges on the named firewall. If the firewall does not exist nothing
   155  // happens. If the firewall is left with no ports then it is removed.
   156  // Otherwise it will be left with just the open ports it has that do not
   157  // match the provided port ranges. The call blocks until the ports are
   158  // closed or the request fails.
   159  func (gce Connection) ClosePorts(target string, rules ...network.IngressRule) error {
   160  	// First gather the current ingress rules.
   161  	currentRuleSet, err := gce.firewallRules(target)
   162  	if err != nil {
   163  		return errors.Trace(err)
   164  	}
   165  
   166  	// From the input rules, compose the firewall specs we want to add.
   167  	inputRuleSet := newRuleSetFromRules(rules...)
   168  
   169  	// For each input firewall, find an existing firewall including it
   170  	// and update or remove it.
   171  	for _, inputFirewall := range inputRuleSet {
   172  		existingFirewall, allPortsMatch := currentRuleSet.matchProtocolPorts(inputFirewall.AllowedPorts)
   173  		if allPortsMatch {
   174  			// All the ports match so it may be that just a CIDR needs to be removed.
   175  			cidrs := set.NewStrings(existingFirewall.SourceCIDRs...)
   176  			remainingCidrs := cidrs.Difference(set.NewStrings(inputFirewall.SourceCIDRs...)).SortedValues()
   177  
   178  			// If all CIDRs are also to be removed, we can delete the firewall.
   179  			if len(remainingCidrs) == 0 {
   180  				// Delete a firewall.
   181  				// TODO(ericsnow) Handle case where firewall does not exist.
   182  				if err := gce.raw.RemoveFirewall(gce.projectID, existingFirewall.Name); err != nil {
   183  					return errors.Annotatef(err, "closing port(s) %+v", rules)
   184  				}
   185  				continue
   186  			}
   187  
   188  			// Update the existing firewall with the remaining CIDRs.
   189  			spec := firewallSpec(existingFirewall.Name, target, remainingCidrs, existingFirewall.AllowedPorts)
   190  			if err := gce.raw.UpdateFirewall(gce.projectID, existingFirewall.Name, spec); err != nil {
   191  				return errors.Annotatef(err, "closing port(s) %+v", rules)
   192  			}
   193  			continue
   194  		}
   195  
   196  		existingFirewall, sourceCIDRMatch := currentRuleSet.matchSourceCIDRs(inputFirewall.SourceCIDRs)
   197  		if !sourceCIDRMatch {
   198  			// We already know ports don't match, so if CIDRs don't match either, we either
   199  			// have a partial match or no match.
   200  			// No matches are a no-op. Partial matches might require splitting firewall rules
   201  			// which is not supported at the moment. We'll return an error as it's better to
   202  			// be overly cautious than accidentally leave ports open. The issue shouldn't occur
   203  			// in practice unless people have manually played with the firewall rules.
   204  			return errors.NotSupportedf("closing port(s) %+v over non-matching rules", rules)
   205  		}
   206  
   207  		// Delete the ports to close.
   208  		remainingPorts := existingFirewall.AllowedPorts.remove(inputFirewall.AllowedPorts)
   209  
   210  		// Copy new firewall details into required firewall spec.
   211  		spec := firewallSpec(existingFirewall.Name, target, existingFirewall.SourceCIDRs, remainingPorts)
   212  		if err := gce.raw.UpdateFirewall(gce.projectID, existingFirewall.Name, spec); err != nil {
   213  			return errors.Annotatef(err, "closing port(s) %+v", rules)
   214  		}
   215  	}
   216  	return nil
   217  }
   218  
   219  // Subnetworks returns the subnets available in this region.
   220  func (gce Connection) Subnetworks(region string) ([]*compute.Subnetwork, error) {
   221  	results, err := gce.raw.ListSubnetworks(gce.projectID, region)
   222  	if err != nil {
   223  		return nil, errors.Trace(err)
   224  	}
   225  	return results, nil
   226  }
   227  
   228  // Networks returns the networks available.
   229  func (gce Connection) Networks() ([]*compute.Network, error) {
   230  	results, err := gce.raw.ListNetworks(gce.projectID)
   231  	if err != nil {
   232  		return nil, errors.Trace(err)
   233  	}
   234  	return results, nil
   235  }