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