github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/controller/firewaller/firewaller.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package firewaller
     5  
     6  import (
     7  	"sort"
     8  	"strconv"
     9  
    10  	"github.com/juju/collections/set"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/names/v5"
    14  
    15  	"github.com/juju/juju/apiserver/common"
    16  	"github.com/juju/juju/apiserver/common/cloudspec"
    17  	"github.com/juju/juju/apiserver/common/firewall"
    18  	apiservererrors "github.com/juju/juju/apiserver/errors"
    19  	"github.com/juju/juju/apiserver/facade"
    20  	"github.com/juju/juju/core/network"
    21  	"github.com/juju/juju/core/status"
    22  	"github.com/juju/juju/rpc/params"
    23  	"github.com/juju/juju/state"
    24  	"github.com/juju/juju/state/watcher"
    25  )
    26  
    27  var logger = loggo.GetLogger("juju.apiserver.firewaller")
    28  
    29  // FirewallerAPI provides access to the Firewaller API facade.
    30  type FirewallerAPI struct {
    31  	*common.LifeGetter
    32  	*common.ModelWatcher
    33  	*common.AgentEntityWatcher
    34  	*common.UnitsWatcher
    35  	*common.ModelMachinesWatcher
    36  	*common.InstanceIdGetter
    37  	ControllerConfigAPI
    38  	cloudspec.CloudSpecer
    39  
    40  	st                State
    41  	resources         facade.Resources
    42  	authorizer        facade.Authorizer
    43  	accessUnit        common.GetAuthFunc
    44  	accessApplication common.GetAuthFunc
    45  	accessMachine     common.GetAuthFunc
    46  	accessModel       common.GetAuthFunc
    47  
    48  	// Fetched on demand and memoized
    49  	spaceInfos          network.SpaceInfos
    50  	appEndpointBindings map[string]map[string]string
    51  }
    52  
    53  // NewStateFirewallerAPI creates a new server-side FirewallerAPIV7 facade.
    54  func NewStateFirewallerAPI(
    55  	st State,
    56  	resources facade.Resources,
    57  	authorizer facade.Authorizer,
    58  	cloudSpecAPI cloudspec.CloudSpecer,
    59  	controllerConfigAPI ControllerConfigAPI,
    60  ) (*FirewallerAPI, error) {
    61  	if !authorizer.AuthController() {
    62  		// Firewaller must run as a controller.
    63  		return nil, apiservererrors.ErrPerm
    64  	}
    65  	// Set up the various authorization checkers.
    66  	accessModel := common.AuthFuncForTagKind(names.ModelTagKind)
    67  	accessUnit := common.AuthFuncForTagKind(names.UnitTagKind)
    68  	accessApplication := common.AuthFuncForTagKind(names.ApplicationTagKind)
    69  	accessMachine := common.AuthFuncForTagKind(names.MachineTagKind)
    70  	accessRelation := common.AuthFuncForTagKind(names.RelationTagKind)
    71  	accessUnitApplicationOrMachineOrRelation := common.AuthAny(accessUnit, accessApplication, accessMachine, accessRelation)
    72  
    73  	// Life() is supported for units, applications or machines.
    74  	lifeGetter := common.NewLifeGetter(
    75  		st,
    76  		accessUnitApplicationOrMachineOrRelation,
    77  	)
    78  	// ModelConfig() and WatchForModelConfigChanges() are allowed
    79  	// with unrestricted access.
    80  	modelWatcher := common.NewModelWatcher(
    81  		st,
    82  		resources,
    83  		authorizer,
    84  	)
    85  	// Watch() is supported for applications only.
    86  	entityWatcher := common.NewAgentEntityWatcher(
    87  		st,
    88  		resources,
    89  		accessApplication,
    90  	)
    91  	// WatchUnits() is supported for machines.
    92  	unitsWatcher := common.NewUnitsWatcher(st,
    93  		resources,
    94  		accessMachine,
    95  	)
    96  	// WatchModelMachines() is allowed with unrestricted access.
    97  	machinesWatcher := common.NewModelMachinesWatcher(
    98  		st,
    99  		resources,
   100  		authorizer,
   101  	)
   102  	// InstanceId() is supported for machines.
   103  	instanceIdGetter := common.NewInstanceIdGetter(
   104  		st,
   105  		accessMachine,
   106  	)
   107  
   108  	return &FirewallerAPI{
   109  		LifeGetter:           lifeGetter,
   110  		ModelWatcher:         modelWatcher,
   111  		AgentEntityWatcher:   entityWatcher,
   112  		UnitsWatcher:         unitsWatcher,
   113  		ModelMachinesWatcher: machinesWatcher,
   114  		InstanceIdGetter:     instanceIdGetter,
   115  		CloudSpecer:          cloudSpecAPI,
   116  		ControllerConfigAPI:  controllerConfigAPI,
   117  		st:                   st,
   118  		resources:            resources,
   119  		authorizer:           authorizer,
   120  		accessUnit:           accessUnit,
   121  		accessApplication:    accessApplication,
   122  		accessMachine:        accessMachine,
   123  		accessModel:          accessModel,
   124  	}, nil
   125  }
   126  
   127  // WatchOpenedPorts returns a new StringsWatcher for each given
   128  // model tag.
   129  func (f *FirewallerAPI) WatchOpenedPorts(args params.Entities) (params.StringsWatchResults, error) {
   130  	result := params.StringsWatchResults{
   131  		Results: make([]params.StringsWatchResult, len(args.Entities)),
   132  	}
   133  	if len(args.Entities) == 0 {
   134  		return result, nil
   135  	}
   136  	canWatch, err := f.accessModel()
   137  	if err != nil {
   138  		return params.StringsWatchResults{}, errors.Trace(err)
   139  	}
   140  	for i, entity := range args.Entities {
   141  		tag, err := names.ParseTag(entity.Tag)
   142  		if err != nil {
   143  			result.Results[i].Error = apiservererrors.ServerError(apiservererrors.ErrPerm)
   144  			continue
   145  		}
   146  		if !canWatch(tag) {
   147  			result.Results[i].Error = apiservererrors.ServerError(apiservererrors.ErrPerm)
   148  			continue
   149  		}
   150  		watcherId, initial, err := f.watchOneModelOpenedPorts(tag)
   151  		if err != nil {
   152  			result.Results[i].Error = apiservererrors.ServerError(err)
   153  			continue
   154  		}
   155  		result.Results[i].StringsWatcherId = watcherId
   156  		result.Results[i].Changes = initial
   157  	}
   158  	return result, nil
   159  }
   160  
   161  func (f *FirewallerAPI) watchOneModelOpenedPorts(tag names.Tag) (string, []string, error) {
   162  	// NOTE: tag is ignored, as there is only one model in the
   163  	// state DB. Once this changes, change the code below accordingly.
   164  	watch := f.st.WatchOpenedPorts()
   165  	// Consume the initial event and forward it to the result.
   166  	if changes, ok := <-watch.Changes(); ok {
   167  		return f.resources.Register(watch), changes, nil
   168  	}
   169  	return "", nil, watcher.EnsureErr(watch)
   170  }
   171  
   172  // ModelFirewallRules returns the firewall rules that this model is
   173  // configured to open
   174  func (f *FirewallerAPI) ModelFirewallRules() (params.IngressRulesResult, error) {
   175  	cfg, err := f.st.ModelConfig()
   176  	if err != nil {
   177  		return params.IngressRulesResult{Error: apiservererrors.ServerError(err)}, nil
   178  	}
   179  	ctrlCfg, err := f.st.ControllerConfig()
   180  	if err != nil {
   181  		return params.IngressRulesResult{Error: apiservererrors.ServerError(err)}, nil
   182  	}
   183  	isController := f.st.IsController()
   184  
   185  	var rules []params.IngressRule
   186  	sshAllow := cfg.SSHAllow()
   187  	if len(sshAllow) != 0 {
   188  		portRange := params.FromNetworkPortRange(network.MustParsePortRange("22"))
   189  		rules = append(rules, params.IngressRule{PortRange: portRange, SourceCIDRs: sshAllow})
   190  	}
   191  	if isController {
   192  		portRange := params.FromNetworkPortRange(network.MustParsePortRange(strconv.Itoa(ctrlCfg.APIPort())))
   193  		rules = append(rules, params.IngressRule{PortRange: portRange, SourceCIDRs: []string{"0.0.0.0/0", "::/0"}})
   194  	}
   195  	if isController && ctrlCfg.AutocertDNSName() != "" {
   196  		portRange := params.FromNetworkPortRange(network.MustParsePortRange("80"))
   197  		rules = append(rules, params.IngressRule{PortRange: portRange, SourceCIDRs: []string{"0.0.0.0/0", "::/0"}})
   198  	}
   199  	return params.IngressRulesResult{
   200  		Rules: rules,
   201  	}, nil
   202  }
   203  
   204  // WatchModelFirewallRules returns a NotifyWatcher that notifies of
   205  // potential changes to a model's configured firewall rules
   206  func (f *FirewallerAPI) WatchModelFirewallRules() (params.NotifyWatchResult, error) {
   207  	watch, err := NewModelFirewallRulesWatcher(f.st)
   208  	if err != nil {
   209  		return params.NotifyWatchResult{Error: apiservererrors.ServerError(err)}, nil
   210  	}
   211  
   212  	if _, ok := <-watch.Changes(); ok {
   213  		return params.NotifyWatchResult{NotifyWatcherId: f.resources.Register(watch)}, nil
   214  	} else {
   215  		err := watcher.EnsureErr(watch)
   216  		return params.NotifyWatchResult{Error: apiservererrors.ServerError(err)}, nil
   217  	}
   218  }
   219  
   220  // GetAssignedMachine returns the assigned machine tag (if any) for
   221  // each given unit.
   222  func (f *FirewallerAPI) GetAssignedMachine(args params.Entities) (params.StringResults, error) {
   223  	result := params.StringResults{
   224  		Results: make([]params.StringResult, len(args.Entities)),
   225  	}
   226  	canAccess, err := f.accessUnit()
   227  	if err != nil {
   228  		return params.StringResults{}, err
   229  	}
   230  	for i, entity := range args.Entities {
   231  		tag, err := names.ParseUnitTag(entity.Tag)
   232  		if err != nil {
   233  			result.Results[i].Error = apiservererrors.ServerError(apiservererrors.ErrPerm)
   234  			continue
   235  		}
   236  		unit, err := f.getUnit(canAccess, tag)
   237  		if err == nil {
   238  			var machineId string
   239  			machineId, err = unit.AssignedMachineId()
   240  			if err == nil {
   241  				result.Results[i].Result = names.NewMachineTag(machineId).String()
   242  			}
   243  		}
   244  		result.Results[i].Error = apiservererrors.ServerError(err)
   245  	}
   246  	return result, nil
   247  }
   248  
   249  // getSpaceInfos returns the cached SpaceInfos or retrieves them from state
   250  // and memoizes it for future invocations.
   251  func (f *FirewallerAPI) getSpaceInfos() (network.SpaceInfos, error) {
   252  	if f.spaceInfos != nil {
   253  		return f.spaceInfos, nil
   254  	}
   255  
   256  	si, err := f.st.SpaceInfos()
   257  	if err != nil {
   258  		return nil, errors.Trace(err)
   259  	}
   260  
   261  	return si, nil
   262  }
   263  
   264  // getApplicationBindings returns the cached endpoint bindings for all model
   265  // applications grouped by app name. If the application endpoints have not yet
   266  // been retrieved they will be retrieved and memoized for future calls.
   267  func (f *FirewallerAPI) getApplicationBindings() (map[string]map[string]string, error) {
   268  	if f.appEndpointBindings == nil {
   269  		bindings, err := f.st.AllEndpointBindings()
   270  		if err != nil {
   271  			return nil, errors.Trace(err)
   272  		}
   273  		f.appEndpointBindings = bindings
   274  	}
   275  
   276  	return f.appEndpointBindings, nil
   277  }
   278  
   279  func (f *FirewallerAPI) getEntity(canAccess common.AuthFunc, tag names.Tag) (state.Entity, error) {
   280  	if !canAccess(tag) {
   281  		return nil, apiservererrors.ErrPerm
   282  	}
   283  	return f.st.FindEntity(tag)
   284  }
   285  
   286  func (f *FirewallerAPI) getUnit(canAccess common.AuthFunc, tag names.UnitTag) (*state.Unit, error) {
   287  	entity, err := f.getEntity(canAccess, tag)
   288  	if err != nil {
   289  		return nil, err
   290  	}
   291  	// The authorization function guarantees that the tag represents a
   292  	// unit.
   293  	return entity.(*state.Unit), nil
   294  }
   295  
   296  func (f *FirewallerAPI) getApplication(canAccess common.AuthFunc, tag names.ApplicationTag) (*state.Application, error) {
   297  	entity, err := f.getEntity(canAccess, tag)
   298  	if err != nil {
   299  		return nil, err
   300  	}
   301  	// The authorization function guarantees that the tag represents a
   302  	// application.
   303  	return entity.(*state.Application), nil
   304  }
   305  
   306  func (f *FirewallerAPI) getMachine(canAccess common.AuthFunc, tag names.MachineTag) (firewall.Machine, error) {
   307  	if !canAccess(tag) {
   308  		return nil, apiservererrors.ErrPerm
   309  	}
   310  	return f.st.Machine(tag.Id())
   311  }
   312  
   313  // WatchEgressAddressesForRelations creates a watcher that notifies when addresses, from which
   314  // connections will originate for the relation, change.
   315  // Each event contains the entire set of addresses which are required for ingress for the relation.
   316  func (f *FirewallerAPI) WatchEgressAddressesForRelations(relations params.Entities) (params.StringsWatchResults, error) {
   317  	return firewall.WatchEgressAddressesForRelations(f.resources, f.st, relations)
   318  }
   319  
   320  // WatchIngressAddressesForRelations creates a watcher that returns the ingress networks
   321  // that have been recorded against the specified relations.
   322  func (f *FirewallerAPI) WatchIngressAddressesForRelations(relations params.Entities) (params.StringsWatchResults, error) {
   323  	results := params.StringsWatchResults{
   324  		make([]params.StringsWatchResult, len(relations.Entities)),
   325  	}
   326  
   327  	one := func(tag string) (id string, changes []string, _ error) {
   328  		logger.Debugf("Watching ingress addresses for %+v from model %v", tag, f.st.ModelUUID())
   329  
   330  		relationTag, err := names.ParseRelationTag(tag)
   331  		if err != nil {
   332  			return "", nil, errors.Trace(err)
   333  		}
   334  		rel, err := f.st.KeyRelation(relationTag.Id())
   335  		if err != nil {
   336  			return "", nil, errors.Trace(err)
   337  		}
   338  		w := rel.WatchRelationIngressNetworks()
   339  		changes, ok := <-w.Changes()
   340  		if !ok {
   341  			return "", nil, apiservererrors.ServerError(watcher.EnsureErr(w))
   342  		}
   343  		return f.resources.Register(w), changes, nil
   344  	}
   345  
   346  	for i, e := range relations.Entities {
   347  		watcherId, changes, err := one(e.Tag)
   348  		if err != nil {
   349  			results.Results[i].Error = apiservererrors.ServerError(err)
   350  			continue
   351  		}
   352  		results.Results[i].StringsWatcherId = watcherId
   353  		results.Results[i].Changes = changes
   354  	}
   355  	return results, nil
   356  }
   357  
   358  // MacaroonForRelations returns the macaroon for the specified relations.
   359  func (f *FirewallerAPI) MacaroonForRelations(args params.Entities) (params.MacaroonResults, error) {
   360  	var result params.MacaroonResults
   361  	result.Results = make([]params.MacaroonResult, len(args.Entities))
   362  	for i, entity := range args.Entities {
   363  		relationTag, err := names.ParseRelationTag(entity.Tag)
   364  		if err != nil {
   365  			result.Results[i].Error = apiservererrors.ServerError(err)
   366  			continue
   367  		}
   368  		mac, err := f.st.GetMacaroon(relationTag)
   369  		if err != nil {
   370  			result.Results[i].Error = apiservererrors.ServerError(err)
   371  			continue
   372  		}
   373  		result.Results[i].Result = mac
   374  	}
   375  	return result, nil
   376  }
   377  
   378  // SetRelationsStatus sets the status for the specified relations.
   379  func (f *FirewallerAPI) SetRelationsStatus(args params.SetStatus) (params.ErrorResults, error) {
   380  	var result params.ErrorResults
   381  	result.Results = make([]params.ErrorResult, len(args.Entities))
   382  	for i, entity := range args.Entities {
   383  		relationTag, err := names.ParseRelationTag(entity.Tag)
   384  		if err != nil {
   385  			result.Results[i].Error = apiservererrors.ServerError(err)
   386  			continue
   387  		}
   388  		rel, err := f.st.KeyRelation(relationTag.Id())
   389  		if err != nil {
   390  			result.Results[i].Error = apiservererrors.ServerError(err)
   391  			continue
   392  		}
   393  		err = rel.SetStatus(status.StatusInfo{
   394  			Status:  status.Status(entity.Status),
   395  			Message: entity.Info,
   396  		})
   397  		result.Results[i].Error = apiservererrors.ServerError(err)
   398  	}
   399  	return result, nil
   400  }
   401  
   402  // AreManuallyProvisioned returns whether each given entity is
   403  // manually provisioned or not. Only machine tags are accepted.
   404  func (f *FirewallerAPI) AreManuallyProvisioned(args params.Entities) (params.BoolResults, error) {
   405  	result := params.BoolResults{
   406  		Results: make([]params.BoolResult, len(args.Entities)),
   407  	}
   408  	canAccess, err := f.accessMachine()
   409  	if err != nil {
   410  		return result, err
   411  	}
   412  	for i, arg := range args.Entities {
   413  		machineTag, err := names.ParseMachineTag(arg.Tag)
   414  		if err != nil {
   415  			result.Results[i].Error = apiservererrors.ServerError(err)
   416  			continue
   417  		}
   418  		machine, err := f.getMachine(canAccess, machineTag)
   419  		if err == nil {
   420  			result.Results[i].Result, err = machine.IsManual()
   421  		}
   422  		result.Results[i].Error = apiservererrors.ServerError(err)
   423  	}
   424  	return result, nil
   425  }
   426  
   427  // OpenedMachinePortRanges returns a list of the opened port ranges for the
   428  // specified machines where each result is broken down by unit. The list of
   429  // opened ports for each unit is further grouped by endpoint name and includes
   430  // the subnet CIDRs that belong to the space that each endpoint is bound to.
   431  func (f *FirewallerAPI) OpenedMachinePortRanges(args params.Entities) (params.OpenMachinePortRangesResults, error) {
   432  	result := params.OpenMachinePortRangesResults{
   433  		Results: make([]params.OpenMachinePortRangesResult, len(args.Entities)),
   434  	}
   435  	canAccess, err := f.accessMachine()
   436  	if err != nil {
   437  		return result, err
   438  	}
   439  
   440  	for i, arg := range args.Entities {
   441  		machineTag, err := names.ParseMachineTag(arg.Tag)
   442  		if err != nil {
   443  			result.Results[i].Error = apiservererrors.ServerError(err)
   444  			continue
   445  		}
   446  
   447  		machine, err := f.getMachine(canAccess, machineTag)
   448  		if err != nil {
   449  			result.Results[i].Error = apiservererrors.ServerError(err)
   450  			continue
   451  		}
   452  
   453  		unitPortRanges, err := f.openedPortRangesForOneMachine(machine)
   454  		if err != nil {
   455  			result.Results[i].Error = apiservererrors.ServerError(err)
   456  			continue
   457  
   458  		}
   459  		result.Results[i].UnitPortRanges = unitPortRanges
   460  	}
   461  	return result, nil
   462  }
   463  
   464  func (f *FirewallerAPI) openedPortRangesForOneMachine(machine firewall.Machine) (map[string][]params.OpenUnitPortRanges, error) {
   465  	machPortRanges, err := machine.OpenedPortRanges()
   466  	if err != nil {
   467  		return nil, errors.Trace(err)
   468  	}
   469  
   470  	portRangesByUnit := machPortRanges.ByUnit()
   471  	if len(portRangesByUnit) == 0 { // no ports open
   472  		return nil, nil
   473  	}
   474  
   475  	// Look up space to subnet mappings
   476  	spaceInfos, err := f.getSpaceInfos()
   477  	if err != nil {
   478  		return nil, errors.Trace(err)
   479  	}
   480  	subnetCIDRsBySpaceID := spaceInfos.SubnetCIDRsBySpaceID()
   481  
   482  	// Fetch application endpoint bindings
   483  	allApps := set.NewStrings()
   484  	for unitName := range portRangesByUnit {
   485  		appName, err := names.UnitApplication(unitName)
   486  		if err != nil {
   487  			return nil, errors.Trace(err)
   488  		}
   489  		allApps.Add(appName)
   490  	}
   491  	allAppBindings, err := f.getApplicationBindings()
   492  	if err != nil {
   493  		return nil, errors.Trace(err)
   494  	}
   495  
   496  	// Map the port ranges for each unit to one or more subnet CIDRs
   497  	// depending on the endpoints they apply to.
   498  	res := make(map[string][]params.OpenUnitPortRanges)
   499  	for unitName, unitPortRanges := range portRangesByUnit {
   500  		// Already checked for validity; error can be ignored
   501  		appName, _ := names.UnitApplication(unitName)
   502  		appBindings := allAppBindings[appName]
   503  
   504  		unitTag := names.NewUnitTag(unitName).String()
   505  		res[unitTag] = mapUnitPortsAndResolveSubnetCIDRs(unitPortRanges.ByEndpoint(), appBindings, subnetCIDRsBySpaceID)
   506  	}
   507  
   508  	return res, nil
   509  }
   510  
   511  // mapUnitPortsAndResolveSubnetCIDRs maps the provided list of opened port
   512  // ranges by endpoint to a params.OpenUnitPortRanges result list. Each entry in
   513  // the result list also contains the subnet CIDRs that correspond to each
   514  // endpoint.
   515  //
   516  // To resolve the subnet CIDRs, the function consults the application endpoint
   517  // bindings for the unit in conjunction with the provided subnetCIDRs by
   518  // spaceID map. Using this information, each endpoint from the incoming port
   519  // range grouping is resolved to a space ID and the space ID is in turn
   520  // resolved into a list of subnet CIDRs (the wildcard endpoint is treated as
   521  // *all known* endpoints for this conversion step).
   522  func mapUnitPortsAndResolveSubnetCIDRs(portRangesByEndpoint network.GroupedPortRanges, endpointBindings map[string]string, subnetCIDRsBySpaceID map[string][]string) []params.OpenUnitPortRanges {
   523  	var entries []params.OpenUnitPortRanges
   524  
   525  	for endpointName, portRanges := range portRangesByEndpoint {
   526  		entry := params.OpenUnitPortRanges{
   527  			Endpoint:   endpointName,
   528  			PortRanges: make([]params.PortRange, len(portRanges)),
   529  		}
   530  
   531  		// These port ranges target an explicit endpoint; just iterate
   532  		// the subnets that correspond to the space it is bound to and
   533  		// append their CIDRs.
   534  		if endpointName != "" {
   535  			entry.SubnetCIDRs = subnetCIDRsBySpaceID[endpointBindings[endpointName]]
   536  			sort.Strings(entry.SubnetCIDRs)
   537  		} else {
   538  			// The wildcard endpoint expands to all known endpoints.
   539  			for boundEndpoint, spaceID := range endpointBindings {
   540  				if boundEndpoint == "" { // ignore default endpoint entry in the set of app bindings
   541  					continue
   542  				}
   543  				entry.SubnetCIDRs = append(entry.SubnetCIDRs, subnetCIDRsBySpaceID[spaceID]...)
   544  			}
   545  
   546  			// Ensure that any duplicate CIDRs are removed.
   547  			entry.SubnetCIDRs = set.NewStrings(entry.SubnetCIDRs...).SortedValues()
   548  		}
   549  
   550  		// Finally, map the port ranges to params.PortRange and
   551  		network.SortPortRanges(portRanges)
   552  		for i, pr := range portRanges {
   553  			entry.PortRanges[i] = params.FromNetworkPortRange(pr)
   554  		}
   555  
   556  		entries = append(entries, entry)
   557  	}
   558  
   559  	// Ensure results are sorted by endpoint name to be consistent.
   560  	sort.Slice(entries, func(a, b int) bool {
   561  		return entries[a].Endpoint < entries[b].Endpoint
   562  	})
   563  
   564  	return entries
   565  }
   566  
   567  // GetExposeInfo returns the expose flag and per-endpoint expose settings
   568  // for the specified applications.
   569  func (f *FirewallerAPI) GetExposeInfo(args params.Entities) (params.ExposeInfoResults, error) {
   570  	canAccess, err := f.accessApplication()
   571  	if err != nil {
   572  		return params.ExposeInfoResults{}, err
   573  	}
   574  
   575  	result := params.ExposeInfoResults{
   576  		Results: make([]params.ExposeInfoResult, len(args.Entities)),
   577  	}
   578  
   579  	for i, entity := range args.Entities {
   580  		tag, err := names.ParseApplicationTag(entity.Tag)
   581  		if err != nil {
   582  			result.Results[i].Error = apiservererrors.ServerError(apiservererrors.ErrPerm)
   583  			continue
   584  		}
   585  		application, err := f.getApplication(canAccess, tag)
   586  		if err != nil {
   587  			result.Results[i].Error = apiservererrors.ServerError(err)
   588  			continue
   589  		}
   590  
   591  		if !application.IsExposed() {
   592  			continue
   593  		}
   594  
   595  		result.Results[i].Exposed = true
   596  		if exposedEndpoints := application.ExposedEndpoints(); len(exposedEndpoints) != 0 {
   597  			mappedEndpoints := make(map[string]params.ExposedEndpoint)
   598  			for endpoint, exposeDetails := range exposedEndpoints {
   599  				mappedEndpoints[endpoint] = params.ExposedEndpoint{
   600  					ExposeToSpaces: exposeDetails.ExposeToSpaceIDs,
   601  					ExposeToCIDRs:  exposeDetails.ExposeToCIDRs,
   602  				}
   603  			}
   604  			result.Results[i].ExposedEndpoints = mappedEndpoints
   605  		}
   606  	}
   607  	return result, nil
   608  }
   609  
   610  // SpaceInfos returns a comprehensive representation of either all spaces or
   611  // a filtered subset of the known spaces and their associated subnet details.
   612  func (f *FirewallerAPI) SpaceInfos(args params.SpaceInfosParams) (params.SpaceInfos, error) {
   613  	if !f.authorizer.AuthController() {
   614  		return params.SpaceInfos{}, apiservererrors.ServerError(apiservererrors.ErrPerm)
   615  	}
   616  
   617  	allSpaceInfos, err := f.getSpaceInfos()
   618  	if err != nil {
   619  		return params.SpaceInfos{}, apiservererrors.ServerError(err)
   620  	}
   621  
   622  	// Apply filtering if required
   623  	if len(args.FilterBySpaceIDs) != 0 {
   624  		var (
   625  			filteredList network.SpaceInfos
   626  			selectList   = set.NewStrings(args.FilterBySpaceIDs...)
   627  		)
   628  		for _, si := range allSpaceInfos {
   629  			if selectList.Contains(si.ID) {
   630  				filteredList = append(filteredList, si)
   631  			}
   632  		}
   633  
   634  		allSpaceInfos = filteredList
   635  	}
   636  
   637  	return params.FromNetworkSpaceInfos(allSpaceInfos), nil
   638  }
   639  
   640  // WatchSubnets returns a new StringsWatcher that watches the specified
   641  // subnet tags or all tags if no entities are specified.
   642  func (f *FirewallerAPI) WatchSubnets(args params.Entities) (params.StringsWatchResult, error) {
   643  	if !f.authorizer.AuthController() {
   644  		return params.StringsWatchResult{}, apiservererrors.ServerError(apiservererrors.ErrPerm)
   645  	}
   646  
   647  	var (
   648  		filterFn  func(id interface{}) bool
   649  		filterSet set.Strings
   650  		result    = params.StringsWatchResult{}
   651  	)
   652  
   653  	if len(args.Entities) != 0 {
   654  		filterSet = set.NewStrings()
   655  		for _, arg := range args.Entities {
   656  			subnetTag, err := names.ParseSubnetTag(arg.Tag)
   657  			if err != nil {
   658  				return params.StringsWatchResult{}, apiservererrors.ServerError(err)
   659  			}
   660  
   661  			filterSet.Add(subnetTag.Id())
   662  		}
   663  
   664  		filterFn = func(id interface{}) bool {
   665  			return filterSet.Contains(id.(string))
   666  		}
   667  	}
   668  
   669  	watcherId, initial, err := f.watchModelSubnets(filterFn)
   670  	if err != nil {
   671  		result.Error = apiservererrors.ServerError(err)
   672  		return result, nil
   673  	}
   674  	result.StringsWatcherId = watcherId
   675  	result.Changes = initial
   676  	return result, nil
   677  }
   678  
   679  func (f *FirewallerAPI) watchModelSubnets(filterFn func(interface{}) bool) (string, []string, error) {
   680  	watch := f.st.WatchSubnets(filterFn)
   681  
   682  	// Consume the initial event and forward it to the result.
   683  	if changes, ok := <-watch.Changes(); ok {
   684  		return f.resources.Register(watch), changes, nil
   685  	}
   686  	return "", nil, watcher.EnsureErr(watch)
   687  }
   688  
   689  func setEquals(a, b set.Strings) bool {
   690  	if a.Size() != b.Size() {
   691  		return false
   692  	}
   693  	return a.Intersection(b).Size() == a.Size()
   694  }