Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter chain #155

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions pkg/ads/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,82 @@ type Client interface {
ResolveHost(hostName string) ([]net.IP, error)
}

// NetworkFilter represents a network filter configuration which a filter chain consists of
type NetworkFilter interface {
// Name returns the name of the network filter configuration
Name() string

// Configuration returns filter specific configuration which depends on the filter being instantiated.
Configuration() map[string]interface{}
}

// NetworkFilterSelectOptions holds the options used to determine the
// filter chain to be instantiated for inbound/outbound connections
// and the attributes to filter by the filters of the selected filter chain
type NetworkFilterSelectOptions struct {
// destinationPort is the destination port of the connection
destinationPort uint32

// destinationIP is the destination IP of the connection
destinationIP net.IP

// serverName is the server name used with TLS connections
serverName string

// transportProtocol is the transport protocol of the connection
transportProtocol string

// applicationProtocols is the list of application protocols (e.g. ALPN for TLS protocol) of the connection
applicationProtocols []string

// includeFiltersWithTypes include network filters with these types
includeFiltersWithTypes []string
}

type NetworkFilterSelectOption func(*NetworkFilterSelectOptions)

// ConnectionWithDestinationPort specifies the given destination port as connection option to select filter chain by
func ConnectionWithDestinationPort(destinationPort uint32) NetworkFilterSelectOption {
return func(o *NetworkFilterSelectOptions) {
o.destinationPort = destinationPort
}
}

// ConnectionWithDestinationIP specifies the given destination IP as connection option to select filter chain by
func ConnectionWithDestinationIP(destinationIP net.IP) NetworkFilterSelectOption {
return func(o *NetworkFilterSelectOptions) {
o.destinationIP = destinationIP
}
}

// TLSConnectionWithServerName specifies the given server name as TLS connection option to select filter chain by
func TLSConnectionWithServerName(serverName string) NetworkFilterSelectOption {
return func(o *NetworkFilterSelectOptions) {
o.serverName = serverName
}
}

// ConnectionWithTransportProtocol specifies the given transport protocol as connection option to select filter chain by
func ConnectionWithTransportProtocol(transportProtocol string) NetworkFilterSelectOption {
return func(o *NetworkFilterSelectOptions) {
o.transportProtocol = transportProtocol
}
}

// ConnectionWithApplicationProtocols specifies the given application protocols as connection option select filter chain by
func ConnectionWithApplicationProtocols(applicationProtocols []string) NetworkFilterSelectOption {
return func(o *NetworkFilterSelectOptions) {
o.applicationProtocols = applicationProtocols
}
}

// IncludeNetworkFiltersWithTypes specifies the network filter types to filter by the network filters of the selected filter chain
func IncludeNetworkFiltersWithTypes(filterTypes ...string) NetworkFilterSelectOption {
return func(o *NetworkFilterSelectOptions) {
o.includeFiltersWithTypes = filterTypes
}
}

// ListenerPropertiesResponse contains the result for the API call
// to retrieve ListenerProperties for a given address.
type ListenerPropertiesResponse interface {
Expand All @@ -109,6 +185,10 @@ type ListenerProperties interface {

// Metadata returns the metadata associated with this listener
Metadata() map[string]interface{}

// NetworkFilters returns the network filter chain that inbound traffic flows through
// when client workload connects with the given connection options.
NetworkFilters(...NetworkFilterSelectOption) ([]NetworkFilter, error)
}

// ClientPropertiesResponse contains the result for the API call
Expand Down Expand Up @@ -137,6 +217,10 @@ type ClientProperties interface {

// Metadata returns the metadata associated with the target service
Metadata() map[string]interface{}

// NetworkFilters returns the network filter chain that outbound traffic flows through to the target service
// when client workload connects with the given connection options
NetworkFilters(...NetworkFilterSelectOption) ([]NetworkFilter, error)
}

// HTTPClientPropertiesResponse contains the result for the API call
Expand Down
32 changes: 16 additions & 16 deletions pkg/ads/api_http_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,35 +152,35 @@ func (c *client) getHTTPClientPropertiesByHost(input getHTTPClientPropertiesByHo
return nil, nil
}

cluster, route, err := c.getHttpClientTargetCluster(input.host, int(input.port))
hostIPs, err := c.ResolveHost(input.host)
if err != nil {
return nil, errors.WrapIf(err, "couldn't get target cluster")
return nil, errors.WrapIff(err, "couldn't resolve host %q", input.host)
}

clientProps, err := c.newClientProperties(cluster, route)
listener, err := c.getHTTPOutboundListener(hostIPs, int(input.port))
if err != nil {
return nil, errors.WrapIff(err, "couldn't create client properties for target service at %s:%d", input.host, input.port)
return nil, errors.WrapIff(err, "couldn't get HTTP outbound listener for address: %s:%d", input.host, input.port)
}

return clientProps, nil
}

