github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/common/firewall/firewall.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package firewall
     5  
     6  import (
     7  	"github.com/juju/charm/v12"
     8  	"github.com/juju/errors"
     9  	"github.com/juju/loggo"
    10  	"github.com/juju/names/v5"
    11  
    12  	apiservererrors "github.com/juju/juju/apiserver/errors"
    13  	"github.com/juju/juju/apiserver/facade"
    14  	"github.com/juju/juju/rpc/params"
    15  	"github.com/juju/juju/state/watcher"
    16  )
    17  
    18  var logger = loggo.GetLogger("juju.apiserver.crossmodelrelations")
    19  
    20  // WatchEgressAddressesForRelations creates a watcher that notifies when addresses, from which
    21  // connections will originate for the relation, change.
    22  // Each event contains the entire set of addresses which are required for ingress for the relation.
    23  func WatchEgressAddressesForRelations(resources facade.Resources, st State, relations params.Entities) (params.StringsWatchResults, error) {
    24  	results := params.StringsWatchResults{
    25  		make([]params.StringsWatchResult, len(relations.Entities)),
    26  	}
    27  
    28  	one := func(tag string) (id string, changes []string, _ error) {
    29  		logger.Debugf("Watching egress addresses for %+v", tag)
    30  
    31  		relationTag, err := names.ParseRelationTag(tag)
    32  		if err != nil {
    33  			return "", nil, errors.Trace(err)
    34  		}
    35  
    36  		// Load the relation details for the current token.
    37  		localEndpoint, err := localApplication(st, relationTag)
    38  		if err != nil {
    39  			return "", nil, errors.Trace(err)
    40  		}
    41  
    42  		w, err := NewEgressAddressWatcher(st, localEndpoint.relation, localEndpoint.application)
    43  		if err != nil {
    44  			return "", nil, errors.Trace(err)
    45  		}
    46  
    47  		// TODO(wallyworld) - we will need to watch subnets too, but only
    48  		// when we support using cloud local addresses
    49  		//filter := func(id interface{}) bool {
    50  		//	include, err := includeAsIngressSubnet(id.(string))
    51  		//	if err != nil {
    52  		//		logger.Warningf("invalid CIDR %q", id)
    53  		//	}
    54  		//	return include
    55  		//}
    56  		//w := api.st.WatchSubnets(filter)
    57  
    58  		changes, ok := <-w.Changes()
    59  		if !ok {
    60  			return "", nil, apiservererrors.ServerError(watcher.EnsureErr(w))
    61  		}
    62  		return resources.Register(w), changes, nil
    63  	}
    64  
    65  	for i, e := range relations.Entities {
    66  		watcherId, changes, err := one(e.Tag)
    67  		if err != nil {
    68  			results.Results[i].Error = apiservererrors.ServerError(err)
    69  			continue
    70  		}
    71  		results.Results[i].StringsWatcherId = watcherId
    72  		results.Results[i].Changes = changes
    73  	}
    74  	return results, nil
    75  }
    76  
    77  type localEndpointInfo struct {
    78  	relation    Relation
    79  	application string
    80  	name        string
    81  	role        charm.RelationRole
    82  }
    83  
    84  func localApplication(st State, relationTag names.RelationTag) (*localEndpointInfo, error) {
    85  	rel, err := st.KeyRelation(relationTag.Id())
    86  	if err != nil {
    87  		return nil, errors.Trace(err)
    88  	}
    89  
    90  	// Gather info about the local (this model) application of the relation.
    91  	// We'll use the info to figure out what addresses/subnets to include.
    92  	localEndpoint := localEndpointInfo{relation: rel}
    93  	for _, ep := range rel.Endpoints() {
    94  		// Try looking up the info for the local application.
    95  		_, err = st.Application(ep.ApplicationName)
    96  		if err != nil && !errors.IsNotFound(err) {
    97  			return nil, errors.Trace(err)
    98  		} else if err == nil {
    99  			localEndpoint.application = ep.ApplicationName
   100  			localEndpoint.name = ep.Name
   101  			localEndpoint.role = ep.Role
   102  			break
   103  		}
   104  	}
   105  	// Until networking support becomes more sophisticated, for now
   106  	// we only care about opening access to applications with an endpoint
   107  	// having the "provider" role. The assumption is that such endpoints listen
   108  	// to incoming connections and thus require ingress. An exception to this
   109  	// would be applications which accept connections onto an endpoint which
   110  	// has a "requirer" role.
   111  	// We are operating in the model hosting the "consuming" application, so check
   112  	// that its endpoint has the "requirer" role, meaning that we need to notify
   113  	// the offering model of subnets from this model required for ingress.
   114  	if localEndpoint.role != charm.RoleRequirer {
   115  		return nil, errors.NotSupportedf(
   116  			"egress network for application %v without requires endpoint", localEndpoint.application)
   117  	}
   118  	return &localEndpoint, nil
   119  }
   120  
   121  // TODO(wallyworld) - this is unused until we query subnets again
   122  /*
   123  func includeAsEgressSubnet(cidr string) (bool, error) {
   124  	ip, _, err := net.ParseCIDR(cidr)
   125  	if err != nil {
   126  		return false, errors.Trace(err)
   127  	}
   128  	if ip.IsLoopback() || ip.IsMulticast() {
   129  		return false, nil
   130  	}
   131  	// TODO(wallyworld) - We only support IPv4 addresses as not all providers support IPv6.
   132  	if ip.To4() == nil {
   133  		return false, nil
   134  	}
   135  	return true, nil
   136  }
   137  */