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 }