Skip to content

Commit

Permalink
feat: add support for a trusted proxies ingress annotation (#249)
Browse files Browse the repository at this point in the history
* feat: add support for a trusted proxies ingress annotation

Signed-off-by: Lucas Rodriguez <[email protected]>

* Added extra tests

---------

Signed-off-by: Lucas Rodriguez <[email protected]>
Co-authored-by: Marco Vito Moscaritolo <[email protected]>
  • Loading branch information
lucasrod16 and mavimo authored Sep 10, 2024
1 parent 48ef36b commit 7414ecd
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 0 deletions.
1 change: 1 addition & 0 deletions internal/caddy/ingress/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const (
permanentRedirectAnnotation = "permanent-redirect"
permanentRedirectCodeAnnotation = "permanent-redirect-code"
temporaryRedirectAnnotation = "temporal-redirect"
trustedProxies = "trusted-proxies"
)

func getAnnotation(ing *v1.Ingress, rule string) string {
Expand Down
35 changes: 35 additions & 0 deletions internal/caddy/ingress/reverseproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ingress

import (
"fmt"
"net/netip"
"strings"

"github.com/caddyserver/caddy/v2/caddyconfig"
Expand All @@ -26,6 +27,7 @@ func (p ReverseProxyPlugin) IngressHandler(input converter.IngressMiddlewareInpu
path := input.Path
ing := input.Ingress
backendProtocol := strings.ToLower(getAnnotation(ing, backendProtocol))
trustedProxiesAnnotation := strings.ToLower(getAnnotation(ing, trustedProxies))

// TODO :-
// when setting the upstream url we should bypass kube-dns and get the ip address of
Expand All @@ -41,11 +43,22 @@ func (p ReverseProxyPlugin) IngressHandler(input converter.IngressMiddlewareInpu
}
}

var err error
var parsedProxies []string
if trustedProxiesAnnotation != "" {
trustedProxies := strings.Split(trustedProxiesAnnotation, ",")
parsedProxies, err = parseTrustedProxies(trustedProxies)
if err != nil {
return nil, err
}
}

handler := reverseproxy.Handler{
TransportRaw: caddyconfig.JSONModuleObject(transport, "protocol", "http", nil),
Upstreams: reverseproxy.UpstreamPool{
{Dial: clusterHostName},
},
TrustedProxies: parsedProxies,
}

handlerModule := caddyconfig.JSONModuleObject(
Expand All @@ -58,6 +71,28 @@ func (p ReverseProxyPlugin) IngressHandler(input converter.IngressMiddlewareInpu
return input.Route, nil
}

// Copied from https://github.com/caddyserver/caddy/blob/21af88fefc9a8239a024f635f1c6fdd9defd7eb7/modules/caddyhttp/reverseproxy/reverseproxy.go#L270-L286
func parseTrustedProxies(trustedProxies []string) (parsedProxies []string, err error) {
for _, trustedProxy := range trustedProxies {
trustedProxy = strings.TrimSpace(trustedProxy)
if strings.Contains(trustedProxy, "/") {
ipNet, err := netip.ParsePrefix(trustedProxy)
if err != nil {
return nil, fmt.Errorf("failed to parse IP: %q", trustedProxy)
}
parsedProxies = append(parsedProxies, ipNet.String())
} else {
ipAddr, err := netip.ParseAddr(trustedProxy)
if err != nil {
return nil, fmt.Errorf("failed to parse IP: %q", trustedProxy)
}
ipNew := netip.PrefixFrom(ipAddr, ipAddr.BitLen())
parsedProxies = append(parsedProxies, ipNew.String())
}
}
return parsedProxies, nil
}

func init() {
converter.RegisterPlugin(ReverseProxyPlugin{})
}
Expand Down
168 changes: 168 additions & 0 deletions internal/caddy/ingress/reverseproxy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package ingress

import (
"encoding/json"
"os"
"testing"

"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/ingress/pkg/converter"
"github.com/stretchr/testify/require"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestTrustedProxesConvertToCaddyConfig(t *testing.T) {
rpp := ReverseProxyPlugin{}

tests := []struct {
name string
annotations map[string]string
expectedConfigPath string
}{
{
name: "ipv4 trusted proxies",
annotations: map[string]string{
"caddy.ingress.kubernetes.io/trusted-proxies": "192.168.1.0, 10.0.0.1",
},
expectedConfigPath: "test_data/reverseproxy_trusted_proxies_ipv4.json",
},
{
name: "ipv4 trusted proxies wit subnet",
annotations: map[string]string{
"caddy.ingress.kubernetes.io/trusted-proxies": "192.168.1.0/16,10.0.0.1/8",
},
expectedConfigPath: "test_data/reverseproxy_trusted_proxies_ipv4_subnet.json",
},
{
name: "ipv6 trusted proxies",
annotations: map[string]string{
"caddy.ingress.kubernetes.io/trusted-proxies": "2001:db8::1, 2001:db8::5",
},
expectedConfigPath: "test_data/reverseproxy_trusted_proxies_ipv6.json",
},
{
name: "ipv6 trusted proxies",
annotations: map[string]string{
"caddy.ingress.kubernetes.io/trusted-proxies": "2001:db8::1/36,2001:db8::5/60",
},
expectedConfigPath: "test_data/reverseproxy_trusted_proxies_ipv6_subnet.json",
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
input := converter.IngressMiddlewareInput{
Ingress: &networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Annotations: test.annotations,
Namespace: "namespace",
},
},
Path: networkingv1.HTTPIngressPath{
Backend: networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: "svcName",
Port: networkingv1.ServiceBackendPort{Number: 80},
},
},
},
Route: &caddyhttp.Route{},
}

route, err := rpp.IngressHandler(input)
require.NoError(t, err)

expectedCfg, err := os.ReadFile(test.expectedConfigPath)
require.NoError(t, err)

cfgJson, err := json.Marshal(&route)
require.NoError(t, err)

require.JSONEq(t, string(expectedCfg), string(cfgJson))
})
}
}

