github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/firewaller/firewaller.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package firewaller 5 6 import ( 7 stdcontext "context" 8 "io" 9 "sort" 10 "time" 11 12 "github.com/EvilSuperstars/go-cidrman" 13 "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery" 14 "github.com/juju/charm/v12" 15 "github.com/juju/clock" 16 "github.com/juju/collections/set" 17 "github.com/juju/errors" 18 "github.com/juju/names/v5" 19 "github.com/juju/worker/v3" 20 "github.com/juju/worker/v3/catacomb" 21 "gopkg.in/macaroon.v2" 22 23 "github.com/juju/juju/api" 24 "github.com/juju/juju/api/controller/firewaller" 25 "github.com/juju/juju/api/controller/remoterelations" 26 "github.com/juju/juju/core/instance" 27 "github.com/juju/juju/core/life" 28 "github.com/juju/juju/core/network" 29 "github.com/juju/juju/core/network/firewall" 30 "github.com/juju/juju/core/relation" 31 "github.com/juju/juju/core/watcher" 32 "github.com/juju/juju/environs" 33 "github.com/juju/juju/environs/config" 34 "github.com/juju/juju/environs/context" 35 "github.com/juju/juju/environs/instances" 36 "github.com/juju/juju/environs/models" 37 "github.com/juju/juju/rpc/params" 38 "github.com/juju/juju/worker/common" 39 ) 40 41 // FirewallerAPI exposes functionality off the firewaller API facade to a worker. 42 type FirewallerAPI interface { 43 WatchModelMachines() (watcher.StringsWatcher, error) 44 WatchOpenedPorts() (watcher.StringsWatcher, error) 45 WatchModelFirewallRules() (watcher.NotifyWatcher, error) 46 ModelFirewallRules() (firewall.IngressRules, error) 47 ModelConfig() (*config.Config, error) 48 Machine(tag names.MachineTag) (*firewaller.Machine, error) 49 Unit(tag names.UnitTag) (*firewaller.Unit, error) 50 Relation(tag names.RelationTag) (*firewaller.Relation, error) 51 WatchEgressAddressesForRelation(tag names.RelationTag) (watcher.StringsWatcher, error) 52 WatchIngressAddressesForRelation(tag names.RelationTag) (watcher.StringsWatcher, error) 53 ControllerAPIInfoForModel(modelUUID string) (*api.Info, error) 54 MacaroonForRelation(relationKey string) (*macaroon.Macaroon, error) 55 SetRelationStatus(relationKey string, status relation.Status, message string) error 56 AllSpaceInfos() (network.SpaceInfos, error) 57 WatchSubnets() (watcher.StringsWatcher, error) 58 } 59 60 // CrossModelFirewallerFacade exposes firewaller functionality on the 61 // remote offering model to a worker. 62 type CrossModelFirewallerFacade interface { 63 PublishIngressNetworkChange(params.IngressNetworksChangeEvent) error 64 WatchEgressAddressesForRelation(details params.RemoteEntityArg) (watcher.StringsWatcher, error) 65 } 66 67 // RemoteFirewallerAPICloser implements CrossModelFirewallerFacade 68 // and adds a Close() method. 69 type CrossModelFirewallerFacadeCloser interface { 70 io.Closer 71 CrossModelFirewallerFacade 72 } 73 74 // EnvironFirewaller defines methods to allow the worker to perform 75 // firewall operations (open/close ports) on a Juju global firewall. 76 type EnvironFirewaller interface { 77 environs.Firewaller 78 } 79 80 // EnvironModelFirewaller defines methods to allow the worker to 81 // perform firewall operations (open/close port) on a Juju model firewall. 82 type EnvironModelFirewaller interface { 83 models.ModelFirewaller 84 } 85 86 // EnvironInstances defines methods to allow the worker to perform 87 // operations on instances in a Juju cloud environment. 88 type EnvironInstances interface { 89 Instances(ctx context.ProviderCallContext, ids []instance.Id) ([]instances.Instance, error) 90 } 91 92 type newCrossModelFacadeFunc func(*api.Info) (CrossModelFirewallerFacadeCloser, error) 93 94 // Config defines the operation of a Worker. 95 type Config struct { 96 ModelUUID string 97 Mode string 98 FirewallerAPI FirewallerAPI 99 RemoteRelationsApi *remoterelations.Client 100 EnvironFirewaller EnvironFirewaller 101 EnvironModelFirewaller EnvironModelFirewaller 102 EnvironInstances EnvironInstances 103 EnvironIPV6CIDRSupport bool 104 105 NewCrossModelFacadeFunc newCrossModelFacadeFunc 106 107 Clock clock.Clock 108 Logger Logger 109 110 CredentialAPI common.CredentialAPI 111 112 // TODO: (jack-w-shaw) Drop these once we move tests to mocks based 113 // WatchMachineNotify is called when the Firewaller starts watching the 114 // machine with the given tag (manual machines aren't watched). This 115 // should only be used for testing. 116 WatchMachineNotify func(tag names.MachineTag) 117 // FlushModelNotify is called when the Firewaller flushes it's model. 118 // This should only be used for testing 119 FlushModelNotify func() 120 } 121 122 // Validate returns an error if cfg cannot drive a Worker. 123 func (cfg Config) Validate() error { 124 if cfg.ModelUUID == "" { 125 return errors.NotValidf("empty model uuid") 126 } 127 if cfg.FirewallerAPI == nil { 128 return errors.NotValidf("nil Firewaller Facade") 129 } 130 if cfg.RemoteRelationsApi == nil { 131 return errors.NotValidf("nil RemoteRelations Facade") 132 } 133 if cfg.Mode == config.FwGlobal && cfg.EnvironFirewaller == nil { 134 return errors.NotValidf("nil EnvironFirewaller") 135 } 136 if cfg.EnvironInstances == nil { 137 return errors.NotValidf("nil EnvironInstances") 138 } 139 if cfg.NewCrossModelFacadeFunc == nil { 140 return errors.NotValidf("nil Cross Model Facade func") 141 } 142 if cfg.Logger == nil { 143 return errors.NotValidf("nil Logger") 144 } 145 if cfg.CredentialAPI == nil { 146 return errors.NotValidf("nil Credential Facade") 147 } 148 return nil 149 } 150 151 // Firewaller watches the state for port ranges opened or closed on 152 // machines and reflects those changes onto the backing environment. 153 // Uses Firewaller API V1. 154 type Firewaller struct { 155 catacomb catacomb.Catacomb 156 firewallerApi FirewallerAPI 157 remoteRelationsApi *remoterelations.Client 158 environFirewaller EnvironFirewaller 159 environModelFirewaller EnvironModelFirewaller 160 environInstances EnvironInstances 161 162 machinesWatcher watcher.StringsWatcher 163 portsWatcher watcher.StringsWatcher 164 subnetWatcher watcher.StringsWatcher 165 modelFirewallWatcher watcher.NotifyWatcher 166 machineds map[names.MachineTag]*machineData 167 unitsChange chan *unitsChange 168 unitds map[names.UnitTag]*unitData 169 applicationids map[names.ApplicationTag]*applicationData 170 exposedChange chan *exposedChange 171 spaceInfos network.SpaceInfos 172 globalMode bool 173 globalIngressRuleRef map[string]int // map of rule names to count of occurrences 174 175 // Set to true if the environment supports ingress rules containing 176 // IPV6 CIDRs. 177 envIPV6CIDRSupport bool 178 179 modelUUID string 180 newRemoteFirewallerAPIFunc newCrossModelFacadeFunc 181 remoteRelationsWatcher watcher.StringsWatcher 182 localRelationsChange chan *remoteRelationNetworkChange 183 relationIngress map[names.RelationTag]*remoteRelationData 184 relationWorkerRunner *worker.Runner 185 clk clock.Clock 186 logger Logger 187 188 cloudCallContextFunc common.CloudCallContextFunc 189 190 // Only used for testing 191 watchMachineNotify func(tag names.MachineTag) 192 flushModelNotify func() 193 } 194 195 // NewFirewaller returns a new Firewaller. 196 func NewFirewaller(cfg Config) (worker.Worker, error) { 197 if err := cfg.Validate(); err != nil { 198 return nil, errors.Trace(err) 199 } 200 clk := cfg.Clock 201 if clk == nil { 202 clk = clock.WallClock 203 } 204 205 fw := &Firewaller{ 206 firewallerApi: cfg.FirewallerAPI, 207 remoteRelationsApi: cfg.RemoteRelationsApi, 208 environFirewaller: cfg.EnvironFirewaller, 209 environModelFirewaller: cfg.EnvironModelFirewaller, 210 environInstances: cfg.EnvironInstances, 211 envIPV6CIDRSupport: cfg.EnvironIPV6CIDRSupport, 212 newRemoteFirewallerAPIFunc: cfg.NewCrossModelFacadeFunc, 213 modelUUID: cfg.ModelUUID, 214 machineds: make(map[names.MachineTag]*machineData), 215 unitsChange: make(chan *unitsChange), 216 unitds: make(map[names.UnitTag]*unitData), 217 applicationids: make(map[names.ApplicationTag]*applicationData), 218 exposedChange: make(chan *exposedChange), 219 relationIngress: make(map[names.RelationTag]*remoteRelationData), 220 localRelationsChange: make(chan *remoteRelationNetworkChange), 221 clk: clk, 222 logger: cfg.Logger, 223 relationWorkerRunner: worker.NewRunner(worker.RunnerParams{ 224 Clock: clk, 225 Logger: cfg.Logger, 226 227 // One of the remote relation workers failing should not 228 // prevent the others from running. 229 IsFatal: func(error) bool { return false }, 230 231 // For any failures, try again in 1 minute. 232 RestartDelay: time.Minute, 233 }), 234 cloudCallContextFunc: common.NewCloudCallContextFunc(cfg.CredentialAPI), 235 watchMachineNotify: cfg.WatchMachineNotify, 236 flushModelNotify: cfg.FlushModelNotify, 237 } 238 239 switch cfg.Mode { 240 case config.FwInstance: 241 case config.FwGlobal: 242 fw.globalMode = true 243 fw.globalIngressRuleRef = make(map[string]int) 244 default: 245 return nil, errors.Errorf("invalid firewall-mode %q", cfg.Mode) 246 } 247 248 err := catacomb.Invoke(catacomb.Plan{ 249 Site: &fw.catacomb, 250 Work: fw.loop, 251 Init: []worker.Worker{fw.relationWorkerRunner}, 252 }) 253 if err != nil { 254 return nil, errors.Trace(err) 255 } 256 return fw, nil 257 } 258 259 func (fw *Firewaller) setUp() error { 260 var err error 261 fw.machinesWatcher, err = fw.firewallerApi.WatchModelMachines() 262 if err != nil { 263 return errors.Trace(err) 264 } 265 if err := fw.catacomb.Add(fw.machinesWatcher); err != nil { 266 return errors.Trace(err) 267 } 268 269 fw.portsWatcher, err = fw.firewallerApi.WatchOpenedPorts() 270 if err != nil { 271 return errors.Annotatef(err, "failed to start ports watcher") 272 } 273 if err := fw.catacomb.Add(fw.portsWatcher); err != nil { 274 return errors.Trace(err) 275 } 276 277 fw.remoteRelationsWatcher, err = fw.remoteRelationsApi.WatchRemoteRelations() 278 if err != nil { 279 return errors.Trace(err) 280 } 281 if err := fw.catacomb.Add(fw.remoteRelationsWatcher); err != nil { 282 return errors.Trace(err) 283 } 284 285 fw.subnetWatcher, err = fw.firewallerApi.WatchSubnets() 286 if err != nil { 287 return errors.Annotatef(err, "failed to start subnet watcher") 288 } 289 if err := fw.catacomb.Add(fw.subnetWatcher); err != nil { 290 return errors.Trace(err) 291 } 292 293 if fw.environModelFirewaller != nil { 294 fw.modelFirewallWatcher, err = fw.firewallerApi.WatchModelFirewallRules() 295 if err != nil { 296 return errors.Annotatef(err, "failed to start subnet watcher") 297 } 298 if err := fw.catacomb.Add(fw.modelFirewallWatcher); err != nil { 299 return errors.Trace(err) 300 } 301 } 302 303 if fw.spaceInfos, err = fw.firewallerApi.AllSpaceInfos(); err != nil { 304 return errors.Trace(err) 305 } 306 307 fw.logger.Debugf("started watching opened port ranges for the model") 308 return nil 309 } 310 311 func (fw *Firewaller) loop() error { 312 if err := fw.setUp(); err != nil { 313 return errors.Trace(err) 314 } 315 var ( 316 reconciled bool 317 modelGroupInitiallyConfigured bool 318 ) 319 portsChange := fw.portsWatcher.Changes() 320 321 var modelFirewallChanges watcher.NotifyChannel 322 var ensureModelFirewalls <-chan time.Time 323 if fw.modelFirewallWatcher != nil { 324 modelFirewallChanges = fw.modelFirewallWatcher.Changes() 325 } 326 327 for { 328 select { 329 case <-fw.catacomb.Dying(): 330 return fw.catacomb.ErrDying() 331 case <-ensureModelFirewalls: 332 err := fw.flushModel() 333 if errors.Is(err, errors.NotFound) { 334 ensureModelFirewalls = fw.clk.After(time.Second) 335 } else if err != nil { 336 return err 337 } else { 338 ensureModelFirewalls = nil 339 } 340 case _, ok := <-modelFirewallChanges: 341 if !ok { 342 return errors.New("model config watcher closed") 343 } 344 if ensureModelFirewalls == nil { 345 ensureModelFirewalls = fw.clk.After(0) 346 } 347 case change, ok := <-fw.machinesWatcher.Changes(): 348 if !ok { 349 return errors.New("machines watcher closed") 350 } 351 for _, machineId := range change { 352 if err := fw.machineLifeChanged(names.NewMachineTag(machineId)); err != nil { 353 return err 354 } 355 } 356 if !reconciled { 357 reconciled = true 358 var err error 359 if fw.globalMode { 360 err = fw.reconcileGlobal() 361 } else { 362 err = fw.reconcileInstances() 363 } 364 if err != nil { 365 return errors.Trace(err) 366 } 367 } 368 // After first machine exists, make sure to trigger the model firewall flush. 369 if len(change) > 0 && !modelGroupInitiallyConfigured { 370 modelGroupInitiallyConfigured = true 371 if ensureModelFirewalls == nil { 372 ensureModelFirewalls = fw.clk.After(0) 373 } 374 } 375 case change, ok := <-portsChange: 376 if !ok { 377 return errors.New("ports watcher closed") 378 } 379 for _, portsGlobalKey := range change { 380 machineTag := names.NewMachineTag(portsGlobalKey) 381 if err := fw.openedPortsChanged(machineTag); err != nil { 382 return errors.Trace(err) 383 } 384 } 385 case change, ok := <-fw.remoteRelationsWatcher.Changes(): 386 if !ok { 387 return errors.New("remote relations watcher closed") 388 } 389 for _, relationKey := range change { 390 if err := fw.relationLifeChanged(names.NewRelationTag(relationKey)); err != nil { 391 return err 392 } 393 } 394 case _, ok := <-fw.subnetWatcher.Changes(): 395 if !ok { 396 return errors.New("subnet watcher closed") 397 } 398 399 if err := fw.subnetsChanged(); err != nil { 400 return errors.Trace(err) 401 } 402 case change := <-fw.localRelationsChange: 403 // We have a notification that the remote (consuming) model 404 // has changed egress networks so need to update the local 405 // model to allow those networks through the firewall. 406 if err := fw.relationIngressChanged(change); err != nil { 407 return errors.Trace(err) 408 } 409 case change := <-fw.unitsChange: 410 if err := fw.unitsChanged(change); err != nil { 411 return errors.Trace(err) 412 } 413 case change := <-fw.exposedChange: 414 change.applicationd.exposed = change.exposed 415 change.applicationd.exposedEndpoints = change.exposedEndpoints 416 var unitds []*unitData 417 for _, unitd := range change.applicationd.unitds { 418 unitds = append(unitds, unitd) 419 } 420 if err := fw.flushUnits(unitds); err != nil { 421 return errors.Annotate(err, "cannot change firewall ports") 422 } 423 } 424 } 425 } 426 427 func (fw *Firewaller) subnetsChanged() error { 428 // Refresh space topology 429 var err error 430 if fw.spaceInfos, err = fw.firewallerApi.AllSpaceInfos(); err != nil { 431 return errors.Trace(err) 432 } 433 434 // Select units for which the ingress rules must be refreshed. We only 435 // consider applications that expose endpoints to at least one space. 436 var unitds []*unitData 437 for _, appd := range fw.applicationids { 438 var exposedToSpaces bool 439 for _, exposeDetails := range appd.exposedEndpoints { 440 if len(exposeDetails.ExposeToSpaces) != 0 { 441 exposedToSpaces = true 442 break 443 } 444 } 445 446 if !exposedToSpaces { 447 continue // no need to re-eval ingress rules. 448 } 449 450 for _, unitd := range appd.unitds { 451 unitds = append(unitds, unitd) 452 } 453 } 454 455 if len(unitds) == 0 { 456 return nil // nothing to do 457 } 458 459 if err := fw.flushUnits(unitds); err != nil { 460 return errors.Annotate(err, "cannot update unit ingress rules") 461 } 462 return nil 463 } 464 465 func (fw *Firewaller) relationIngressChanged(change *remoteRelationNetworkChange) error { 466 fw.logger.Debugf("process remote relation ingress change for %v", change.relationTag) 467 relData, ok := fw.relationIngress[change.relationTag] 468 if ok { 469 relData.networks = change.networks 470 relData.ingressRequired = change.ingressRequired 471 } 472 appData, ok := fw.applicationids[change.localApplicationTag] 473 if !ok { 474 fw.logger.Debugf("ignoring unknown application: %v", change.localApplicationTag) 475 return nil 476 } 477 unitds := []*unitData{} 478 for _, unitd := range appData.unitds { 479 unitds = append(unitds, unitd) 480 } 481 if err := fw.flushUnits(unitds); err != nil { 482 return errors.Annotate(err, "cannot change firewall ports") 483 } 484 return nil 485 } 486 487 // startMachine creates a new data value for tracking details of the 488 // machine and starts watching the machine for units added or removed. 489 func (fw *Firewaller) startMachine(tag names.MachineTag) error { 490 machined := &machineData{ 491 fw: fw, 492 tag: tag, 493 unitds: make(map[names.UnitTag]*unitData), 494 } 495 m, err := machined.machine() 496 if params.IsCodeNotFound(err) { 497 fw.logger.Debugf("not watching %q", tag) 498 return nil 499 } else if err != nil { 500 return errors.Annotate(err, "cannot watch machine units") 501 } 502 manual, err := m.IsManual() 503 if err != nil { 504 return errors.Trace(err) 505 } 506 if manual { 507 // Don't track manual machines, we can't change their ports. 508 fw.logger.Debugf("not watching manual %q", tag) 509 return nil 510 } 511 unitw, err := m.WatchUnits() 512 if err != nil { 513 return errors.Trace(err) 514 } 515 // XXX(fwereade): this is the best of a bunch of bad options. We've started 516 // the watch, so we're responsible for it; but we (probably?) need to do this 517 // little dance below to update the machined data on the fw loop goroutine, 518 // whence it's usually accessed, before we start the machined watchLoop 519 // below. That catacomb *should* be the only one responsible -- and it *is* 520 // responsible -- but having it in the main fw catacomb as well does no harm, 521 // and greatly simplifies the code below (which would otherwise have to 522 // manage unitw lifetime and errors manually). 523 if err := fw.catacomb.Add(unitw); err != nil { 524 return errors.Trace(err) 525 } 526 select { 527 case <-fw.catacomb.Dying(): 528 return fw.catacomb.ErrDying() 529 case change, ok := <-unitw.Changes(): 530 if !ok { 531 return errors.New("machine units watcher closed") 532 } 533 fw.machineds[tag] = machined 534 err = fw.unitsChanged(&unitsChange{machined, change}) 535 if err != nil { 536 delete(fw.machineds, tag) 537 return errors.Annotatef(err, "cannot respond to units changes for %q, %q", tag, fw.modelUUID) 538 } 539 } 540 541 err = catacomb.Invoke(catacomb.Plan{ 542 Site: &machined.catacomb, 543 Work: func() error { 544 return machined.watchLoop(unitw) 545 }, 546 }) 547 if err != nil { 548 delete(fw.machineds, tag) 549 return errors.Trace(err) 550 } 551 552 // register the machined with the firewaller's catacomb. 553 err = fw.catacomb.Add(machined) 554 if err != nil { 555 return errors.Trace(err) 556 } 557 fw.logger.Debugf("started watching %q", tag) 558 if fw.watchMachineNotify != nil { 559 fw.watchMachineNotify(tag) 560 } 561 return nil 562 } 563 564 // startUnit creates a new data value for tracking details of the unit 565 // The provided machineTag must be the tag for the machine the unit was last 566 // observed to be assigned to. 567 func (fw *Firewaller) startUnit(unit *firewaller.Unit, machineTag names.MachineTag) error { 568 application, err := unit.Application() 569 if err != nil { 570 return err 571 } 572 573 applicationTag := application.Tag() 574 unitTag := unit.Tag() 575 unitd := &unitData{ 576 fw: fw, 577 unit: unit, 578 tag: unitTag, 579 } 580 fw.unitds[unitTag] = unitd 581 582 unitd.machined = fw.machineds[machineTag] 583 unitd.machined.unitds[unitTag] = unitd 584 if fw.applicationids[applicationTag] == nil { 585 err := fw.startApplication(application) 586 if err != nil { 587 delete(fw.unitds, unitTag) 588 delete(unitd.machined.unitds, unitTag) 589 return err 590 } 591 } 592 unitd.applicationd = fw.applicationids[applicationTag] 593 unitd.applicationd.unitds[unitTag] = unitd 594 595 if err = fw.openedPortsChanged(machineTag); err != nil { 596 return errors.Trace(err) 597 } 598 599 return nil 600 } 601 602 // startApplication creates a new data value for tracking details of the 603 // application and starts watching the application for exposure changes. 604 func (fw *Firewaller) startApplication(app *firewaller.Application) error { 605 exposed, exposedEndpoints, err := app.ExposeInfo() 606 if err != nil { 607 return err 608 } 609 applicationd := &applicationData{ 610 fw: fw, 611 application: app, 612 exposed: exposed, 613 exposedEndpoints: exposedEndpoints, 614 unitds: make(map[names.UnitTag]*unitData), 615 } 616 fw.applicationids[app.Tag()] = applicationd 617 618 err = catacomb.Invoke(catacomb.Plan{ 619 Site: &applicationd.catacomb, 620 Work: func() error { 621 return applicationd.watchLoop(exposed, exposedEndpoints) 622 }, 623 }) 624 if err != nil { 625 return errors.Trace(err) 626 } 627 if err := fw.catacomb.Add(applicationd); err != nil { 628 return errors.Trace(err) 629 } 630 return nil 631 } 632 633 // reconcileGlobal compares the initially started watcher for machines, 634 // units and applications with the opened and closed ports globally and 635 // opens and closes the appropriate ports for the whole environment. 636 func (fw *Firewaller) reconcileGlobal() error { 637 var machines []*machineData 638 for _, machined := range fw.machineds { 639 machines = append(machines, machined) 640 } 641 want, err := fw.gatherIngressRules(machines...) 642 if err != nil { 643 return err 644 } 645 ctx := stdcontext.Background() 646 initialPortRanges, err := fw.environFirewaller.IngressRules(fw.cloudCallContextFunc(ctx)) 647 if err != nil { 648 return err 649 } 650 651 // Check which ports to open or to close. 652 toOpen, toClose := initialPortRanges.Diff(want) 653 if len(toOpen) > 0 { 654 fw.logger.Infof("opening global ports %v", toOpen) 655 if err := fw.environFirewaller.OpenPorts(fw.cloudCallContextFunc(ctx), toOpen); err != nil { 656 return errors.Annotatef(err, "failed to open global ports %v", toOpen) 657 } 658 } 659 if len(toClose) > 0 { 660 fw.logger.Infof("closing global ports %v", toClose) 661 if err := fw.environFirewaller.ClosePorts(fw.cloudCallContextFunc(ctx), toClose); err != nil { 662 return errors.Annotatef(err, "failed to close global ports %v", toClose) 663 } 664 } 665 return nil 666 } 667 668 // reconcileInstances compares the initially started watcher for machines, 669 // units and applications with the opened and closed ports of the instances and 670 // opens and closes the appropriate ports for each instance. 671 func (fw *Firewaller) reconcileInstances() error { 672 for _, machined := range fw.machineds { 673 m, err := machined.machine() 674 if params.IsCodeNotFound(err) { 675 if err := fw.forgetMachine(machined); err != nil { 676 return err 677 } 678 continue 679 } 680 if err != nil { 681 return err 682 } 683 instanceId, err := m.InstanceId() 684 if errors.IsNotProvisioned(err) { 685 fw.logger.Errorf("Machine not yet provisioned: %v", err) 686 continue 687 } 688 if err != nil { 689 return err 690 } 691 ctx := stdcontext.Background() 692 envInstances, err := fw.environInstances.Instances(fw.cloudCallContextFunc(ctx), []instance.Id{instanceId}) 693 if err == environs.ErrNoInstances { 694 return nil 695 } 696 if err != nil { 697 return err 698 } 699 machineId := machined.tag.Id() 700 701 fwInstance, ok := envInstances[0].(instances.InstanceFirewaller) 702 if !ok { 703 return nil 704 } 705 706 initialRules, err := fwInstance.IngressRules(fw.cloudCallContextFunc(ctx), machineId) 707 if err != nil { 708 return err 709 } 710 711 // Check which ports to open or to close. 712 toOpen, toClose := initialRules.Diff(machined.ingressRules) 713 if len(toOpen) > 0 { 714 fw.logger.Infof("opening instance port ranges %v for %q", 715 toOpen, machined.tag) 716 if err := fwInstance.OpenPorts(fw.cloudCallContextFunc(ctx), machineId, toOpen); err != nil { 717 // TODO(mue) Add local retry logic. 718 return errors.Annotatef(err, "failed to open instance ports %v for %q", toOpen, machined.tag) 719 } 720 } 721 if len(toClose) > 0 { 722 fw.logger.Infof("closing instance port ranges %v for %q", 723 toClose, machined.tag) 724 if err := fwInstance.ClosePorts(fw.cloudCallContextFunc(ctx), machineId, toClose); err != nil { 725 // TODO(mue) Add local retry logic. 726 return errors.Annotatef(err, "failed to close instance ports %v for %q", toOpen, machined.tag) 727 } 728 } 729 } 730 return nil 731 } 732 733 // unitsChanged responds to changes to the assigned units. 734 func (fw *Firewaller) unitsChanged(change *unitsChange) error { 735 changed := []*unitData{} 736 for _, name := range change.units { 737 unitTag := names.NewUnitTag(name) 738 unit, err := fw.firewallerApi.Unit(unitTag) 739 if err != nil && !params.IsCodeNotFound(err) { 740 return err 741 } 742 var machineTag names.MachineTag 743 if unit != nil { 744 machineTag, err = unit.AssignedMachine() 745 if params.IsCodeNotFound(err) { 746 continue 747 } else if err != nil && !params.IsCodeNotAssigned(err) { 748 return err 749 } 750 } 751 if unitd, known := fw.unitds[unitTag]; known { 752 knownMachineTag := fw.unitds[unitTag].machined.tag 753 if unit == nil || unit.Life() == life.Dead || machineTag != knownMachineTag { 754 fw.forgetUnit(unitd) 755 changed = append(changed, unitd) 756 fw.logger.Debugf("stopped watching unit %s", name) 757 } 758 } else if unit != nil && unit.Life() != life.Dead && fw.machineds[machineTag] != nil { 759 err = fw.startUnit(unit, machineTag) 760 if params.IsCodeNotFound(err) { 761 continue 762 } 763 if err != nil { 764 return err 765 } 766 changed = append(changed, fw.unitds[unitTag]) 767 fw.logger.Debugf("started watching %q", unitTag) 768 } 769 } 770 if err := fw.flushUnits(changed); err != nil { 771 return errors.Annotate(err, "cannot change firewall ports") 772 } 773 return nil 774 } 775 776 // openedPortsChanged handles port change notifications 777 func (fw *Firewaller) openedPortsChanged(machineTag names.MachineTag) (err error) { 778 defer func() { 779 if params.IsCodeNotFound(err) { 780 err = nil 781 } 782 }() 783 machined, ok := fw.machineds[machineTag] 784 if !ok { 785 // It is common to receive a port change notification before 786 // registering the machine, so if a machine is not found in 787 // firewaller's list, just skip the change. Look up will also 788 // fail if it's a manual machine. 789 fw.logger.Debugf("failed to lookup %q, skipping port change", machineTag) 790 return nil 791 } 792 793 m, err := machined.machine() 794 if err != nil { 795 return err 796 } 797 798 _, opendPortRangesByEndpoint, err := m.OpenedMachinePortRanges() 799 if err != nil { 800 return err 801 } 802 803 // Check for missing units and defer the handling of this change for 804 // the future. 805 for unitTag := range opendPortRangesByEndpoint { 806 if _, ok := machined.unitds[unitTag]; !ok { 807 // It is common to receive port change notification before 808 // registering a unit. Skip handling the port change - it will 809 // be handled when the unit is registered. 810 fw.logger.Debugf("failed to lookup %q, skipping port change", unitTag) 811 return nil 812 } 813 } 814 815 if equalGroupedPortRanges(machined.openedPortRangesByEndpoint, opendPortRangesByEndpoint) { 816 return nil // no change 817 } 818 819 machined.openedPortRangesByEndpoint = opendPortRangesByEndpoint 820 return fw.flushMachine(machined) 821 } 822 823 func equalGroupedPortRanges(a, b map[names.UnitTag]network.GroupedPortRanges) bool { 824 if len(a) != len(b) { 825 return false 826 } 827 for unitTag, portRangesA := range a { 828 portRangesB, exists := b[unitTag] 829 if !exists || !portRangesA.EqualTo(portRangesB) { 830 return false 831 } 832 } 833 return true 834 } 835 836 // flushUnits opens and closes ports for the passed unit data. 837 func (fw *Firewaller) flushUnits(unitds []*unitData) error { 838 machineds := map[names.MachineTag]*machineData{} 839 for _, unitd := range unitds { 840 machineds[unitd.machined.tag] = unitd.machined 841 } 842 for _, machined := range machineds { 843 if err := fw.flushMachine(machined); err != nil { 844 return err 845 } 846 } 847 return nil 848 } 849 850 // flushMachine opens and closes ports for the passed machine. 851 func (fw *Firewaller) flushMachine(machined *machineData) error { 852 want, err := fw.gatherIngressRules(machined) 853 if err != nil { 854 return errors.Trace(err) 855 } 856 toOpen, toClose := machined.ingressRules.Diff(want) 857 machined.ingressRules = want 858 if fw.globalMode { 859 return fw.flushGlobalPorts(toOpen, toClose) 860 } 861 return fw.flushInstancePorts(machined, toOpen, toClose) 862 } 863 864 // gatherIngressRules returns the ingress rules to open and close 865 // for the specified machines. 866 func (fw *Firewaller) gatherIngressRules(machines ...*machineData) (firewall.IngressRules, error) { 867 var want firewall.IngressRules 868 for _, machined := range machines { 869 for unitTag := range machined.openedPortRangesByEndpoint { 870 unitd, known := machined.unitds[unitTag] 871 if !known { 872 fw.logger.Debugf("no ingress rules for unknown %v on %v", unitTag, machined.tag) 873 continue 874 } 875 876 unitRules, err := fw.ingressRulesForMachineUnit(machined, unitd) 877 if err != nil { 878 return nil, errors.Trace(err) 879 } 880 881 want = append(want, unitRules...) 882 } 883 } 884 if err := want.Validate(); err != nil { 885 return nil, errors.Trace(err) 886 } 887 888 // Substrates that do not support IPV6 CIDRs will complain if we pass 889 // an IPV6 CIDR. To work around this issue, we filter out any IPV6 890 // CIDRs from the collected ingress rule list. 891 if !fw.envIPV6CIDRSupport { 892 want = want.RemoveCIDRsMatchingAddressType(network.IPv6Address) 893 } 894 895 return want, nil 896 } 897 898 func (fw *Firewaller) ingressRulesForMachineUnit(machine *machineData, unit *unitData) (firewall.IngressRules, error) { 899 unitPortRanges := machine.openedPortRangesByEndpoint[unit.tag] 900 if len(unitPortRanges) == 0 { 901 return nil, nil // no ports opened by the charm 902 } 903 904 var rules firewall.IngressRules 905 var err error 906 if unit.applicationd.exposed { 907 rules = fw.ingressRulesForExposedMachineUnit(machine, unit, unitPortRanges) 908 } else { 909 if rules, err = fw.ingressRulesForNonExposedMachineUnit(unit.applicationd.application.Tag(), 910 unitPortRanges); err != nil { 911 return nil, errors.Trace(err) 912 } 913 } 914 915 // De-dup and sort rules before returning them back. 916 rules = rules.UniqueRules() 917 sort.Slice(rules, func(i, j int) bool { return rules[i].LessThan(rules[j]) }) 918 fw.logger.Debugf("ingress rules for %q: %v", unit.tag, rules) 919 return rules, nil 920 } 921 922 func (fw *Firewaller) ingressRulesForNonExposedMachineUnit(appTag names.ApplicationTag, 923 openUnitPortRanges network.GroupedPortRanges) (firewall.IngressRules, error) { 924 // Not exposed, so add any ingress rules required by remote relations. 925 srcCIDRs, err := fw.updateForRemoteRelationIngress(appTag) 926 if err != nil || len(srcCIDRs) == 0 { 927 return nil, errors.Trace(err) 928 } 929 930 var rules firewall.IngressRules 931 for _, portRange := range openUnitPortRanges.UniquePortRanges() { 932 rules = append(rules, firewall.NewIngressRule(portRange, srcCIDRs.Values()...)) 933 } 934 935 return rules, nil 936 } 937 938 func (fw *Firewaller) ingressRulesForExposedMachineUnit(machine *machineData, unit *unitData, 939 openUnitPortRanges network.GroupedPortRanges) firewall.IngressRules { 940 var ( 941 exposedEndpoints = unit.applicationd.exposedEndpoints 942 rules firewall.IngressRules 943 ) 944 945 for exposedEndpoint, exposeDetails := range exposedEndpoints { 946 // Collect the operator-provided CIDRs that should be able to 947 // access the port ranges opened for this endpoint; then resolve 948 // the CIDRs for the spaces specified in the expose details to 949 // construct the full source CIDR list for the generated rules. 950 srcCIDRs := set.NewStrings(exposeDetails.ExposeToCIDRs...) 951 for _, spaceID := range exposeDetails.ExposeToSpaces { 952 sp := fw.spaceInfos.GetByID(spaceID) 953 if sp == nil { 954 fw.logger.Warningf("exposed endpoint references unknown space ID %q", spaceID) 955 continue 956 } 957 958 if len(sp.Subnets) == 0 { 959 if exposedEndpoint == "" { 960 fw.logger.Warningf("all endpoints of application %q are exposed to space %q which contains no subnets", 961 unit.applicationd.application.Name(), sp.Name) 962 } else { 963 fw.logger.Warningf("endpoint %q application %q are exposed to space %q which contains no subnets", 964 exposedEndpoint, unit.applicationd.application.Name(), sp.Name) 965 } 966 } 967 for _, subnet := range sp.Subnets { 968 srcCIDRs.Add(subnet.CIDR) 969 } 970 } 971 972 if len(srcCIDRs) == 0 { 973 continue // no rules required 974 } 975 976 // If this is a named (i.e. not the wildcard) endpoint, look up 977 // the port ranges opened for *all* endpoints as well as for 978 // that endpoint name specifically, and create ingress rules. 979 if exposedEndpoint != "" { 980 for _, portRange := range openUnitPortRanges[exposedEndpoint] { // ports opened for this endpoint 981 rules = append(rules, firewall.NewIngressRule(portRange, srcCIDRs.Values()...)) 982 } 983 for _, portRange := range openUnitPortRanges[""] { // ports opened for ALL endpoints 984 rules = append(rules, firewall.NewIngressRule(portRange, srcCIDRs.Values()...)) 985 } 986 continue 987 } 988 989 // Create ingress rules for all endpoints except the ones that 990 // have their own dedicated entry in the exposed endpoints map. 991 for endpointName, portRanges := range openUnitPortRanges { 992 // This non-wildcard endpoint has an entry in the exposed 993 // endpoints list that override the global expose-all 994 // entry so we should skip it. 995 if _, hasExposeOverride := exposedEndpoints[endpointName]; hasExposeOverride && endpointName != "" { 996 continue 997 } 998 999 for _, portRange := range portRanges { 1000 rules = append(rules, firewall.NewIngressRule(portRange, srcCIDRs.Values()...)) 1001 } 1002 } 1003 } 1004 1005 return rules 1006 } 1007 1008 // TODO(wallyworld) - consider making this configurable. 1009 const maxAllowedCIDRS = 20 1010 1011 func (fw *Firewaller) updateForRemoteRelationIngress(appTag names.ApplicationTag) (set.Strings, error) { 1012 fw.logger.Debugf("finding egress rules for %v", appTag) 1013 // Now create the rules for any remote relations of which the 1014 // unit's application is a part. 1015 cidrs := make(set.Strings) 1016 for _, data := range fw.relationIngress { 1017 if data.localApplicationTag != appTag { 1018 continue 1019 } 1020 if !data.ingressRequired { 1021 continue 1022 } 1023 for _, cidr := range data.networks.Values() { 1024 cidrs.Add(cidr) 1025 } 1026 } 1027 // If we have too many CIDRs to create a rule for, consolidate. 1028 // If a firewall rule with a whitelist of CIDRs has been set up, 1029 // use that, else open to the world. 1030 if cidrs.Size() > maxAllowedCIDRS { 1031 // First, try and merge the cidrs. 1032 merged, err := cidrman.MergeCIDRs(cidrs.Values()) 1033 if err != nil { 1034 return nil, errors.Trace(err) 1035 } 1036 cidrs = set.NewStrings(merged...) 1037 } 1038 1039 // If there's still too many after merging, look for any firewall whitelist. 1040 if cidrs.Size() > maxAllowedCIDRS { 1041 cfg, err := fw.firewallerApi.ModelConfig() 1042 if err != nil { 1043 return nil, errors.Trace(err) 1044 } 1045 whitelistCidrs := cfg.SAASIngressAllow() 1046 cidrs = set.NewStrings(whitelistCidrs...) 1047 } 1048 1049 return cidrs, nil 1050 } 1051 1052 // flushGlobalPorts opens and closes global ports in the environment. 1053 // It keeps a reference count for ports so that only 0-to-1 and 1-to-0 events 1054 // modify the environment. 1055 func (fw *Firewaller) flushGlobalPorts(rawOpen, rawClose firewall.IngressRules) error { 1056 // Filter which ports are really to open or close. 1057 var toOpen, toClose firewall.IngressRules 1058 for _, rule := range rawOpen { 1059 ruleName := rule.String() 1060 if fw.globalIngressRuleRef[ruleName] == 0 { 1061 toOpen = append(toOpen, rule) 1062 } 1063 fw.globalIngressRuleRef[ruleName]++ 1064 } 1065 for _, rule := range rawClose { 1066 ruleName := rule.String() 1067 fw.globalIngressRuleRef[ruleName]-- 1068 if fw.globalIngressRuleRef[ruleName] == 0 { 1069 toClose = append(toClose, rule) 1070 delete(fw.globalIngressRuleRef, ruleName) 1071 } 1072 } 1073 ctx := stdcontext.Background() 1074 // Open and close the ports. 1075 if len(toOpen) > 0 { 1076 toOpen.Sort() 1077 fw.logger.Infof("opening port ranges %v in environment", toOpen) 1078 if err := fw.environFirewaller.OpenPorts(fw.cloudCallContextFunc(ctx), toOpen); err != nil { 1079 // TODO(mue) Add local retry logic. 1080 return errors.Annotatef(err, "failed to open port ranges %v in environment", toOpen) 1081 } 1082 } 1083 if len(toClose) > 0 { 1084 toClose.Sort() 1085 fw.logger.Infof("closing port ranges %v in environment", toClose) 1086 if err := fw.environFirewaller.ClosePorts(fw.cloudCallContextFunc(ctx), toClose); err != nil { 1087 // TODO(mue) Add local retry logic. 1088 return errors.Annotatef(err, "failed to close port ranges %v in environment", toOpen) 1089 } 1090 } 1091 return nil 1092 } 1093 1094 func (fw *Firewaller) flushModel() error { 1095 if fw.environModelFirewaller == nil { 1096 return nil 1097 } 1098 want, err := fw.firewallerApi.ModelFirewallRules() 1099 if err != nil { 1100 return errors.Trace(err) 1101 } 1102 1103 ctx := stdcontext.Background() 1104 curr, err := fw.environModelFirewaller.ModelIngressRules(fw.cloudCallContextFunc(ctx)) 1105 if err != nil { 1106 return errors.Trace(err) 1107 } 1108 1109 toOpen, toClose := curr.Diff(want) 1110 if len(toOpen) > 0 { 1111 toOpen.Sort() 1112 fw.logger.Infof("opening port ranges %v on model firewall", toOpen) 1113 if err := fw.environModelFirewaller.OpenModelPorts(fw.cloudCallContextFunc(ctx), toOpen); err != nil { 1114 // TODO(mue) Add local retry logic. 1115 return errors.Annotatef(err, "failed to open port ranges %v on model firewall", toOpen) 1116 } 1117 } 1118 if len(toClose) > 0 { 1119 toClose.Sort() 1120 fw.logger.Infof("closing port ranges %v on model firewall", toClose) 1121 if err := fw.environModelFirewaller.CloseModelPorts(fw.cloudCallContextFunc(ctx), toClose); err != nil { 1122 // TODO(mue) Add local retry logic. 1123 return errors.Annotatef(err, "failed to close port ranges %v on model firewall", toOpen) 1124 } 1125 } 1126 if fw.flushModelNotify != nil { 1127 fw.flushModelNotify() 1128 } 1129 return nil 1130 } 1131 1132 // flushInstancePorts opens and closes ports global on the machine. 1133 func (fw *Firewaller) flushInstancePorts(machined *machineData, toOpen, toClose firewall.IngressRules) (err error) { 1134 defer func() { 1135 if params.IsCodeNotFound(err) { 1136 err = nil 1137 } 1138 }() 1139 1140 // If there's nothing to do, do nothing. 1141 // This is important because when a machine is first created, 1142 // it will have no instance id but also no open ports - 1143 // InstanceId will fail but we don't care. 1144 fw.logger.Debugf("flush instance ports: to open %v, to close %v", toOpen, toClose) 1145 if len(toOpen) == 0 && len(toClose) == 0 { 1146 return nil 1147 } 1148 m, err := machined.machine() 1149 if err != nil { 1150 return err 1151 } 1152 machineId := machined.tag.Id() 1153 instanceId, err := m.InstanceId() 1154 if errors.IsNotProvisioned(err) { 1155 // Not provisioned yet, so nothing to do for this instance 1156 return nil 1157 } 1158 if err != nil { 1159 return err 1160 } 1161 ctx := stdcontext.Background() 1162 envInstances, err := fw.environInstances.Instances(fw.cloudCallContextFunc(ctx), []instance.Id{instanceId}) 1163 if err != nil { 1164 return err 1165 } 1166 fwInstance, ok := envInstances[0].(instances.InstanceFirewaller) 1167 if !ok { 1168 fw.logger.Infof("flushInstancePorts called on an instance of type %T which doesn't support firewall.", 1169 envInstances[0]) 1170 return nil 1171 } 1172 1173 // Open and close the ports. 1174 if len(toOpen) > 0 { 1175 toOpen.Sort() 1176 if err := fwInstance.OpenPorts(fw.cloudCallContextFunc(ctx), machineId, toOpen); err != nil { 1177 // TODO(mue) Add local retry logic. 1178 return err 1179 } 1180 fw.logger.Infof("opened port ranges %v on %q", toOpen, machined.tag) 1181 } 1182 if len(toClose) > 0 { 1183 toClose.Sort() 1184 if err := fwInstance.ClosePorts(fw.cloudCallContextFunc(ctx), machineId, toClose); err != nil { 1185 // TODO(mue) Add local retry logic. 1186 return err 1187 } 1188 fw.logger.Infof("closed port ranges %v on %q", toClose, machined.tag) 1189 } 1190 return nil 1191 } 1192 1193 // machineLifeChanged starts watching new machines when the firewaller 1194 // is starting, or when new machines come to life, and stops watching 1195 // machines that are dying. 1196 func (fw *Firewaller) machineLifeChanged(tag names.MachineTag) error { 1197 m, err := fw.firewallerApi.Machine(tag) 1198 found := !params.IsCodeNotFound(err) 1199 if found && err != nil { 1200 return err 1201 } 1202 dead := !found || m.Life() == life.Dead 1203 machined, known := fw.machineds[tag] 1204 if known && dead { 1205 return fw.forgetMachine(machined) 1206 } 1207 if !known && !dead { 1208 err := fw.startMachine(tag) 1209 if err != nil { 1210 return err 1211 } 1212 } 1213 return nil 1214 } 1215 1216 // forgetMachine cleans the machine data after the machine is removed. 1217 func (fw *Firewaller) forgetMachine(machined *machineData) error { 1218 for _, unitd := range machined.unitds { 1219 fw.forgetUnit(unitd) 1220 } 1221 if err := fw.flushMachine(machined); err != nil { 1222 return errors.Trace(err) 1223 } 1224 1225 // Unusually, it's fine to ignore this error, because we know the machined 1226 // is being tracked in fw.catacomb. But we do still want to wait until the 1227 // watch loop has stopped before we nuke the last data and return. 1228 _ = worker.Stop(machined) 1229 delete(fw.machineds, machined.tag) 1230 fw.logger.Debugf("stopped watching %q", machined.tag) 1231 return nil 1232 } 1233 1234 // forgetUnit cleans the unit data after the unit is removed. 1235 func (fw *Firewaller) forgetUnit(unitd *unitData) { 1236 applicationd := unitd.applicationd 1237 machined := unitd.machined 1238 1239 // If it's the last unit in the application, we'll need to stop the applicationd. 1240 stoppedApplication := false 1241 if len(applicationd.unitds) == 1 { 1242 if _, found := applicationd.unitds[unitd.tag]; found { 1243 // Unusually, it's fine to ignore this error, because we know the 1244 // applicationd is being tracked in fw.catacomb. But we do still want 1245 // to wait until the watch loop has stopped before we nuke the last 1246 // data and return. 1247 _ = worker.Stop(applicationd) 1248 stoppedApplication = true 1249 } 1250 } 1251 1252 // Clean up after stopping. 1253 delete(fw.unitds, unitd.tag) 1254 delete(machined.unitds, unitd.tag) 1255 delete(applicationd.unitds, unitd.tag) 1256 fw.logger.Debugf("stopped watching %q", unitd.tag) 1257 if stoppedApplication { 1258 applicationTag := applicationd.application.Tag() 1259 delete(fw.applicationids, applicationTag) 1260 fw.logger.Debugf("stopped watching %q", applicationTag) 1261 } 1262 } 1263 1264 // Kill is part of the worker.Worker interface. 1265 func (fw *Firewaller) Kill() { 1266 fw.catacomb.Kill(nil) 1267 } 1268 1269 // Wait is part of the worker.Worker interface. 1270 func (fw *Firewaller) Wait() error { 1271 return fw.catacomb.Wait() 1272 } 1273 1274 // unitsChange contains the changed units for one specific machine. 1275 type unitsChange struct { 1276 machined *machineData 1277 units []string 1278 } 1279 1280 // machineData holds machine details and watches units added or removed. 1281 type machineData struct { 1282 catacomb catacomb.Catacomb 1283 fw *Firewaller 1284 tag names.MachineTag 1285 unitds map[names.UnitTag]*unitData 1286 ingressRules firewall.IngressRules 1287 // ports defined by units on this machine 1288 openedPortRangesByEndpoint map[names.UnitTag]network.GroupedPortRanges 1289 } 1290 1291 func (md *machineData) machine() (*firewaller.Machine, error) { 1292 return md.fw.firewallerApi.Machine(md.tag) 1293 } 1294 1295 // watchLoop watches the machine for units added or removed. 1296 func (md *machineData) watchLoop(unitw watcher.StringsWatcher) error { 1297 if err := md.catacomb.Add(unitw); err != nil { 1298 return errors.Trace(err) 1299 } 1300 for { 1301 select { 1302 case <-md.catacomb.Dying(): 1303 return md.catacomb.ErrDying() 1304 case change, ok := <-unitw.Changes(): 1305 if !ok { 1306 return errors.New("machine units watcher closed") 1307 } 1308 select { 1309 case <-md.catacomb.Dying(): 1310 return md.catacomb.ErrDying() 1311 case md.fw.unitsChange <- &unitsChange{md, change}: 1312 } 1313 } 1314 } 1315 } 1316 1317 // Kill is part of the worker.Worker interface. 1318 func (md *machineData) Kill() { 1319 md.catacomb.Kill(nil) 1320 } 1321 1322 // Wait is part of the worker.Worker interface. 1323 func (md *machineData) Wait() error { 1324 return md.catacomb.Wait() 1325 } 1326 1327 // unitData holds unit details. 1328 type unitData struct { 1329 fw *Firewaller 1330 tag names.UnitTag 1331 unit *firewaller.Unit 1332 applicationd *applicationData 1333 machined *machineData 1334 } 1335 1336 // exposedChange contains the changed exposed flag for one specific application. 1337 type exposedChange struct { 1338 applicationd *applicationData 1339 exposed bool 1340 exposedEndpoints map[string]params.ExposedEndpoint 1341 } 1342 1343 // applicationData holds application details and watches exposure changes. 1344 type applicationData struct { 1345 catacomb catacomb.Catacomb 1346 fw *Firewaller 1347 application *firewaller.Application 1348 exposed bool 1349 exposedEndpoints map[string]params.ExposedEndpoint 1350 unitds map[names.UnitTag]*unitData 1351 } 1352 1353 // watchLoop watches the application's exposed flag for changes. 1354 func (ad *applicationData) watchLoop(curExposed bool, curExposedEndpoints map[string]params.ExposedEndpoint) error { 1355 appWatcher, err := ad.application.Watch() 1356 if err != nil { 1357 if params.IsCodeNotFound(err) { 1358 return nil 1359 } 1360 return errors.Trace(err) 1361 } 1362 if err := ad.catacomb.Add(appWatcher); err != nil { 1363 return errors.Trace(err) 1364 } 1365 for { 1366 select { 1367 case <-ad.catacomb.Dying(): 1368 return ad.catacomb.ErrDying() 1369 case _, ok := <-appWatcher.Changes(): 1370 if !ok { 1371 return errors.New("application watcher closed") 1372 } 1373 newExposed, newExposedEndpoints, err := ad.application.ExposeInfo() 1374 if err != nil { 1375 if errors.IsNotFound(err) { 1376 ad.fw.logger.Debugf("application(%q).IsExposed() returned NotFound: %v", ad.application.Name(), err) 1377 return nil 1378 } 1379 return errors.Trace(err) 1380 } 1381 if curExposed == newExposed && equalExposedEndpoints(curExposedEndpoints, newExposedEndpoints) { 1382 ad.fw.logger.Tracef("application(%q) expose settings unchanged: exposed: %v, exposedEndpoints: %v", 1383 ad.application.Name(), curExposed, curExposedEndpoints) 1384 continue 1385 } 1386 ad.fw.logger.Tracef("application(%q) expose settings changed: exposed: %v, exposedEndpoints: %v", 1387 ad.application.Name(), newExposed, newExposedEndpoints) 1388 1389 curExposed, curExposedEndpoints = newExposed, newExposedEndpoints 1390 select { 1391 case <-ad.catacomb.Dying(): 1392 return ad.catacomb.ErrDying() 1393 case ad.fw.exposedChange <- &exposedChange{ad, newExposed, newExposedEndpoints}: 1394 } 1395 } 1396 } 1397 } 1398 1399 func equalExposedEndpoints(a, b map[string]params.ExposedEndpoint) bool { 1400 if len(a) != len(b) { 1401 return false 1402 } 1403 1404 for endpoint, exposeDetailsA := range a { 1405 exposeDetailsB, found := b[endpoint] 1406 if !found { 1407 return false 1408 } 1409 1410 if !equalStringSlices(exposeDetailsA.ExposeToSpaces, exposeDetailsB.ExposeToSpaces) || 1411 !equalStringSlices(exposeDetailsA.ExposeToCIDRs, exposeDetailsB.ExposeToCIDRs) { 1412 return false 1413 } 1414 1415 } 1416 1417 return true 1418 } 1419 1420 func equalStringSlices(a, b []string) bool { 1421 if len(a) != len(b) { 1422 return false 1423 } 1424 1425 setA := set.NewStrings(a...) 1426 setB := set.NewStrings(b...) 1427 return setA.Difference(setB).IsEmpty() 1428 } 1429 1430 // Kill is part of the worker.Worker interface. 1431 func (ad *applicationData) Kill() { 1432 ad.catacomb.Kill(nil) 1433 } 1434 1435 // Wait is part of the worker.Worker interface. 1436 func (ad *applicationData) Wait() error { 1437 return ad.catacomb.Wait() 1438 } 1439 1440 // relationLifeChanged manages the workers to process ingress changes for 1441 // the specified relation. 1442 func (fw *Firewaller) relationLifeChanged(tag names.RelationTag) error { 1443 results, err := fw.remoteRelationsApi.Relations([]string{tag.Id()}) 1444 if err != nil { 1445 return errors.Trace(err) 1446 } 1447 relErr := results[0].Error 1448 notfound := relErr != nil && params.IsCodeNotFound(relErr) 1449 if relErr != nil && !notfound { 1450 return err 1451 } 1452 rel := results[0].Result 1453 1454 gone := notfound || rel.Life == life.Dead || rel.Suspended 1455 data, known := fw.relationIngress[tag] 1456 if known && gone { 1457 fw.logger.Debugf("relation %v was known but has died or been suspended", tag.Id()) 1458 // If relation is suspended, shut off ingress immediately. 1459 // Units will also eventually leave scope which would cause 1460 // ingress to be shut off, but best to do it up front. 1461 if rel != nil && rel.Suspended { 1462 change := &remoteRelationNetworkChange{ 1463 relationTag: tag, 1464 localApplicationTag: data.localApplicationTag, 1465 ingressRequired: false, 1466 } 1467 if err := fw.relationIngressChanged(change); err != nil { 1468 return errors.Trace(err) 1469 } 1470 } 1471 return fw.forgetRelation(data) 1472 } 1473 if !known && !gone { 1474 err := fw.startRelation(rel, rel.Endpoint.Role) 1475 if err != nil { 1476 return err 1477 } 1478 } 1479 return nil 1480 } 1481 1482 type remoteRelationInfo struct { 1483 relationToken string 1484 } 1485 1486 type remoteRelationData struct { 1487 catacomb catacomb.Catacomb 1488 fw *Firewaller 1489 relationReady chan remoteRelationInfo 1490 1491 tag names.RelationTag 1492 localApplicationTag names.ApplicationTag 1493 relationToken string 1494 remoteModelUUID string 1495 endpointRole charm.RelationRole 1496 isOffer bool 1497 1498 crossModelFirewallerFacade CrossModelFirewallerFacadeCloser 1499 1500 // These values are updated when ingress information on the 1501 // relation changes in the model. 1502 ingressRequired bool 1503 networks set.Strings 1504 } 1505 1506 // startRelation creates a new data value for tracking details of the 1507 // relation and starts watching the related models for subnets added or removed. 1508 func (fw *Firewaller) startRelation(rel *params.RemoteRelation, role charm.RelationRole) error { 1509 remoteApps, err := fw.remoteRelationsApi.RemoteApplications([]string{rel.RemoteApplicationName}) 1510 if err != nil { 1511 return errors.Trace(err) 1512 } 1513 remoteAppResult := remoteApps[0] 1514 if remoteAppResult.Error != nil { 1515 return errors.Trace(err) 1516 } 1517 1518 tag := names.NewRelationTag(rel.Key) 1519 data := &remoteRelationData{ 1520 fw: fw, 1521 tag: tag, 1522 remoteModelUUID: rel.SourceModelUUID, 1523 localApplicationTag: names.NewApplicationTag(rel.ApplicationName), 1524 endpointRole: role, 1525 relationReady: make(chan remoteRelationInfo), 1526 } 1527 1528 // Start the worker which will watch the remote relation for things like new networks. 1529 // We use ReplaceWorker since the relation may have been removed and we are re-adding it. 1530 if err := fw.relationWorkerRunner.StartWorker(tag.Id(), func() (worker.Worker, error) { 1531 // This may be a restart after an api error, so ensure any previous 1532 // worker is killed and the catacomb is reset. 1533 data.Kill() 1534 data.catacomb = catacomb.Catacomb{} 1535 if err := catacomb.Invoke(catacomb.Plan{ 1536 Site: &data.catacomb, 1537 Work: data.watchLoop, 1538 }); err != nil { 1539 return nil, errors.Trace(err) 1540 } 1541 return data, nil 1542 }); err != nil { 1543 return errors.Annotate(err, "error starting remote relation worker") 1544 } 1545 fw.relationIngress[tag] = data 1546 1547 data.isOffer = remoteAppResult.Result.IsConsumerProxy 1548 return fw.startRelationPoller(rel.Key, rel.RemoteApplicationName, data.relationReady) 1549 } 1550 1551 // watchLoop watches the relation for networks added or removed. 1552 func (rd *remoteRelationData) watchLoop() error { 1553 defer func() { 1554 if rd.crossModelFirewallerFacade != nil { 1555 rd.crossModelFirewallerFacade.Close() 1556 } 1557 }() 1558 1559 // First, wait for relation to become ready. 1560 for rd.relationToken == "" { 1561 select { 1562 case <-rd.catacomb.Dying(): 1563 return rd.catacomb.ErrDying() 1564 case remoteRelationInfo := <-rd.relationReady: 1565 rd.relationToken = remoteRelationInfo.relationToken 1566 rd.fw.logger.Debugf( 1567 "relation %v in model %v is ready", 1568 rd.relationToken, rd.remoteModelUUID) 1569 } 1570 } 1571 1572 if rd.endpointRole == charm.RoleRequirer { 1573 return rd.requirerEndpointLoop() 1574 } 1575 return rd.providerEndpointLoop() 1576 } 1577 1578 func (rd *remoteRelationData) requirerEndpointLoop() error { 1579 // If the requirer end of the relation is on the offering model, 1580 // there's nothing to do here because the provider end on the 1581 // consuming model will be watching for changes. 1582 // TODO(wallyworld) - this will change if we want to allow bidirectional traffic. 1583 if rd.isOffer { 1584 return nil 1585 } 1586 1587 rd.fw.logger.Debugf("starting requirer endpoint loop for %v on %v ", rd.tag.Id(), rd.localApplicationTag.Id()) 1588 // Now watch for updates to egress addresses so we can inform the offering 1589 // model what firewall ingress to allow. 1590 egressAddressWatcher, err := rd.fw.firewallerApi.WatchEgressAddressesForRelation(rd.tag) 1591 if err != nil { 1592 if !params.IsCodeNotFound(err) && !params.IsCodeNotSupported(err) { 1593 return errors.Trace(err) 1594 } 1595 rd.fw.logger.Infof("no egress required for %v", rd.localApplicationTag) 1596 rd.ingressRequired = false 1597 return nil 1598 } 1599 if err := rd.catacomb.Add(egressAddressWatcher); err != nil { 1600 return errors.Trace(err) 1601 } 1602 for { 1603 select { 1604 case <-rd.catacomb.Dying(): 1605 return rd.catacomb.ErrDying() 1606 case cidrs := <-egressAddressWatcher.Changes(): 1607 rd.fw.logger.Debugf("relation egress addresses for %v changed in model %v: %v", rd.tag, rd.fw.modelUUID, 1608 cidrs) 1609 if err := rd.updateProviderModel(cidrs); err != nil { 1610 return errors.Trace(err) 1611 } 1612 } 1613 } 1614 } 1615 1616 func (rd *remoteRelationData) providerEndpointLoop() error { 1617 rd.fw.logger.Debugf("starting provider endpoint loop for %v on %v ", rd.tag.Id(), rd.localApplicationTag.Id()) 1618 // Watch for ingress changes requested by the consuming model. 1619 ingressAddressWatcher, err := rd.ingressAddressWatcher() 1620 if err != nil { 1621 if !params.IsCodeNotFound(err) && !params.IsCodeNotSupported(err) { 1622 return errors.Trace(err) 1623 } 1624 rd.fw.logger.Infof("no ingress required for %v", rd.localApplicationTag) 1625 rd.ingressRequired = false 1626 return nil 1627 } 1628 if err := rd.catacomb.Add(ingressAddressWatcher); err != nil { 1629 return errors.Trace(err) 1630 } 1631 for { 1632 select { 1633 case <-rd.catacomb.Dying(): 1634 return rd.catacomb.ErrDying() 1635 case cidrs := <-ingressAddressWatcher.Changes(): 1636 rd.fw.logger.Debugf("relation ingress addresses for %v changed in model %v: %v", rd.tag, rd.fw.modelUUID, 1637 cidrs) 1638 if err := rd.updateIngressNetworks(cidrs); err != nil { 1639 return errors.Trace(err) 1640 } 1641 } 1642 } 1643 } 1644 1645 func (rd *remoteRelationData) ingressAddressWatcher() (watcher.StringsWatcher, error) { 1646 if rd.isOffer { 1647 // On the offering side we watch the local model for ingress changes 1648 // which will have been published from the consuming model. 1649 return rd.fw.firewallerApi.WatchIngressAddressesForRelation(rd.tag) 1650 } else { 1651 // On the consuming side, if this is the provider end of the relation, 1652 // we watch the remote model's egress changes to get our ingress changes. 1653 apiInfo, err := rd.fw.firewallerApi.ControllerAPIInfoForModel(rd.remoteModelUUID) 1654 if err != nil { 1655 return nil, errors.Annotatef(err, "cannot get api info for model %v", rd.remoteModelUUID) 1656 } 1657 rd.crossModelFirewallerFacade, err = rd.fw.newRemoteFirewallerAPIFunc(apiInfo) 1658 if err != nil { 1659 return nil, errors.Annotate(err, "cannot open facade to remote model to watch ingress addresses") 1660 } 1661 1662 mac, err := rd.fw.firewallerApi.MacaroonForRelation(rd.tag.Id()) 1663 if err != nil { 1664 return nil, errors.Annotatef(err, "cannot get macaroon for %v", rd.tag.Id()) 1665 } 1666 arg := params.RemoteEntityArg{ 1667 Token: rd.relationToken, 1668 Macaroons: macaroon.Slice{mac}, 1669 } 1670 return rd.crossModelFirewallerFacade.WatchEgressAddressesForRelation(arg) 1671 } 1672 } 1673 1674 type remoteRelationNetworkChange struct { 1675 relationTag names.RelationTag 1676 localApplicationTag names.ApplicationTag 1677 networks set.Strings 1678 ingressRequired bool 1679 } 1680 1681 // updateProviderModel gathers the ingress CIDRs for the relation and notifies 1682 // that a change has occurred. 1683 func (rd *remoteRelationData) updateProviderModel(cidrs []string) error { 1684 rd.fw.logger.Debugf("ingress cidrs for %v: %+v", rd.tag, cidrs) 1685 change := &remoteRelationNetworkChange{ 1686 relationTag: rd.tag, 1687 localApplicationTag: rd.localApplicationTag, 1688 networks: set.NewStrings(cidrs...), 1689 ingressRequired: len(cidrs) > 0, 1690 } 1691 1692 apiInfo, err := rd.fw.firewallerApi.ControllerAPIInfoForModel(rd.remoteModelUUID) 1693 if err != nil { 1694 return errors.Annotatef(err, "cannot get api info for model %v", rd.remoteModelUUID) 1695 } 1696 mac, err := rd.fw.firewallerApi.MacaroonForRelation(rd.tag.Id()) 1697 if params.IsCodeNotFound(err) { 1698 // Relation has gone, nothing to do. 1699 return nil 1700 } 1701 if err != nil { 1702 return errors.Annotatef(err, "cannot get macaroon for %v", rd.tag.Id()) 1703 } 1704 remoteModelAPI, err := rd.fw.newRemoteFirewallerAPIFunc(apiInfo) 1705 if err != nil { 1706 return errors.Annotate(err, "cannot open facade to remote model to publish network change") 1707 } 1708 defer remoteModelAPI.Close() 1709 event := params.IngressNetworksChangeEvent{ 1710 RelationToken: rd.relationToken, 1711 Networks: change.networks.Values(), 1712 IngressRequired: change.ingressRequired, 1713 Macaroons: macaroon.Slice{mac}, 1714 BakeryVersion: bakery.LatestVersion, 1715 } 1716 err = remoteModelAPI.PublishIngressNetworkChange(event) 1717 if errors.IsNotFound(err) { 1718 rd.fw.logger.Debugf("relation id not found publishing %+v", event) 1719 return nil 1720 } 1721 1722 // If the requested ingress is not permitted on the offering side, 1723 // mark the relation as in error. It's not an error that requires a 1724 // worker restart though. 1725 if params.IsCodeForbidden(err) { 1726 return rd.fw.firewallerApi.SetRelationStatus(rd.tag.Id(), relation.Error, err.Error()) 1727 } 1728 return errors.Annotate(err, "cannot publish ingress network change") 1729 } 1730 1731 // updateIngressNetworks processes the changed ingress networks on the relation. 1732 func (rd *remoteRelationData) updateIngressNetworks(cidrs []string) error { 1733 rd.fw.logger.Debugf("ingress cidrs for %v: %+v", rd.tag, cidrs) 1734 change := &remoteRelationNetworkChange{ 1735 relationTag: rd.tag, 1736 localApplicationTag: rd.localApplicationTag, 1737 networks: set.NewStrings(cidrs...), 1738 ingressRequired: len(cidrs) > 0, 1739 } 1740 select { 1741 case <-rd.catacomb.Dying(): 1742 return rd.catacomb.ErrDying() 1743 case rd.fw.localRelationsChange <- change: 1744 } 1745 return nil 1746 } 1747 1748 // Kill is part of the worker.Worker interface. 1749 func (rd *remoteRelationData) Kill() { 1750 rd.catacomb.Kill(nil) 1751 } 1752 1753 // Wait is part of the worker.Worker interface. 1754 func (rd *remoteRelationData) Wait() error { 1755 return rd.catacomb.Wait() 1756 } 1757 1758 // forgetRelation cleans the relation data after the relation is removed. 1759 func (fw *Firewaller) forgetRelation(data *remoteRelationData) error { 1760 fw.logger.Debugf("forget relation %v", data.tag.Id()) 1761 delete(fw.relationIngress, data.tag) 1762 // There's not much we can do if there's an error stopping the remote 1763 // relation worker, so just log it. 1764 if err := fw.relationWorkerRunner.StopAndRemoveWorker(data.tag.Id(), fw.catacomb.Dying()); err != nil { 1765 fw.logger.Errorf("error stopping remote relation worker for %s: %v", data.tag, err) 1766 } 1767 fw.logger.Debugf("stopped watching %q", data.tag) 1768 return nil 1769 } 1770 1771 type remoteRelationPoller struct { 1772 catacomb catacomb.Catacomb 1773 fw *Firewaller 1774 relationTag names.RelationTag 1775 applicationTag names.ApplicationTag 1776 relationReady chan remoteRelationInfo 1777 } 1778 1779 // startRelationPoller creates a new worker which waits until a remote 1780 // relation is registered in both models. 1781 func (fw *Firewaller) startRelationPoller(relationKey, remoteAppName string, 1782 relationReady chan remoteRelationInfo) error { 1783 poller := &remoteRelationPoller{ 1784 fw: fw, 1785 relationTag: names.NewRelationTag(relationKey), 1786 applicationTag: names.NewApplicationTag(remoteAppName), 1787 relationReady: relationReady, 1788 } 1789 1790 err := catacomb.Invoke(catacomb.Plan{ 1791 Site: &poller.catacomb, 1792 Work: poller.pollLoop, 1793 }) 1794 if err != nil { 1795 return errors.Trace(err) 1796 } 1797 1798 // register poller with the firewaller's catacomb. 1799 return fw.catacomb.Add(poller) 1800 } 1801 1802 // pollLoop waits for a remote relation to be registered. 1803 // It does this by waiting for the relation and app tokens to be created. 1804 func (p *remoteRelationPoller) pollLoop() error { 1805 p.fw.logger.Debugf("polling for relation %v on %v to be ready", p.relationTag, p.applicationTag) 1806 for { 1807 select { 1808 case <-p.catacomb.Dying(): 1809 return p.catacomb.ErrDying() 1810 case <-p.fw.clk.After(3 * time.Second): 1811 // Relation is exported with the consuming model UUID. 1812 relToken, err := p.fw.remoteRelationsApi.GetToken(p.relationTag) 1813 if err != nil { 1814 continue 1815 } 1816 p.fw.logger.Debugf("token %v for relation id: %v in model %v", relToken, p.relationTag.Id(), p.fw.modelUUID) 1817 relationInfo := remoteRelationInfo{ 1818 relationToken: relToken, 1819 } 1820 select { 1821 case <-p.catacomb.Dying(): 1822 return p.catacomb.ErrDying() 1823 case p.relationReady <- relationInfo: 1824 } 1825 return nil 1826 } 1827 } 1828 } 1829 1830 // Kill is part of the worker.Worker interface. 1831 func (p *remoteRelationPoller) Kill() { 1832 p.catacomb.Kill(nil) 1833 } 1834 1835 // Wait is part of the worker.Worker interface. 1836 func (p *remoteRelationPoller) Wait() error { 1837 return p.catacomb.Wait() 1838 }