github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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 "time" 11 12 "github.com/juju/errors" 13 "github.com/juju/retry" 14 "github.com/juju/utils/clock" 15 gooseerrors "gopkg.in/goose.v1/errors" 16 "gopkg.in/goose.v1/nova" 17 18 "github.com/juju/juju/environs" 19 "github.com/juju/juju/environs/config" 20 "github.com/juju/juju/instance" 21 "github.com/juju/juju/network" 22 ) 23 24 //factory for obtaining firawaller object. 25 type FirewallerFactory interface { 26 GetFirewaller(env environs.Environ) Firewaller 27 } 28 29 // Firewaller allows custom openstack provider behaviour. 30 // This is used in other providers that embed the openstack provider. 31 type Firewaller interface { 32 // OpenPorts opens the given port ranges for the whole environment. 33 OpenPorts(ports []network.PortRange) error 34 35 // ClosePorts closes the given port ranges for the whole environment. 36 ClosePorts(ports []network.PortRange) error 37 38 // Ports returns the port ranges opened for the whole environment. 39 Ports() ([]network.PortRange, error) 40 41 // Implementations are expected to delete all security groups for the 42 // environment. 43 DeleteAllModelGroups() error 44 45 // Implementations are expected to delete all security groups for the 46 // controller, ie those for all hosted models. 47 DeleteAllControllerGroups(controllerUUID string) error 48 49 // Implementations should return list of security groups, that belong to given instances. 50 GetSecurityGroups(ids ...instance.Id) ([]string, error) 51 52 // Implementations should set up initial security groups, if any. 53 SetUpGroups(controllerUUID, machineId string, apiPort int) ([]nova.SecurityGroup, error) 54 55 // Set of initial networks, that should be added by default to all new instances. 56 InitialNetworks() []nova.ServerNetworks 57 58 // OpenInstancePorts opens the given port ranges for the specified instance. 59 OpenInstancePorts(inst instance.Instance, machineId string, ports []network.PortRange) error 60 61 // CloseInstancePorts closes the given port ranges for the specified instance. 62 CloseInstancePorts(inst instance.Instance, machineId string, ports []network.PortRange) error 63 64 // InstancePorts returns the port ranges opened for the specified instance. 65 InstancePorts(inst instance.Instance, machineId string) ([]network.PortRange, error) 66 } 67 68 type firewallerFactory struct { 69 } 70 71 // GetFirewaller implements FirewallerFactory 72 func (f *firewallerFactory) GetFirewaller(env environs.Environ) Firewaller { 73 return &defaultFirewaller{environ: env.(*Environ)} 74 } 75 76 type defaultFirewaller struct { 77 environ *Environ 78 } 79 80 // InitialNetworks implements Firewaller interface. 81 func (c *defaultFirewaller) InitialNetworks() []nova.ServerNetworks { 82 return []nova.ServerNetworks{} 83 } 84 85 // SetUpGroups creates the security groups for the new machine, and 86 // returns them. 87 // 88 // Instances are tagged with a group so they can be distinguished from 89 // other instances that might be running on the same OpenStack account. 90 // In addition, a specific machine security group is created for each 91 // machine, so that its firewall rules can be configured per machine. 92 // 93 // Note: ideally we'd have a better way to determine group membership so that 2 94 // people that happen to share an openstack account and name their environment 95 // "openstack" don't end up destroying each other's machines. 96 func (c *defaultFirewaller) SetUpGroups(controllerUUID, machineId string, apiPort int) ([]nova.SecurityGroup, error) { 97 jujuGroup, err := c.setUpGlobalGroup(c.jujuGroupName(controllerUUID), apiPort) 98 if err != nil { 99 return nil, err 100 } 101 var machineGroup nova.SecurityGroup 102 switch c.environ.Config().FirewallMode() { 103 case config.FwInstance: 104 machineGroup, err = c.ensureGroup(c.machineGroupName(controllerUUID, machineId), nil) 105 case config.FwGlobal: 106 machineGroup, err = c.ensureGroup(c.globalGroupName(controllerUUID), nil) 107 } 108 if err != nil { 109 return nil, err 110 } 111 groups := []nova.SecurityGroup{jujuGroup, machineGroup} 112 if c.environ.ecfg().useDefaultSecurityGroup() { 113 defaultGroup, err := c.environ.nova().SecurityGroupByName("default") 114 if err != nil { 115 return nil, fmt.Errorf("loading default security group: %v", err) 116 } 117 groups = append(groups, *defaultGroup) 118 } 119 return groups, nil 120 } 121 122 func (c *defaultFirewaller) setUpGlobalGroup(groupName string, apiPort int) (nova.SecurityGroup, error) { 123 return c.ensureGroup(groupName, 124 []nova.RuleInfo{ 125 { 126 IPProtocol: "tcp", 127 FromPort: 22, 128 ToPort: 22, 129 Cidr: "0.0.0.0/0", 130 }, 131 { 132 IPProtocol: "tcp", 133 FromPort: apiPort, 134 ToPort: apiPort, 135 Cidr: "0.0.0.0/0", 136 }, 137 { 138 IPProtocol: "tcp", 139 FromPort: 1, 140 ToPort: 65535, 141 }, 142 { 143 IPProtocol: "udp", 144 FromPort: 1, 145 ToPort: 65535, 146 }, 147 { 148 IPProtocol: "icmp", 149 FromPort: -1, 150 ToPort: -1, 151 }, 152 }) 153 } 154 155 // zeroGroup holds the zero security group. 156 var zeroGroup nova.SecurityGroup 157 158 // ensureGroup returns the security group with name and perms. 159 // If a group with name does not exist, one will be created. 160 // If it exists, its permissions are set to perms. 161 func (c *defaultFirewaller) ensureGroup(name string, rules []nova.RuleInfo) (nova.SecurityGroup, error) { 162 novaClient := c.environ.nova() 163 // First attempt to look up an existing group by name. 164 group, err := novaClient.SecurityGroupByName(name) 165 if err == nil { 166 // Group exists, so assume it is correctly set up and return it. 167 // TODO(jam): 2013-09-18 http://pad.lv/121795 168 // We really should verify the group is set up correctly, 169 // because deleting and re-creating environments can get us bad 170 // groups (especially if they were set up under Python) 171 return *group, nil 172 } 173 // Doesn't exist, so try and create it. 174 group, err = novaClient.CreateSecurityGroup(name, "juju group") 175 if err != nil { 176 if !gooseerrors.IsDuplicateValue(err) { 177 return zeroGroup, err 178 } else { 179 // We just tried to create a duplicate group, so load the existing group. 180 group, err = novaClient.SecurityGroupByName(name) 181 if err != nil { 182 return zeroGroup, err 183 } 184 return *group, nil 185 } 186 } 187 // The new group is created so now add the rules. 188 group.Rules = make([]nova.SecurityGroupRule, len(rules)) 189 for i, rule := range rules { 190 rule.ParentGroupId = group.Id 191 if rule.Cidr == "" { 192 // http://pad.lv/1226996 Rules that don't have a CIDR 193 // are meant to apply only to this group. If you don't 194 // supply CIDR or GroupId then openstack assumes you 195 // mean CIDR=0.0.0.0/0 196 rule.GroupId = &group.Id 197 } 198 groupRule, err := novaClient.CreateSecurityGroupRule(rule) 199 if err != nil && !gooseerrors.IsDuplicateValue(err) { 200 return zeroGroup, err 201 } 202 group.Rules[i] = *groupRule 203 } 204 return *group, nil 205 } 206 207 // GetSecurityGroups implements Firewaller interface. 208 func (c *defaultFirewaller) GetSecurityGroups(ids ...instance.Id) ([]string, error) { 209 var securityGroupNames []string 210 if c.environ.Config().FirewallMode() == config.FwInstance { 211 instances, err := c.environ.Instances(ids) 212 if err != nil { 213 return nil, err 214 } 215 novaClient := c.environ.nova() 216 securityGroupNames = make([]string, 0, len(ids)) 217 for _, inst := range instances { 218 if inst == nil { 219 continue 220 } 221 openstackName := inst.(*openstackInstance).getServerDetail().Name 222 lastDashPos := strings.LastIndex(openstackName, "-") 223 if lastDashPos == -1 { 224 return nil, fmt.Errorf("cannot identify machine ID in openstack server name %q", openstackName) 225 } 226 serverId := openstackName[lastDashPos+1:] 227 groups, err := novaClient.GetServerSecurityGroups(string(inst.Id())) 228 if err != nil { 229 return nil, err 230 } 231 for _, group := range groups { 232 // We only include the group specifically tied to the instance, not 233 // any group global to the model itself. 234 if strings.HasSuffix(group.Name, fmt.Sprintf("%s-%s", c.environ.Config().UUID(), serverId)) { 235 securityGroupNames = append(securityGroupNames, group.Name) 236 } 237 } 238 } 239 } 240 return securityGroupNames, nil 241 } 242 243 func (c *defaultFirewaller) deleteSecurityGroups(prefix string) error { 244 novaClient := c.environ.nova() 245 securityGroups, err := novaClient.ListSecurityGroups() 246 if err != nil { 247 return errors.Annotate(err, "cannot list security groups") 248 } 249 250 re, err := regexp.Compile("^" + prefix) 251 if err != nil { 252 return errors.Trace(err) 253 } 254 for _, group := range securityGroups { 255 if re.MatchString(group.Name) { 256 deleteSecurityGroup(novaClient, group.Name, group.Id) 257 } 258 } 259 return nil 260 } 261 262 // DeleteAllControllerGroups implements Firewaller interface. 263 func (c *defaultFirewaller) DeleteAllControllerGroups(controllerUUID string) error { 264 return c.deleteSecurityGroups(c.jujuControllerGroupPrefix(controllerUUID)) 265 } 266 267 // DeleteAllModelGroups implements Firewaller interface. 268 func (c *defaultFirewaller) DeleteAllModelGroups() error { 269 return c.deleteSecurityGroups(c.jujuGroupRegexp()) 270 } 271 272 // deleteSecurityGroup attempts to delete the security group. Should it fail, 273 // the deletion is retried due to timing issues in openstack. A security group 274 // cannot be deleted while it is in use. Theoretically we terminate all the 275 // instances before we attempt to delete the associated security groups, but 276 // in practice nova hasn't always finished with the instance before it 277 // returns, so there is a race condition where we think the instance is 278 // terminated and hence attempt to delete the security groups but nova still 279 // has it around internally. To attempt to catch this timing issue, deletion 280 // of the groups is tried multiple times. 281 func deleteSecurityGroup(novaclient *nova.Client, name, id string) { 282 logger.Debugf("deleting security group %q", name) 283 err := retry.Call(retry.CallArgs{ 284 Func: func() error { 285 return novaclient.DeleteSecurityGroup(id) 286 }, 287 NotifyFunc: func(err error, attempt int) { 288 if attempt%4 == 0 { 289 message := fmt.Sprintf("waiting to delete security group %q", name) 290 if attempt != 4 { 291 message = "still " + message 292 } 293 logger.Debugf(message) 294 } 295 }, 296 Attempts: 30, 297 Delay: time.Second, 298 // TODO(dimitern): This should be fixed to take a clock.Clock arg, not 299 // hard-coded WallClock, like in provider/ec2/securitygroups_test.go! 300 // See PR juju:#5197, especially the code around autoAdvancingClock. 301 // LP Bug: http://pad.lv/1580626. 302 Clock: clock.WallClock, 303 }) 304 if err != nil { 305 logger.Warningf("cannot delete security group %q. Used by another model?", name) 306 } 307 } 308 309 // OpenPorts implements Firewaller interface. 310 func (c *defaultFirewaller) OpenPorts(ports []network.PortRange) error { 311 if c.environ.Config().FirewallMode() != config.FwGlobal { 312 return fmt.Errorf("invalid firewall mode %q for opening ports on model", 313 c.environ.Config().FirewallMode()) 314 } 315 if err := c.openPortsInGroup(c.globalGroupRegexp(), ports); err != nil { 316 return err 317 } 318 logger.Infof("opened ports in global group: %v", ports) 319 return nil 320 } 321 322 // ClosePorts implements Firewaller interface. 323 func (c *defaultFirewaller) ClosePorts(ports []network.PortRange) error { 324 if c.environ.Config().FirewallMode() != config.FwGlobal { 325 return fmt.Errorf("invalid firewall mode %q for closing ports on model", 326 c.environ.Config().FirewallMode()) 327 } 328 if err := c.closePortsInGroup(c.globalGroupRegexp(), ports); err != nil { 329 return err 330 } 331 logger.Infof("closed ports in global group: %v", ports) 332 return nil 333 } 334 335 // Ports implements Firewaller interface. 336 func (c *defaultFirewaller) Ports() ([]network.PortRange, error) { 337 if c.environ.Config().FirewallMode() != config.FwGlobal { 338 return nil, fmt.Errorf("invalid firewall mode %q for retrieving ports from model", 339 c.environ.Config().FirewallMode()) 340 } 341 return c.portsInGroup(c.globalGroupRegexp()) 342 } 343 344 // OpenInstancePorts implements Firewaller interface. 345 func (c *defaultFirewaller) OpenInstancePorts(inst instance.Instance, machineId string, ports []network.PortRange) error { 346 if c.environ.Config().FirewallMode() != config.FwInstance { 347 return fmt.Errorf("invalid firewall mode %q for opening ports on instance", 348 c.environ.Config().FirewallMode()) 349 } 350 nameRegexp := c.machineGroupRegexp(machineId) 351 if err := c.openPortsInGroup(nameRegexp, ports); err != nil { 352 return err 353 } 354 logger.Infof("opened ports in security group %s-%s: %v", c.environ.Config().UUID(), machineId, ports) 355 return nil 356 } 357 358 // CloseInstancePorts implements Firewaller interface. 359 func (c *defaultFirewaller) CloseInstancePorts(inst instance.Instance, machineId string, ports []network.PortRange) error { 360 if c.environ.Config().FirewallMode() != config.FwInstance { 361 return fmt.Errorf("invalid firewall mode %q for closing ports on instance", 362 c.environ.Config().FirewallMode()) 363 } 364 nameRegexp := c.machineGroupRegexp(machineId) 365 if err := c.closePortsInGroup(nameRegexp, ports); err != nil { 366 return err 367 } 368 logger.Infof("closed ports in security group %s-%s: %v", c.environ.Config().UUID(), machineId, ports) 369 return nil 370 } 371 372 // InstancePorts implements Firewaller interface. 373 func (c *defaultFirewaller) InstancePorts(inst instance.Instance, machineId string) ([]network.PortRange, error) { 374 if c.environ.Config().FirewallMode() != config.FwInstance { 375 return nil, fmt.Errorf("invalid firewall mode %q for retrieving ports from instance", 376 c.environ.Config().FirewallMode()) 377 } 378 nameRegexp := c.machineGroupRegexp(machineId) 379 portRanges, err := c.portsInGroup(nameRegexp) 380 if err != nil { 381 return nil, err 382 } 383 return portRanges, nil 384 } 385 386 func (c *defaultFirewaller) matchingGroup(nameRegExp string) (nova.SecurityGroup, error) { 387 re, err := regexp.Compile(nameRegExp) 388 if err != nil { 389 return nova.SecurityGroup{}, err 390 } 391 novaclient := c.environ.nova() 392 allGroups, err := novaclient.ListSecurityGroups() 393 if err != nil { 394 return nova.SecurityGroup{}, err 395 } 396 var matchingGroups []nova.SecurityGroup 397 for _, group := range allGroups { 398 if re.MatchString(group.Name) { 399 matchingGroups = append(matchingGroups, group) 400 } 401 } 402 if len(matchingGroups) != 1 { 403 return nova.SecurityGroup{}, errors.NotFoundf("security groups matching %q", nameRegExp) 404 } 405 return matchingGroups[0], nil 406 } 407 408 func (c *defaultFirewaller) openPortsInGroup(nameRegExp string, portRanges []network.PortRange) error { 409 group, err := c.matchingGroup(nameRegExp) 410 if err != nil { 411 return err 412 } 413 novaclient := c.environ.nova() 414 rules := portsToRuleInfo(group.Id, portRanges) 415 for _, rule := range rules { 416 _, err := novaclient.CreateSecurityGroupRule(rule) 417 if err != nil { 418 // TODO: if err is not rule already exists, raise? 419 logger.Debugf("error creating security group rule: %v", err.Error()) 420 } 421 } 422 return nil 423 } 424 425 // ruleMatchesPortRange checks if supplied nova security group rule matches the port range 426 func ruleMatchesPortRange(rule nova.SecurityGroupRule, portRange network.PortRange) bool { 427 if rule.IPProtocol == nil || rule.FromPort == nil || rule.ToPort == nil { 428 return false 429 } 430 return *rule.IPProtocol == portRange.Protocol && 431 *rule.FromPort == portRange.FromPort && 432 *rule.ToPort == portRange.ToPort 433 } 434 435 func (c *defaultFirewaller) closePortsInGroup(nameRegExp string, portRanges []network.PortRange) error { 436 if len(portRanges) == 0 { 437 return nil 438 } 439 group, err := c.matchingGroup(nameRegExp) 440 if err != nil { 441 return err 442 } 443 novaclient := c.environ.nova() 444 // TODO: Hey look ma, it's quadratic 445 for _, portRange := range portRanges { 446 for _, p := range group.Rules { 447 if !ruleMatchesPortRange(p, portRange) { 448 continue 449 } 450 err := novaclient.DeleteSecurityGroupRule(p.Id) 451 if err != nil { 452 return err 453 } 454 break 455 } 456 } 457 return nil 458 } 459 460 func (c *defaultFirewaller) portsInGroup(nameRegexp string) (portRanges []network.PortRange, err error) { 461 group, err := c.matchingGroup(nameRegexp) 462 if err != nil { 463 return nil, err 464 } 465 for _, p := range group.Rules { 466 portRanges = append(portRanges, network.PortRange{ 467 Protocol: *p.IPProtocol, 468 FromPort: *p.FromPort, 469 ToPort: *p.ToPort, 470 }) 471 } 472 network.SortPortRanges(portRanges) 473 return portRanges, nil 474 } 475 476 func (c *defaultFirewaller) globalGroupName(controllerUUID string) string { 477 return fmt.Sprintf("%s-global", c.jujuGroupName(controllerUUID)) 478 } 479 480 func (c *defaultFirewaller) machineGroupName(controllerUUID, machineId string) string { 481 return fmt.Sprintf("%s-%s", c.jujuGroupName(controllerUUID), machineId) 482 } 483 484 func (c *defaultFirewaller) jujuGroupName(controllerUUID string) string { 485 cfg := c.environ.Config() 486 return fmt.Sprintf("juju-%v-%v", controllerUUID, cfg.UUID()) 487 } 488 489 func (c *defaultFirewaller) jujuControllerGroupPrefix(controllerUUID string) string { 490 return fmt.Sprintf("juju-%v-", controllerUUID) 491 } 492 493 func (c *defaultFirewaller) jujuGroupRegexp() string { 494 cfg := c.environ.Config() 495 return fmt.Sprintf("juju-.*-%v", cfg.UUID()) 496 } 497 498 func (c *defaultFirewaller) globalGroupRegexp() string { 499 return fmt.Sprintf("%s-global", c.jujuGroupRegexp()) 500 } 501 502 func (c *defaultFirewaller) machineGroupRegexp(machineId string) string { 503 return fmt.Sprintf("%s-%s", c.jujuGroupRegexp(), machineId) 504 }