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 }