func TestMisconfiguredTrustedProxiesConvertToCaddyConfig(t *testing.T) {
rpp := ReverseProxyPlugin{}

tests := []struct {
name string
annotations map[string]string
expectedError string
}{
{
name: "invalid ipv4 trusted proxy",
annotations: map[string]string{
"caddy.ingress.kubernetes.io/trusted-proxies": "999.999.999.999",
},
expectedError: `failed to parse IP: "999.999.999.999"`,
},
{
name: "invalid ipv4 with subnet trusted proxy",
annotations: map[string]string{
"caddy.ingress.kubernetes.io/trusted-proxies": "999.999.999.999/32",
},
expectedError: `failed to parse IP: "999.999.999.999/32"`,
},
{
name: "invalid subnet for ipv4 trusted proxy",
annotations: map[string]string{
"caddy.ingress.kubernetes.io/trusted-proxies": "10.0.0.0/100",
},
expectedError: `failed to parse IP: "10.0.0.0/100"`,
},
{
name: "invalid ipv6 trusted proxy",
annotations: map[string]string{
"caddy.ingress.kubernetes.io/trusted-proxies": "2001:db8::g",
},
expectedError: `failed to parse IP: "2001:db8::g"`,
},
{
name: "invalid ipv6 with subnet trusted proxy",
annotations: map[string]string{
"caddy.ingress.kubernetes.io/trusted-proxies": "2001:db8::g/128",
},
expectedError: `failed to parse IP: "2001:db8::g/128"`,
},
{
name: "invalid subnet for ipv6 trusted proxy",
annotations: map[string]string{
"caddy.ingress.kubernetes.io/trusted-proxies": "2001:db8::/200",
},
expectedError: `failed to parse IP: "2001:db8::/200"`,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
input := converter.IngressMiddlewareInput{
Ingress: &networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Annotations: test.annotations,
Namespace: "namespace",
},
},
Path: networkingv1.HTTPIngressPath{
Backend: networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: "svcName",
Port: networkingv1.ServiceBackendPort{Number: 80},
},
},
},
Route: &caddyhttp.Route{},
}

route, err := rpp.IngressHandler(input)
require.EqualError(t, err, test.expectedError)

cfgJson, err := json.Marshal(&route)
require.NoError(t, err)

require.JSONEq(t, string(cfgJson), "null")
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"handle": [
{
"handler": "reverse_proxy",
"transport": {
"protocol": "http"
},
"trusted_proxies": [
"192.168.1.0/32",
"10.0.0.1/32"
],
"upstreams": [
{
"dial": "svcName.namespace.svc.cluster.local:80"
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"handle": [
{
"handler": "reverse_proxy",
"transport": {
"protocol": "http"
},
"trusted_proxies": [
"192.168.1.0/16",
"10.0.0.1/8"
],
"upstreams": [
{
"dial": "svcName.namespace.svc.cluster.local:80"
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"handle": [
{
"handler": "reverse_proxy",
"transport": {
"protocol": "http"
},
"trusted_proxies": [
"2001:db8::1/128",
"2001:db8::5/128"
],
"upstreams": [
{
"dial": "svcName.namespace.svc.cluster.local:80"
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"handle": [
{
"handler": "reverse_proxy",
"transport": {
"protocol": "http"
},
"trusted_proxies": [
"2001:db8::1/36",
"2001:db8::5/60"
],
"upstreams": [
{
"dial": "svcName.namespace.svc.cluster.local:80"
}
]
}
]
}

0 comments on commit 7414ecd

Please sign in to comment.