Skip to content

Commit 1b50633

Browse files
committed
Add proxy protocol support for tunnel servers
Add support for configuring proxy protocol on inlets tunnel servers provisioned by the operator. When enabled, the tunnel server is started with the --proxy-proto flag so that the original client IP address is preserved and forwarded to upstream services. The proxy protocol can be set globally using the --tunnel-proxy-proto flag or per-service using the operator.inlets.dev/proxy-proto annotation. The annotation takes precedence over the global flag. Accepted values are 'v1', 'v2', or empty (disabled). Configuration options: - Flag: --tunnel-proxy-proto=v1|v2 - Helm value: tunnelProxyProto - Annotation: operator.inlets.dev/proxy-proto (per-service override) Signed-off-by: Han Verstraete (OpenFaaS Ltd) <han@openfaas.com>
1 parent 081e522 commit 1b50633

11 files changed

Lines changed: 102 additions & 2 deletions

File tree

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,26 @@ Install the chart with `annotatedOnly: true`, then run:
6363
kubectl annotate service nginx-1 operator.inlets.dev/manage=1
6464
```
6565

66+
## Proxy Protocol support
67+
68+
Proxy protocol can be enabled on tunnel exit servers so that the original client IP address is preserved and forwarded to your services.
69+
70+
To enable proxy protocol globally for all tunnel servers, set the `tunnelProxyProto` value in the Helm chart:
71+
72+
```yaml
73+
tunnelProxyProto: "v2"
74+
```
75+
76+
Allowed values are `v1`, `v2`, or `""` (disabled).
77+
78+
Alternatively, you can enable proxy protocol on a per-service basis by annotating individual services. This can be used instead of the global setting, or to override it for specific services:
79+
80+
```bash
81+
kubectl annotate service nginx-1 operator.inlets.dev/proxy-proto=v2
82+
```
83+
84+
The per-service annotation takes precedence over the global setting when both are configured.
85+
6686
## Using IPVS for your Kubernetes networking?
6787

6888
For IPVS, you need to declare a Tunnel Custom Resource instead of using the LoadBalancer field.

chart/inlets-operator/templates/deployment.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ spec:
6262
{{- if .Values.maxClientMemory }}
6363
- "-max-client-memory={{.Values.maxClientMemory}}"
6464
{{- end }}
65+
{{- if .Values.tunnelProxyProto }}
66+
- "-tunnel-proxy-proto={{.Values.tunnelProxyProto}}"
67+
{{- end }}
6568
resources:
6669
{{- toYaml .Values.resources | nindent 12 }}
6770
env:

chart/inlets-operator/values.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ resources:
5959
# Set a maximum memory limit for the inlets client Deployments
6060
maxClientMemory: 128Mi
6161

62+
# Enable proxy protocol on tunnel exit servers.
63+
# Allowed values: "v1", "v2", or "" (disabled)
64+
tunnelProxyProto: ""
65+
6266
nodeSelector: {}
6367
tolerations: []
6468
affinity: {}

config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type InletsProConfig struct {
3232
LicenseFile string
3333
ClientImage string
3434
InletsRelease string
35+
ProxyProto string
3536
}
3637

3738
func (c InletsProConfig) GetLicenseKey() (string, error) {

controller.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import (
4949
const controllerAgentName = "inlets-operator"
5050
const inletsPROControlPort = 8123
5151
const inletsPortsAnnotation = "inlets.dev/ports"
52+
const proxyProtoAnnotation = "operator.inlets.dev/proxy-proto"
5253
const licenseSecretName = "inlets-license"
5354

5455
const (
@@ -593,6 +594,10 @@ func createTunnelResource(service *corev1.Service, c *Controller) error {
593594
return nil
594595
}
595596

597+
if v, ok := service.Annotations[proxyProtoAnnotation]; ok && v != "v1" && v != "v2" {
598+
return fmt.Errorf("%s annotation must be 'v1' or 'v2', got: %s", proxyProtoAnnotation, v)
599+
}
600+
596601
klog.Infof("Creating Tunnel: %s.%s\n", name, namespace)
597602

598603
tunnel := &inletsv1alpha1.Tunnel{
@@ -749,7 +754,12 @@ func getHostConfig(c *Controller, tunnel *inletsv1alpha1.Tunnel, service *corev1
749754
return provision.BasicHost{}, err
750755
}
751756

752-
userData := provision.MakeExitServerUserdata(tokenValue, inletsVersion)
757+
proxyProto := c.infraConfig.ProConfig.ProxyProto
758+
if v, ok := service.Annotations[proxyProtoAnnotation]; ok {
759+
proxyProto = v
760+
}
761+
762+
userData := provision.MakeExitServerUserdataWithProxyProto(tokenValue, inletsVersion, proxyProto)
753763

754764
var host provision.BasicHost
755765

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ module github.com/inlets/inlets-operator
22

33
go 1.25.0
44

5+
replace github.com/inlets/cloud-provision => ../cloud-provision
6+
57
require (
68
github.com/google/go-cmp v0.7.0
79
github.com/inlets/cloud-provision v0.7.1

main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ func main() {
6161
flag.StringVar(&infra.ProConfig.LicenseFile, "license-file", "", "Supply a file to read for the inlets-pro license")
6262
flag.StringVar(&infra.ProConfig.ClientImage, "client-image", "ghcr.io/inlets/inlets-pro:"+defaultRelease, "Container image for inlets tunnel clients run in the cluster")
6363
flag.StringVar(&infra.ProConfig.InletsRelease, "inlets-release", defaultRelease, "Inlets version to use to create tunnel servers")
64+
flag.StringVar(&infra.ProConfig.ProxyProto, "tunnel-proxy-proto", "", "Enable proxy protocol on tunnel servers, allowed values: v1 or v2")
6465

6566
flag.StringVar(&infra.MaxClientMemory, "max-client-memory", "128Mi", "Maximum memory limit for the tunnel clients")
6667

validate.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ func validateFlags(c InfraConfig) error {
3737
return fmt.Errorf("region required for provider: %s", c.Provider)
3838
}
3939
}
40+
if len(c.ProConfig.ProxyProto) > 0 {
41+
if c.ProConfig.ProxyProto != "v1" && c.ProConfig.ProxyProto != "v2" {
42+
return fmt.Errorf("tunnel-proxy-proto must be 'v1' or 'v2', got: %s", c.ProConfig.ProxyProto)
43+
}
44+
}
45+
4046
if len(c.MaxClientMemory) > 0 {
4147
if _, err := resource.ParseQuantity(c.MaxClientMemory); err != nil {
4248
return fmt.Errorf("invalid memory value: %s", err.Error())

validate_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,49 @@ func Test_validateFlags_EmptyMemoryValue(t *testing.T) {
163163
}
164164
}
165165

166+
func Test_validateFlags_ProxyProto_Valid(t *testing.T) {
167+
tests := []struct {
168+
name string
169+
value string
170+
}{
171+
{"v1", "v1"},
172+
{"v2", "v2"},
173+
{"empty", ""},
174+
}
175+
176+
for _, tt := range tests {
177+
t.Run(tt.name, func(t *testing.T) {
178+
c := InfraConfig{
179+
Provider: "digitalocean",
180+
Region: "lon1",
181+
AccessKey: "set",
182+
ProConfig: InletsProConfig{ProxyProto: tt.value},
183+
}
184+
err := validateFlags(c)
185+
if err != nil {
186+
t.Errorf("expected no error, got: %s", err.Error())
187+
}
188+
})
189+
}
190+
}
191+
192+
func Test_validateFlags_ProxyProto_Invalid(t *testing.T) {
193+
c := InfraConfig{
194+
Provider: "digitalocean",
195+
Region: "lon1",
196+
AccessKey: "set",
197+
ProConfig: InletsProConfig{ProxyProto: "v3"},
198+
}
199+
err := validateFlags(c)
200+
want := "tunnel-proxy-proto must be 'v1' or 'v2', got: v3"
201+
if err == nil {
202+
t.Fatalf("expected an error")
203+
}
204+
if err.Error() != want {
205+
t.Errorf("expected error: %q, got: %q", want, err.Error())
206+
}
207+
}
208+
166209
func Test_validateFlags_Hetzner(t *testing.T) {
167210
c := InfraConfig{
168211
Provider: "hetzner",

vendor/github.com/inlets/cloud-provision/provision/userdata.go

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)