Skip to content

Commit

Permalink
mTLS IP filter
Browse files Browse the repository at this point in the history
  • Loading branch information
wardviaene committed Apr 9, 2021
1 parent d6b7e79 commit d742edb
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 11 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,25 @@ spec:

This will run the ACME validation on both hostnames (mocky-1.in4it.io and mocky-2.in4it.io). If successful, it'll create an https listener that redirects to www.mocky.io, a mocking service.

## mTLS
mTLS listeners can be added on different ports than the default listener. You just need to provide server key/crt and CA cert.
```
api: proxy.in4it.io/v1
kind: mTLS
metadata:
name: test-rule
spec:
privateKey: |
replaceme
certificate: |
replaceme
caCertificate: |
replaceme
port: 10002
AllowedSubjectAltNames: ["client1.example.com"] # optional ALT Name subject restriction
AllowedIPRanges: ["1.2.3.4/16"] # optional IP restriction
```

## Run on AWS with terraform

There is a terraform module available in this repository. It'll configure an S3 bucket, a Network Loadbalancer, and 3 fargate containers. The container setup consist of 2 envoy proxies (one for http and one for https), and the roxprox server. To start using it, add the following code to your terraform project:
Expand Down
1 change: 1 addition & 0 deletions pkg/api/mtls.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ type MTLSSpec struct {
CACertificate string `json:"caCertificate" yaml:"caCertificate"`
Port int64 `json:"port" yaml:"port"`
AllowedSubjectAltNames []string `json:"allowedSubjectAltNames" yaml:"allowedSubjectAltNames"`
AllowedIPRanges []string `json:"allowedIPRanges" yaml:"allowedIPRanges"`
}
6 changes: 3 additions & 3 deletions pkg/envoy/jwtprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ func (j *JwtProvider) updateListenerWithJwtProvider(cache *WorkQueueCache, param
if err != nil {
panic(err)
}
ll.FilterChains[0].Filters[0].ConfigType = &api.Filter_TypedConfig{
ll.FilterChains[0].Filters[getFilterIndexByName(ll.FilterChains[0].Filters, Envoy_HTTP_Filter)].ConfigType = &api.Filter_TypedConfig{
TypedConfig: pbst,
}
}
Expand Down Expand Up @@ -306,7 +306,7 @@ func (j *JwtProvider) UpdateJwtRule(cache *WorkQueueCache, params ListenerParams
}

// modify filter
ll.FilterChains[filterId].Filters[0].ConfigType = &api.Filter_TypedConfig{
ll.FilterChains[filterId].Filters[getFilterIndexByName(ll.FilterChains[0].Filters, Envoy_HTTP_Filter)].ConfigType = &api.Filter_TypedConfig{
TypedConfig: pbst,
}

Expand Down Expand Up @@ -383,7 +383,7 @@ func (j *JwtProvider) DeleteJwtRule(cache *WorkQueueCache, params ListenerParams
filterId = 0
}

ll.FilterChains[filterId].Filters[0].ConfigType = &api.Filter_TypedConfig{
ll.FilterChains[filterId].Filters[getFilterIndexByName(ll.FilterChains[0].Filters, Envoy_HTTP_Filter)].ConfigType = &api.Filter_TypedConfig{
TypedConfig: pbst,
}

Expand Down
10 changes: 7 additions & 3 deletions pkg/envoy/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

const Error_NoFilterChainFound = "NoFilterChainFound"
const Error_NoFilterFound = "NoFilterFound"
const Envoy_HTTP_Filter = "envoy.filters.network.http_connection_manager"

type listenerDefaultsMapping struct {
rateLimit bool
Expand Down Expand Up @@ -168,7 +169,7 @@ func (l *Listener) updateListenerWithChallenge(cache *WorkQueueCache, challenge
if err != nil {
panic(err)
}
ll.FilterChains[0].Filters[0].ConfigType = &api.Filter_TypedConfig{
ll.FilterChains[0].Filters[getFilterIndexByName(ll.FilterChains[0].Filters, Envoy_HTTP_Filter)].ConfigType = &api.Filter_TypedConfig{
TypedConfig: pbst,
}
}
Expand Down Expand Up @@ -466,7 +467,7 @@ func (l *Listener) updateListener(cache *WorkQueueCache, params ListenerParams,
}

// modify filter
ll.FilterChains[filterId].Filters[0].ConfigType = &api.Filter_TypedConfig{
ll.FilterChains[filterId].Filters[getFilterIndexByName(ll.FilterChains[0].Filters, Envoy_HTTP_Filter)].ConfigType = &api.Filter_TypedConfig{
TypedConfig: pbst,
}

Expand Down Expand Up @@ -696,7 +697,7 @@ func (l *Listener) DeleteRoute(cache *WorkQueueCache, params ListenerParams, par
filterId = 0
}

ll.FilterChains[filterId].Filters[0].ConfigType = &api.Filter_TypedConfig{
ll.FilterChains[filterId].Filters[getFilterIndexByName(ll.FilterChains[0].Filters, Envoy_HTTP_Filter)].ConfigType = &api.Filter_TypedConfig{
TypedConfig: pbst,
}

Expand Down Expand Up @@ -891,6 +892,9 @@ func (l *Listener) HasMTLSDefault(listenerName, attr string) bool {
if attr == "envoy.filters.http.router" {
return true // always allow the router filter
}
if attr == "envoy.filters.network.rbac" {
return true // always allow the rbac filter
}
if val, ok := l.mTLSListenerDefaultsMapping[listenerName]; ok {
switch attr {
case "envoy.filters.http.ratelimit":
Expand Down
4 changes: 2 additions & 2 deletions pkg/envoy/listener_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -870,7 +870,7 @@ func validateJWTProvider(listeners []cacheTypes.Resource, auth Auth) error {
if len(filterChain.Filters) == 0 {
return fmt.Errorf("No filters found in listener %s", cachedListener.Name)
}
manager, err := getManager((filterChain.Filters[0].ConfigType).(*api.Filter_TypedConfig))
manager, err := getManager((filterChain.Filters[getFilterIndexByName(filterChain.Filters, Envoy_HTTP_Filter)].ConfigType).(*api.Filter_TypedConfig))
if err != nil {
return fmt.Errorf("Could not extract manager from listener %s", cachedListener.Name)
}
Expand Down Expand Up @@ -1044,7 +1044,7 @@ func validateAuthz(listeners []cacheTypes.Resource, params ListenerParams) error
if len(filterChain.Filters) == 0 {
return fmt.Errorf("No filters found in listener %s", cachedListener.Name)
}
manager, err := getManager((filterChain.Filters[0].ConfigType).(*api.Filter_TypedConfig))
manager, err := getManager((filterChain.Filters[getFilterIndexByName(filterChain.Filters, Envoy_HTTP_Filter)].ConfigType).(*api.Filter_TypedConfig))
if err != nil {
return fmt.Errorf("Could not extract manager from listener %s", cachedListener.Name)
}
Expand Down
11 changes: 10 additions & 1 deletion pkg/envoy/listener_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func getListenerHTTPConnectionManager(ll *api.Listener) (*hcm.HttpConnectionMana
if len(ll.FilterChains[0].Filters) == 0 {
return manager, fmt.Errorf("No filters found in listener %s", ll.Name)
}
manager, err = getManager((ll.FilterChains[0].Filters[0].ConfigType).(*api.Filter_TypedConfig))
manager, err = getManager((ll.FilterChains[0].Filters[getFilterIndexByName(ll.FilterChains[0].Filters, Envoy_HTTP_Filter)].ConfigType).(*api.Filter_TypedConfig))
if err != nil {
return manager, err
}
Expand Down Expand Up @@ -413,3 +413,12 @@ func isDefaultListener(listenerName string) bool {
}
return false
}

func getFilterIndexByName(filters []*api.Filter, name string) int {
for k, filter := range filters {
if filter.Name == name {
return k
}
}
return -1
}
67 changes: 65 additions & 2 deletions pkg/envoy/mtls.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ package envoy

import (
"fmt"
"strconv"
"strings"

core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
api "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
rbacConfig "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3"
rbac "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/rbac/v3"
tls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
matcher "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
"github.com/golang/protobuf/ptypes"
"github.com/golang/protobuf/ptypes/wrappers"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/wrapperspb"
)

Expand Down Expand Up @@ -42,12 +48,23 @@ func (l *MTLS) updateMTLSListener(cache *WorkQueueCache, params ListenerParams,
}
ll := cache.listeners[listenerIndex].(*api.Listener)
// set proxy protocol filter
ll.ListenerFilters = []*api.ListenerFilter{}
if mTLSParams.EnableProxyProtocol {
ll.ListenerFilters = []*api.ListenerFilter{
ll.ListenerFilters = append(ll.ListenerFilters, &api.ListenerFilter{
Name: "envoy.filters.listener.proxy_protocol",
})
}
// set AllowedIPRanges
if len(mTLSParams.AllowedIPRanges) > 0 {
rbacFilter := []*api.Filter{
{
Name: "envoy.filters.listener.proxy_protocol",
Name: "envoy.filters.network.rbac",
ConfigType: &api.Filter_TypedConfig{
TypedConfig: getRBACConfig(mTLSParams),
},
},
}
ll.FilterChains[0].Filters = append(rbacFilter, ll.FilterChains[0].Filters...)
}
matchSubjectAltNames := make([]*matcher.StringMatcher, len(mTLSParams.AllowedSubjectAltNames))
for k, name := range mTLSParams.AllowedSubjectAltNames {
Expand Down Expand Up @@ -104,3 +121,49 @@ func (l *MTLS) updateMTLSListener(cache *WorkQueueCache, params ListenerParams,

return nil
}
func getRBACConfig(mTLSParams MTLSParams) *anypb.Any {
principals := []*rbacConfig.Principal{}
for _, ipRange := range mTLSParams.AllowedIPRanges {
ipRangeSplit := strings.Split(ipRange, "/")
prefixLen, err := strconv.ParseUint(ipRangeSplit[1], 10, 32)
if len(ipRangeSplit) != 2 || err != nil {
logger.Warningf("Invalid IP address range: %s in listener: l_mtls_%s", ipRange, mTLSParams.Name)
} else {
principals = append(principals, &rbacConfig.Principal{

Identifier: &rbacConfig.Principal_DirectRemoteIp{
DirectRemoteIp: &core.CidrRange{
AddressPrefix: ipRangeSplit[0],
PrefixLen: &wrappers.UInt32Value{
Value: uint32(prefixLen),
},
},
},
})
}
}
r := &rbac.RBAC{
StatPrefix: "rbac_" + mTLSParams.Name,
Rules: &rbacConfig.RBAC{
Action: rbacConfig.RBAC_ALLOW,
Policies: map[string]*rbacConfig.Policy{
"ip_filter": {
Principals: principals,
Permissions: []*rbacConfig.Permission{
{
Rule: &rbacConfig.Permission_Any{
Any: true,
},
},
},
},
},
},
}
pbst, err := ptypes.MarshalAny(r)
if err != nil {
panic(err)
}

return pbst
}
1 change: 1 addition & 0 deletions pkg/envoy/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ type MTLSParams struct {
Certificate string
Port int64
AllowedSubjectAltNames []string
AllowedIPRanges []string
CACertificate string
EnableProxyProtocol bool
}
1 change: 1 addition & 0 deletions pkg/envoy/xds.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ func (x *XDS) importMTLS(mTLS pkgApi.MTLS) ([]WorkQueueItem, error) {
Certificate: mTLS.Spec.Certificate,
CACertificate: mTLS.Spec.CACertificate,
AllowedSubjectAltNames: mTLS.Spec.AllowedSubjectAltNames,
AllowedIPRanges: mTLS.Spec.AllowedIPRanges,
Port: mTLS.Spec.Port,
EnableProxyProtocol: mTLS.Spec.EnableProxyProtocol,
},
Expand Down

0 comments on commit d742edb

Please sign in to comment.