github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/oracle/network/firewall.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package network 5 6 import ( 7 "fmt" 8 "reflect" 9 "sort" 10 "strconv" 11 "strings" 12 "time" 13 14 "github.com/juju/clock" 15 "github.com/juju/errors" 16 "github.com/juju/go-oracle-cloud/api" 17 "github.com/juju/go-oracle-cloud/common" 18 "github.com/juju/go-oracle-cloud/response" 19 "github.com/juju/utils" 20 21 "github.com/juju/juju/controller" 22 corenetwork "github.com/juju/juju/core/network" 23 "github.com/juju/juju/environs" 24 "github.com/juju/juju/environs/config" 25 "github.com/juju/juju/environs/context" 26 "github.com/juju/juju/network" 27 commonProvider "github.com/juju/juju/provider/oracle/common" 28 ) 29 30 // Firewaller exposes methods for managing network ports. 31 type Firewaller interface { 32 environs.Firewaller 33 34 // Return all machine ingress rules for a given machine id 35 MachineIngressRules(ctx context.ProviderCallContext, id string) ([]network.IngressRule, error) 36 37 // OpenPortsOnInstance will open ports corresponding to the supplied rules 38 // on the given instance 39 OpenPortsOnInstance(ctx context.ProviderCallContext, machineId string, rules []network.IngressRule) error 40 41 // ClosePortsOnInstnace will close ports corresponding to the supplied rules 42 // for a given instance. 43 ClosePortsOnInstance(ctx context.ProviderCallContext, machineId string, rules []network.IngressRule) error 44 45 // CreateMachineSecLists creates a security list for the given instance. 46 // It's worth noting that this function also ensures that the default environment 47 // sec list is also present, and has the appropriate default rules. 48 // The port parameter is the API port for the state machine, for which we need 49 // to create rules. 50 CreateMachineSecLists(id string, port int) ([]string, error) 51 52 // DeleteMachineSecList will delete the security list on the given machine 53 // id 54 DeleteMachineSecList(id string) error 55 56 // CreateDefaultACLAndRules will create a default ACL and associated rules, for 57 // a given machine. This ACL applies to user defined IP networks, which are attached 58 // to the instance. 59 CreateDefaultACLAndRules(id string) (response.Acl, error) 60 61 // RemoveACLAndRules will remove the ACL and any associated rules. 62 RemoveACLAndRules(id string) error 63 } 64 65 var _ Firewaller = (*Firewall)(nil) 66 67 // FirewallerAPI defines methods necessary for interacting with the firewall 68 // feature of Oracle compute cloud 69 type FirewallerAPI interface { 70 commonProvider.Composer 71 commonProvider.RulesAPI 72 commonProvider.AclAPI 73 commonProvider.SecIpAPI 74 commonProvider.IpAddressPrefixSetAPI 75 commonProvider.SecListAPI 76 commonProvider.ApplicationsAPI 77 commonProvider.SecRulesAPI 78 commonProvider.AssociationAPI 79 } 80 81 // Firewall implements environ.Firewaller 82 type Firewall struct { 83 // environ is the current oracle cloud environment 84 // this will use to access the underlying config 85 environ environs.ConfigGetter 86 // client is used to make operations on the oracle provider 87 client FirewallerAPI 88 clock clock.Clock 89 } 90 91 // NewFirewall returns a new Firewall 92 func NewFirewall(cfg environs.ConfigGetter, client FirewallerAPI, c clock.Clock) *Firewall { 93 return &Firewall{ 94 environ: cfg, 95 client: client, 96 clock: c, 97 } 98 } 99 100 // OpenPorts is specified on the environ.Firewaller interface. 101 func (f Firewall) OpenPorts(ctx context.ProviderCallContext, rules []network.IngressRule) error { 102 mode := f.environ.Config().FirewallMode() 103 if mode != config.FwGlobal { 104 return fmt.Errorf( 105 "invalid firewall mode %q for opening ports on model", 106 mode, 107 ) 108 } 109 110 globalGroupName := f.globalGroupName() 111 seclist, err := f.ensureSecList(f.client.ComposeName(globalGroupName)) 112 if err != nil { 113 return errors.Trace(err) 114 } 115 err = f.ensureSecRules(seclist, rules) 116 if err != nil { 117 return errors.Trace(err) 118 } 119 return nil 120 } 121 122 // ClosePorts is specified on the environ.Firewaller interface. 123 func (f Firewall) ClosePorts(ctx context.ProviderCallContext, rules []network.IngressRule) error { 124 groupName := f.globalGroupName() 125 return f.closePortsOnList(ctx, f.client.ComposeName(groupName), rules) 126 } 127 128 // IngressRules is specified on the environ.Firewaller interface. 129 func (f Firewall) IngressRules(ctx context.ProviderCallContext) ([]network.IngressRule, error) { 130 return f.GlobalIngressRules(ctx) 131 } 132 133 // MachineIngressRules returns all ingress rules from the machine specific sec list 134 func (f Firewall) MachineIngressRules(ctx context.ProviderCallContext, machineId string) ([]network.IngressRule, error) { 135 seclist := f.machineGroupName(machineId) 136 return f.getIngressRules(ctx, f.client.ComposeName(seclist)) 137 } 138 139 // OpenPortsOnInstance will open ports corresponding to the supplied rules 140 // on the given instance 141 func (f Firewall) OpenPortsOnInstance(ctx context.ProviderCallContext, machineId string, rules []network.IngressRule) error { 142 machineGroup := f.machineGroupName(machineId) 143 seclist, err := f.ensureSecList(f.client.ComposeName(machineGroup)) 144 if err != nil { 145 return errors.Trace(err) 146 } 147 err = f.ensureSecRules(seclist, rules) 148 if err != nil { 149 return errors.Trace(err) 150 } 151 return nil 152 } 153 154 // ClosePortsOnInstnace will close ports corresponding to the supplied rules 155 // for a given instance. 156 func (f Firewall) ClosePortsOnInstance(ctx context.ProviderCallContext, machineId string, rules []network.IngressRule) error { 157 // fetch the group name based on the machine id provided 158 groupName := f.machineGroupName(machineId) 159 return f.closePortsOnList(ctx, f.client.ComposeName(groupName), rules) 160 } 161 162 // CreateMachineSecLists creates a security list for the given instance. 163 // It's worth noting that this function also ensures that the default environment 164 // sec list is also present, and has the appropriate default rules. 165 // The port parameter is the API port for the state machine, for which we need 166 // to create rules. 167 func (f Firewall) CreateMachineSecLists(machineId string, apiPort int) ([]string, error) { 168 defaultSecList, err := f.createDefaultGroupAndRules(apiPort) 169 if err != nil { 170 return nil, errors.Trace(err) 171 } 172 name := f.machineGroupName(machineId) 173 resourceName := f.client.ComposeName(name) 174 secList, err := f.ensureSecList(resourceName) 175 if err != nil { 176 return nil, errors.Trace(err) 177 } 178 return []string{ 179 defaultSecList.Name, 180 secList.Name, 181 }, nil 182 } 183 184 // DeleteMachineSecList will delete the security list on the given machine 185 func (f Firewall) DeleteMachineSecList(machineId string) error { 186 listName := f.machineGroupName(machineId) 187 globalListName := f.globalGroupName() 188 err := f.maybeDeleteList(f.client.ComposeName(listName)) 189 if err != nil { 190 return errors.Trace(err) 191 } 192 // check if we can delete the global list as well 193 err = f.maybeDeleteList(f.client.ComposeName(globalListName)) 194 if err != nil { 195 return errors.Trace(err) 196 } 197 return nil 198 } 199 200 // CreateDefaultACLAndRules creates default ACL and rules for IP networks attached to 201 // units. 202 // NOTE (gsamfira): For now we apply an allow all on these ACLs. Traffic will be cloud-only 203 // between instances connected to the same ip network exchange (the equivalent of a space) 204 // There will be no public IP associated to interfaces connected to IP networks, so only 205 // instances connected to the same network, or a network managed by the same space will 206 // be able to connect. This will ensure that peers and units entering a relationship can connect 207 // to services deployed by a particular unit, without having to expose the application. 208 func (f Firewall) CreateDefaultACLAndRules(machineId string) (response.Acl, error) { 209 var details response.Acl 210 var err error 211 description := fmt.Sprintf("ACL for machine %s", machineId) 212 groupName := f.machineGroupName(machineId) 213 resourceName := f.client.ComposeName(groupName) 214 if err != nil { 215 return response.Acl{}, err 216 } 217 rules := []api.SecurityRuleParams{ 218 { 219 Name: fmt.Sprintf("%s-allow-ingress", resourceName), 220 Description: "Allow all ingress", 221 FlowDirection: common.Ingress, 222 EnabledFlag: true, 223 DstIpAddressPrefixSets: []string{}, 224 SecProtocols: []string{}, 225 SrcIpAddressPrefixSets: []string{}, 226 }, 227 { 228 Name: fmt.Sprintf("%s-allow-egress", resourceName), 229 Description: "Allow all egress", 230 FlowDirection: common.Egress, 231 EnabledFlag: true, 232 DstIpAddressPrefixSets: []string{}, 233 SecProtocols: []string{}, 234 SrcIpAddressPrefixSets: []string{}, 235 }, 236 } 237 details, err = f.client.AclDetails(resourceName) 238 if err != nil { 239 if api.IsNotFound(err) { 240 details, err = f.client.CreateAcl(resourceName, description, true, nil) 241 if err != nil { 242 return response.Acl{}, errors.Trace(err) 243 } 244 } else { 245 return response.Acl{}, errors.Trace(err) 246 } 247 } 248 aclRules, err := f.getAllSecurityRules(details.Name) 249 if err != nil { 250 return response.Acl{}, errors.Trace(err) 251 } 252 253 var toAdd []api.SecurityRuleParams 254 255 for _, val := range rules { 256 found := false 257 val.Acl = details.Name 258 newRuleAsStub := f.convertSecurityRuleParamsToStub(val) 259 for _, existing := range aclRules { 260 existingRulesAsStub := f.convertSecurityRuleToStub(existing) 261 if reflect.DeepEqual(existingRulesAsStub, newRuleAsStub) { 262 found = true 263 break 264 } 265 } 266 if !found { 267 toAdd = append(toAdd, val) 268 } 269 } 270 for _, val := range toAdd { 271 _, err := f.client.CreateSecurityRule(val) 272 if err != nil { 273 return response.Acl{}, errors.Trace(err) 274 } 275 } 276 return details, nil 277 } 278 279 // RemoveACLAndRules will remove the ACL and any associated rules. 280 func (f Firewall) RemoveACLAndRules(machineId string) error { 281 groupName := f.machineGroupName(machineId) 282 resourceName := f.client.ComposeName(groupName) 283 secRules, err := f.getAllSecurityRules(resourceName) 284 if err != nil { 285 return err 286 } 287 for _, val := range secRules { 288 err := f.client.DeleteSecurityRule(val.Name) 289 if err != nil { 290 if !api.IsNotFound(err) { 291 return err 292 } 293 } 294 } 295 err = f.client.DeleteAcl(resourceName) 296 if err != nil { 297 if !api.IsNotFound(err) { 298 return err 299 } 300 } 301 return nil 302 } 303 304 // GlobalIngressRules returns the ingress rules applied to the whole environment. 305 func (f Firewall) GlobalIngressRules(ctx context.ProviderCallContext) ([]network.IngressRule, error) { 306 seclist := f.globalGroupName() 307 return f.getIngressRules(ctx, f.client.ComposeName(seclist)) 308 } 309 310 // getDefaultIngressRules will create the default ingressRules given an api port 311 func (f Firewall) getDefaultIngressRules(apiPort int) []network.IngressRule { 312 return []network.IngressRule{ 313 { 314 PortRange: corenetwork.PortRange{ 315 FromPort: 22, 316 ToPort: 22, 317 Protocol: "tcp", 318 }, 319 SourceCIDRs: []string{ 320 "0.0.0.0/0", 321 }, 322 }, 323 { 324 PortRange: corenetwork.PortRange{ 325 FromPort: 3389, 326 ToPort: 3389, 327 Protocol: "tcp", 328 }, 329 SourceCIDRs: []string{ 330 "0.0.0.0/0", 331 }, 332 }, 333 { 334 PortRange: corenetwork.PortRange{ 335 FromPort: apiPort, 336 ToPort: apiPort, 337 Protocol: "tcp", 338 }, 339 SourceCIDRs: []string{ 340 "0.0.0.0/0", 341 }, 342 }, 343 { 344 PortRange: corenetwork.PortRange{ 345 FromPort: controller.DefaultStatePort, 346 ToPort: controller.DefaultStatePort, 347 Protocol: "tcp", 348 }, 349 SourceCIDRs: []string{ 350 "0.0.0.0/0", 351 }, 352 }, 353 } 354 } 355 356 type stubSecurityRule struct { 357 Acl string 358 FlowDirection common.FlowDirection 359 DstIpAddressPrefixSets []string 360 SecProtocols []string 361 SrcIpAddressPrefixSets []string 362 } 363 364 func (f Firewall) createDefaultGroupAndRules(apiPort int) (response.SecList, error) { 365 rules := f.getDefaultIngressRules(apiPort) 366 var details response.SecList 367 var err error 368 globalGroupName := f.globalGroupName() 369 resourceName := f.client.ComposeName(globalGroupName) 370 details, err = f.client.SecListDetails(resourceName) 371 if err != nil { 372 if api.IsNotFound(err) { 373 details, err = f.ensureSecList(resourceName) 374 if err != nil { 375 return response.SecList{}, errors.Trace(err) 376 } 377 } else { 378 return response.SecList{}, errors.Trace(err) 379 } 380 } 381 382 err = f.ensureSecRules(details, rules) 383 if err != nil { 384 return response.SecList{}, errors.Trace(err) 385 } 386 return details, nil 387 } 388 389 // closePortsOnList on list will close all ports corresponding to the supplied ingress rules 390 // on a particular list 391 func (f Firewall) closePortsOnList(ctx context.ProviderCallContext, list string, rules []network.IngressRule) error { 392 // get all security rules based on the dst_list=list 393 secrules, err := f.getSecRules(list) 394 if err != nil { 395 return errors.Trace(err) 396 } 397 // converts all security rules into a map of ingress rules 398 mapping, err := f.secRuleToIngresRule(secrules...) 399 if err != nil { 400 return errors.Trace(err) 401 } 402 403 //TODO (gsamfira): optimize this 404 for name, rule := range mapping { 405 sort.Strings(rule.SourceCIDRs) 406 for _, ingressRule := range rules { 407 sort.Strings(ingressRule.SourceCIDRs) 408 if reflect.DeepEqual(rule, ingressRule) { 409 err := f.client.DeleteSecRule(name) 410 if err != nil { 411 return errors.Trace(err) 412 } 413 } 414 } 415 } 416 return nil 417 } 418 419 // deleteAllSecRulesOnList will delete all security rules from a give 420 // security list 421 func (f Firewall) deleteAllSecRulesOnList(list string) error { 422 // get all security rules associated with this list 423 secrules, err := f.getSecRules(list) 424 if err != nil { 425 return errors.Trace(err) 426 } 427 // delete everything 428 for _, rule := range secrules { 429 err := f.client.DeleteSecRule(rule.Name) 430 if err != nil { 431 if api.IsNotFound(err) { 432 continue 433 } 434 return errors.Trace(err) 435 } 436 } 437 return nil 438 } 439 440 // maybeDeleteList tries to delete a security list. Lists that are still in use 441 // may not be deleted. When deleting an environment, we want to also cleanup the 442 // environment level sec list. This function attempts to delete a sec list. If the 443 // sec list still has some associations to any instance, we simply return and assume 444 // the last VM to get killed as part of the tear-down, will also remove the global 445 // list as well 446 func (f *Firewall) maybeDeleteList(list string) error { 447 filter := []api.Filter{ 448 { 449 Arg: "seclist", 450 Value: list, 451 }, 452 } 453 iter := 0 454 found := true 455 var assoc response.AllSecAssociations 456 for { 457 if iter >= 10 { 458 break 459 } 460 assoc, err := f.client.AllSecAssociations(filter) 461 if err != nil { 462 return errors.Trace(err) 463 } 464 if len(assoc.Result) > 0 { 465 <-f.clock.After(1 * time.Second) 466 iter++ 467 continue 468 } 469 found = false 470 break 471 } 472 if found { 473 logger.Warningf( 474 "seclist %s is still has some associations to instance(s): %v. Will not delete", 475 list, assoc.Result, 476 ) 477 return nil 478 } 479 err := f.deleteAllSecRulesOnList(list) 480 if err != nil { 481 return errors.Trace(err) 482 } 483 logger.Tracef("deleting seclist %v", list) 484 err = f.client.DeleteSecList(list) 485 if err != nil { 486 if api.IsNotFound(err) { 487 return nil 488 } 489 return errors.Trace(err) 490 } 491 return nil 492 } 493 494 // getIngressRules returns all rules associated with the given sec list 495 // values are converted and returned as []network.IngressRule 496 func (f Firewall) getIngressRules(ctx context.ProviderCallContext, seclist string) ([]network.IngressRule, error) { 497 // get all security rules associated with the seclist 498 secrules, err := f.getSecRules(seclist) 499 if err != nil { 500 return nil, errors.Trace(err) 501 } 502 // convert all security rules into a map of ingress rules 503 ingressRules, err := f.convertFromSecRules(secrules...) 504 if err != nil { 505 return nil, errors.Trace(err) 506 } 507 if rules, ok := ingressRules[seclist]; ok { 508 return rules, nil 509 } 510 return []network.IngressRule{}, nil 511 } 512 513 // getAllApplications returns all security applications known to the 514 // oracle compute cloud. These are used as part of security rules 515 func (f Firewall) getAllApplications() ([]response.SecApplication, error) { 516 // get all user defined sec applications 517 applications, err := f.client.AllSecApplications(nil) 518 if err != nil { 519 return nil, errors.Trace(err) 520 } 521 // get also default ones defined in the provider 522 defaultApps, err := f.client.DefaultSecApplications(nil) 523 if err != nil { 524 return nil, errors.Trace(err) 525 } 526 allApps := []response.SecApplication{} 527 for _, val := range applications.Result { 528 if val.PortProtocolPair() == "" { 529 // (gsamfira):this should not really happen, 530 // but I get paranoid when I run out of coffee 531 continue 532 } 533 allApps = append(allApps, val) 534 } 535 for _, val := range defaultApps.Result { 536 if val.PortProtocolPair() == "" { 537 continue 538 } 539 allApps = append(allApps, val) 540 } 541 return allApps, nil 542 } 543 544 // getAllApplicationsAsMap returns all sec applications as a map 545 func (f Firewall) getAllApplicationsAsMap() (map[string]response.SecApplication, error) { 546 // get all defined protocols 547 // from the current identity and default ones 548 apps, err := f.getAllApplications() 549 if err != nil { 550 return nil, errors.Trace(err) 551 } 552 // copy all of them into this map 553 allApps := map[string]response.SecApplication{} 554 for _, val := range apps { 555 if val.String() == "" { 556 continue 557 } 558 if _, ok := allApps[val.String()]; !ok { 559 allApps[val.String()] = val 560 } 561 } 562 return allApps, nil 563 } 564 565 func (f Firewall) ensureApplication(portRange corenetwork.PortRange, cache *[]response.SecApplication) (string, error) { 566 // check if the security application is already created 567 for _, val := range *cache { 568 if val.PortProtocolPair() == portRange.String() { 569 return val.Name, nil 570 } 571 } 572 // We need to create a new application 573 // There is always the chance of a race condition 574 // when it comes to creating new resources. 575 // ie: someone may have already created a matching 576 // application between the time we fetched all of them 577 // and the moment we actually got to create one 578 // Worst thing that can happen is that we have a few duplicate 579 // rules, that we cleanup anyway when we destroy the environment 580 uuid, err := utils.NewUUID() 581 if err != nil { 582 return "", errors.Trace(err) 583 } 584 // create new name for sec application 585 secAppName := f.newResourceName(uuid.String()) 586 var dport string 587 588 if portRange.FromPort == portRange.ToPort { 589 dport = strconv.Itoa(portRange.FromPort) 590 } else { 591 dport = fmt.Sprintf("%s-%s", 592 strconv.Itoa(portRange.FromPort), strconv.Itoa(portRange.ToPort)) 593 } 594 // compose the provider resource name for the new application 595 name := f.client.ComposeName(secAppName) 596 secAppParams := api.SecApplicationParams{ 597 Description: "Juju created security application", 598 Dport: dport, 599 Protocol: common.Protocol(portRange.Protocol), 600 Name: name, 601 } 602 application, err := f.client.CreateSecApplication(secAppParams) 603 if err != nil { 604 return "", errors.Trace(err) 605 } 606 *cache = append(*cache, application) 607 return application.Name, nil 608 } 609 610 // convertToSecRules converts network.IngressRules to api.SecRuleParams 611 func (f Firewall) convertToSecRules(seclist response.SecList, rules []network.IngressRule) ([]api.SecRuleParams, error) { 612 applications, err := f.getAllApplications() 613 if err != nil { 614 return nil, errors.Trace(err) 615 } 616 iplists, err := f.getAllIPLists() 617 if err != nil { 618 return nil, errors.Trace(err) 619 } 620 621 ret := make([]api.SecRuleParams, 0, len(rules)) 622 // for every rule we need to ensure that the there is a relationship 623 // between security applications and security IP lists 624 // and from every one of them create a slice of security rule parameters 625 for _, val := range rules { 626 app, err := f.ensureApplication(val.PortRange, &applications) 627 if err != nil { 628 return nil, errors.Trace(err) 629 } 630 ipList, err := f.ensureSecIpList(val.SourceCIDRs, &iplists) 631 if err != nil { 632 return nil, errors.Trace(err) 633 } 634 uuid, err := utils.NewUUID() 635 if err != nil { 636 return nil, errors.Trace(err) 637 } 638 name := f.newResourceName(uuid.String()) 639 resourceName := f.client.ComposeName(name) 640 dstList := fmt.Sprintf("seclist:%s", seclist.Name) 641 srcList := fmt.Sprintf("seciplist:%s", ipList) 642 // create the new security rule parameters 643 rule := api.SecRuleParams{ 644 Action: common.SecRulePermit, 645 Application: app, 646 Description: "Juju created security rule", 647 Disabled: false, 648 Dst_list: dstList, 649 Name: resourceName, 650 Src_list: srcList, 651 } 652 // append the new parameters rule 653 ret = append(ret, rule) 654 } 655 return ret, nil 656 } 657 658 // convertApplicationToPortRange takes a SecApplication and 659 // converts it to a network.PortRange type 660 func (f Firewall) convertApplicationToPortRange(app response.SecApplication) corenetwork.PortRange { 661 appCopy := app 662 if appCopy.Value2 == -1 { 663 appCopy.Value2 = appCopy.Value1 664 } 665 return corenetwork.PortRange{ 666 FromPort: appCopy.Value1, 667 ToPort: appCopy.Value2, 668 Protocol: string(appCopy.Protocol), 669 } 670 } 671 672 // convertFromSecRules takes a slice of security rules and creates a map of them 673 func (f Firewall) convertFromSecRules(rules ...response.SecRule) (map[string][]network.IngressRule, error) { 674 applications, err := f.getAllApplicationsAsMap() 675 if err != nil { 676 return nil, errors.Trace(err) 677 } 678 679 iplists, err := f.getAllIPListsAsMap() 680 if err != nil { 681 return nil, errors.Trace(err) 682 } 683 684 ret := map[string][]network.IngressRule{} 685 for _, val := range rules { 686 app := val.Application 687 srcList := strings.TrimPrefix(val.Src_list, "seciplist:") 688 dstList := strings.TrimPrefix(val.Dst_list, "seclist:") 689 portRange := f.convertApplicationToPortRange(applications[app]) 690 if _, ok := ret[dstList]; !ok { 691 ret[dstList] = []network.IngressRule{ 692 { 693 PortRange: portRange, 694 SourceCIDRs: iplists[srcList].Secipentries, 695 }, 696 } 697 } else { 698 toAdd := network.IngressRule{ 699 PortRange: portRange, 700 SourceCIDRs: iplists[srcList].Secipentries, 701 } 702 ret[dstList] = append(ret[dstList], toAdd) 703 } 704 } 705 return ret, nil 706 } 707 708 // secRuleToIngressRule convert all security rules into a map of ingress rules 709 func (f Firewall) secRuleToIngresRule(rules ...response.SecRule) (map[string]network.IngressRule, error) { 710 711 applications, err := f.getAllApplicationsAsMap() 712 if err != nil { 713 return nil, errors.Trace(err) 714 } 715 iplists, err := f.getAllIPListsAsMap() 716 if err != nil { 717 return nil, errors.Trace(err) 718 } 719 720 ret := map[string]network.IngressRule{} 721 for _, val := range rules { 722 app := val.Application 723 srcList := strings.TrimPrefix(val.Src_list, "seciplist:") 724 portRange := f.convertApplicationToPortRange(applications[app]) 725 if _, ok := ret[val.Name]; !ok { 726 ret[val.Name] = network.IngressRule{ 727 PortRange: portRange, 728 SourceCIDRs: iplists[srcList].Secipentries, 729 } 730 } 731 } 732 return ret, nil 733 } 734 735 func (f Firewall) convertSecurityRuleToStub(rules response.SecurityRule) stubSecurityRule { 736 sort.Strings(rules.DstIpAddressPrefixSets) 737 sort.Strings(rules.SecProtocols) 738 sort.Strings(rules.SrcIpAddressPrefixSets) 739 return stubSecurityRule{ 740 Acl: rules.Acl, 741 FlowDirection: rules.FlowDirection, 742 DstIpAddressPrefixSets: rules.DstIpAddressPrefixSets, 743 SecProtocols: rules.SecProtocols, 744 SrcIpAddressPrefixSets: rules.SrcIpAddressPrefixSets, 745 } 746 } 747 748 func (f Firewall) convertSecurityRuleParamsToStub(params api.SecurityRuleParams) stubSecurityRule { 749 sort.Strings(params.DstIpAddressPrefixSets) 750 sort.Strings(params.SecProtocols) 751 sort.Strings(params.SrcIpAddressPrefixSets) 752 return stubSecurityRule{ 753 Acl: params.Acl, 754 FlowDirection: params.FlowDirection, 755 DstIpAddressPrefixSets: params.DstIpAddressPrefixSets, 756 SecProtocols: params.SecProtocols, 757 SrcIpAddressPrefixSets: params.SrcIpAddressPrefixSets, 758 } 759 } 760 761 // globalGroupName returns the global group name 762 // derived from the model UUID 763 func (f Firewall) globalGroupName() string { 764 return fmt.Sprintf("juju-%s-global", f.environ.Config().UUID()) 765 } 766 767 // machineGroupName returns the machine group name 768 // derived from the model UUID and the machine ID 769 func (f Firewall) machineGroupName(machineId string) string { 770 return fmt.Sprintf("juju-%s-%s", f.environ.Config().UUID(), machineId) 771 } 772 773 // resourceName returns the resource name 774 // derived from the model UUID and the name of the resource 775 func (f Firewall) newResourceName(appName string) string { 776 return fmt.Sprintf("juju-%s-%s", f.environ.Config().UUID(), appName) 777 } 778 779 // get all security rules associated with an ACL 780 func (f Firewall) getAllSecurityRules(aclName string) ([]response.SecurityRule, error) { 781 rules, err := f.client.AllSecurityRules(nil) 782 if err != nil { 783 return nil, err 784 } 785 if aclName == "" { 786 return rules.Result, nil 787 } 788 var ret []response.SecurityRule 789 for _, val := range rules.Result { 790 if val.Acl == aclName { 791 ret = append(ret, val) 792 } 793 } 794 return ret, nil 795 } 796 797 // getSecRules retrieves the security rules associated with a particular security list 798 func (f Firewall) getSecRules(seclist string) ([]response.SecRule, error) { 799 // we only care about ingress rules 800 name := fmt.Sprintf("seclist:%s", seclist) 801 rulesFilter := []api.Filter{ 802 { 803 Arg: "dst_list", 804 Value: name, 805 }, 806 } 807 rules, err := f.client.AllSecRules(rulesFilter) 808 if err != nil { 809 return nil, errors.Trace(err) 810 } 811 // gsamfira: the oracle compute API does not allow filtering by action 812 ret := []response.SecRule{} 813 for _, val := range rules.Result { 814 // gsamfira: We set a default policy of DENY. No use in worrying about 815 // DENY rules (if by any chance someone add one manually for some reason) 816 if val.Action != common.SecRulePermit { 817 continue 818 } 819 // We only care about rules that have a destination set 820 // to a security list. Those lists get attached to VMs 821 // NOTE: someone decided, when writing the oracle API 822 // that some fields should be bool, some should be string. 823 // never mind they both are boolean values...but hey. 824 // I swear...some people like to watch the world burn 825 if val.Dst_is_ip == "true" { 826 continue 827 } 828 // We only care about rules that have an IP list as source 829 if val.Src_is_ip == "false" { 830 continue 831 } 832 ret = append(ret, val) 833 } 834 return ret, nil 835 } 836 837 // ensureSecRules ensures that the list passed has all the rules 838 // that it needs, if one is missing it will create it inside the oracle 839 // cloud environment and it will return nil 840 // if none rule is missing then it will return nil 841 func (f Firewall) ensureSecRules(seclist response.SecList, rules []network.IngressRule) error { 842 // get all security rules associated with the seclist 843 secRules, err := f.getSecRules(seclist.Name) 844 if err != nil { 845 return errors.Trace(err) 846 } 847 logger.Tracef("list %v has sec rules: %v", seclist.Name, secRules) 848 849 converted, err := f.convertFromSecRules(secRules...) 850 if err != nil { 851 return errors.Trace(err) 852 } 853 logger.Tracef("converted rules are: %v", converted) 854 asIngressRules := converted[seclist.Name] 855 missing := []network.IngressRule{} 856 857 // search through all rules and find the missing ones 858 for _, toAdd := range rules { 859 found := false 860 for _, exists := range asIngressRules { 861 sort.Strings(toAdd.SourceCIDRs) 862 sort.Strings(exists.SourceCIDRs) 863 logger.Tracef("comparing %v to %v", toAdd.SourceCIDRs, exists.SourceCIDRs) 864 if reflect.DeepEqual(toAdd, exists) { 865 found = true 866 break 867 } 868 } 869 if found { 870 continue 871 } 872 missing = append(missing, toAdd) 873 } 874 if len(missing) == 0 { 875 return nil 876 } 877 logger.Tracef("Found missing rules: %v", missing) 878 // convert the missing rules back to sec rules 879 asSecRule, err := f.convertToSecRules(seclist, missing) 880 if err != nil { 881 return errors.Trace(err) 882 } 883 884 for _, val := range asSecRule { 885 _, err = f.client.CreateSecRule(val) 886 if err != nil { 887 return errors.Trace(err) 888 } 889 } 890 return nil 891 } 892 893 // ensureSecList creates a new seclist if one does not already exist. 894 // this function is idempotent 895 func (f Firewall) ensureSecList(name string) (response.SecList, error) { 896 logger.Infof("Fetching details for list: %s", name) 897 // check if the security list is already there 898 details, err := f.client.SecListDetails(name) 899 if err != nil { 900 logger.Infof("Got error fetching details for %s: %v", name, err) 901 if api.IsNotFound(err) { 902 logger.Infof("Creating new seclist: %s", name) 903 details, err := f.client.CreateSecList( 904 "Juju created security list", 905 name, 906 common.SecRulePermit, 907 common.SecRuleDeny) 908 if err != nil { 909 return response.SecList{}, err 910 } 911 return details, nil 912 } 913 return response.SecList{}, err 914 } 915 return details, nil 916 } 917 918 // getAllIPLists returns all IP lists known to the provider (both the user defined ones, 919 // and the default ones) 920 func (f Firewall) getAllIPLists() ([]response.SecIpList, error) { 921 // get all security ip lists from the current identity endpoint 922 secIpLists, err := f.client.AllSecIpLists(nil) 923 if err != nil { 924 return nil, errors.Trace(err) 925 } 926 // get all security ip lists from the default oracle cloud definitions 927 defaultSecIpLists, err := f.client.AllDefaultSecIpLists(nil) 928 if err != nil { 929 return nil, errors.Trace(err) 930 } 931 932 allIpLists := []response.SecIpList{} 933 allIpLists = append(allIpLists, secIpLists.Result...) 934 allIpLists = append(allIpLists, defaultSecIpLists.Result...) 935 return allIpLists, nil 936 } 937 938 // getAllIPListsAsMap returns all IP lists as a map, with the key being 939 // the resource name 940 func (f Firewall) getAllIPListsAsMap() (map[string]response.SecIpList, error) { 941 allIps, err := f.getAllIPLists() 942 if err != nil { 943 return nil, errors.Trace(err) 944 } 945 allIpLists := map[string]response.SecIpList{} 946 for _, val := range allIps { 947 allIpLists[val.Name] = val 948 } 949 return allIpLists, nil 950 } 951 952 // ensureSecIpList ensures that a sec ip list with the provided cidr list 953 // exists. If one does not, it gets created. This function is idempotent. 954 func (f Firewall) ensureSecIpList(cidr []string, cache *[]response.SecIpList) (string, error) { 955 sort.Strings(cidr) 956 for _, val := range *cache { 957 sort.Strings(val.Secipentries) 958 if reflect.DeepEqual(val.Secipentries, cidr) { 959 return val.Name, nil 960 } 961 } 962 uuid, err := utils.NewUUID() 963 if err != nil { 964 return "", errors.Trace(err) 965 } 966 name := f.newResourceName(uuid.String()) 967 resource := f.client.ComposeName(name) 968 secList, err := f.client.CreateSecIpList( 969 "Juju created security IP list", 970 resource, cidr) 971 if err != nil { 972 return "", errors.Trace(err) 973 } 974 *cache = append(*cache, secList) 975 return secList.Name, nil 976 }