// getHttpClientTargetCluster returns the upstream cluster which HTTP traffic is directed to when
// clients connect to host:port
func (c *client) getHttpClientTargetCluster(host string, port int) (*envoy_config_cluster_v3.Cluster, *envoy_config_route_v3.Route, error) {
hostIPs, err := c.ResolveHost(host)
cluster, route, err := c.getHttpClientTargetCluster(input.host, int(input.port), hostIPs, listener)
if err != nil {
return nil, nil, errors.WrapIff(err, "couldn't resolve host %q", host)
return nil, errors.WrapIff(err, "couldn't get target cluster for address: %s:%d", input.host, input.port)
}

lstnr, err := c.getHTTPOutboundListener(hostIPs, port)
clientProps, err := c.newClientProperties(cluster, listener, route)
if err != nil {
return nil, nil, errors.WrapIff(err, "couldn't get HTTP outbound listener for address: %s:%d", host, port)
return nil, errors.WrapIff(err, "couldn't create client properties for target service at %s:%d", input.host, input.port)
}

routeConfigName := listener.GetRouteConfigName(lstnr)
return clientProps, nil
}

// getHttpClientTargetCluster returns the upstream cluster which HTTP traffic is directed to when
// clients connect to host:port
func (c *client) getHttpClientTargetCluster(host string, port int, hostIPs []net.IP, targetListener *envoy_config_listener_v3.Listener) (*envoy_config_cluster_v3.Cluster, *envoy_config_route_v3.Route, error) {
routeConfigName := listener.GetRouteConfigName(targetListener)
if routeConfigName == "" {
return nil, nil, errors.Errorf("no route config found for address: %s:%d", host, port)
return nil, nil, errors.New("couldn't determine route config")
}
routeConfig, err := c.getRouteConfig(routeConfigName)
if err != nil {
Expand Down
97 changes: 15 additions & 82 deletions pkg/ads/api_listener_properties.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
"reflect"
"strconv"

"github.com/cisco-open/nasp/pkg/ads/internal/filterchain"

"github.com/cisco-open/nasp/pkg/ads/internal/listener"
"github.com/cisco-open/nasp/pkg/ads/internal/util"

Expand All @@ -36,6 +38,7 @@ type listenerProperties struct {
permissive bool
requireClientCertificate bool
metadata map[string]interface{}
inboundListener *envoy_config_listener_v3.Listener
}

func (lp *listenerProperties) UseTLS() bool {
Expand All @@ -54,6 +57,10 @@ func (lp *listenerProperties) Metadata() map[string]interface{} {
return lp.metadata
}

func (lp *listenerProperties) NetworkFilters(networkFilterSelectOpts ...NetworkFilterSelectOption) ([]NetworkFilter, error) {
return listenerNetworkFilters(lp.inboundListener, networkFilterSelectOpts...)
}

func (lp *listenerProperties) String() string {
return fmt.Sprintf("{useTLS=%t, permissive=%t, isClientCertificateRequired=%t}", lp.useTLS, lp.permissive, lp.requireClientCertificate)
}
Expand Down Expand Up @@ -183,8 +190,13 @@ func (c *client) getListenerProperties(input getListenerPropertiesInput) (Listen
return nil, errors.WrapIf(err, "couldn't list inbound listeners for address")
}
for _, lstnr := range listeners {
// matching rules https://github.com/envoyproxy/go-control-plane/blob/v0.9.9/envoy/config/listener/v3/listener_components.pb.go#L211
filterChains, err := findFilterChain(lstnr.GetFilterChains(), input.port, net.ParseIP(input.host))
// find listener's filter chains that are matching the first 2 steps of the rules described here
// https://github.com/envoyproxy/go-control-plane/blob/v0.9.9/envoy/config/listener/v3/listener_components.pb.go#L211
// which is enough to determine the properties of a workload listener
filterChains, err := filterchain.Filter(lstnr,
filterchain.WithDestinationPort(input.port),
filterchain.WithDestinationIP(net.ParseIP(input.host)))

if err != nil {
return nil, err
}
Expand Down Expand Up @@ -231,6 +243,7 @@ func (c *client) getListenerProperties(input getListenerPropertiesInput) (Listen
// shows whether client certificate is required
requireClientCertificate: requireClientCertificate,
metadata: metadata,
inboundListener: matchedListener,
}
}
}
Expand All @@ -241,83 +254,3 @@ func (c *client) getListenerProperties(input getListenerPropertiesInput) (Listen

return lp, nil
}

// findFilterChain returns filter chain items from the provided
// 'filterChains' that are matching the first 2 steps of the rules described here
// https://github.com/envoyproxy/go-control-plane/blob/v0.9.9/envoy/config/listener/v3/listener_components.pb.go#L211
// which is enough to determine the properties of a workload listener
func findFilterChain(filterChains []*envoy_config_listener_v3.FilterChain, port uint32, ip net.IP) ([]*envoy_config_listener_v3.FilterChain, error) {
// 1. match by destination port
var fcsMatchedByPort []*envoy_config_listener_v3.FilterChain

// match by exact destination port first as that is the most specific match
// if there are no matches by specific destination port then check the next most specific
// match which is the filter chain matches with no destination port
dstPortsToMatch := []uint32{port, 0}
for _, matchPort := range dstPortsToMatch {
for _, fc := range filterChains {
fcm := fc.GetFilterChainMatch()

dstPort := uint32(0)
if fcm.GetDestinationPort() != nil {
dstPort = fcm.GetDestinationPort().GetValue()
}

if matchPort == dstPort {
fcsMatchedByPort = append(fcsMatchedByPort, fc)
}
}

if len(fcsMatchedByPort) > 0 {
break
}
}

// 2. match destination IP address
var fcsMatchedByDstIP []*envoy_config_listener_v3.FilterChain
for _, fc := range fcsMatchedByPort {
cidrs := fc.GetFilterChainMatch().GetPrefixRanges()
if cidrs != nil {
// verify destination IP address matches any of the cidrs of the filter chain
ok, err := matchPrefixRanges(cidrs, ip)
if err != nil {
return nil, err
}
if ok {
fcsMatchedByDstIP = append(fcsMatchedByDstIP, fc)
}
}
}

// if there are no filter chain matches by CIDR than the next most specific matches are those
// which don't have a CIDR defined
if len(fcsMatchedByDstIP) == 0 {
// if there is no CIDR specified for the filter chain main
for _, fc := range fcsMatchedByPort {
if fc.GetFilterChainMatch().GetPrefixRanges() == nil {
fcsMatchedByDstIP = append(fcsMatchedByDstIP, fc)
}
}
}

return fcsMatchedByDstIP, nil
}

func matchPrefixRanges(prefixRanges []*envoy_config_core_v3.CidrRange, ip net.IP) (bool, error) {
for _, cidr := range prefixRanges {
if cidr.GetAddressPrefix() == "" {
continue
}

cidrStr := fmt.Sprintf("%s/%d", cidr.GetAddressPrefix(), cidr.GetPrefixLen().GetValue())
_, ipnet, err := net.ParseCIDR(cidrStr)
if err != nil {
return false, errors.WrapIff(err, "couldn't parse address prefix: %q", cidrStr)
}

if ipnet.Contains(ip) {
return true, nil
}
}
return false, nil
}
Loading