Skip to content

Commit 39672c7

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 39672c7

11 files changed

Lines changed: 228 additions & 1 deletion

File tree

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ COPY validate.go validate.go
3030
COPY validate_test.go validate_test.go
3131
COPY config.go config.go
3232
COPY config_test.go config_test.go
33+
COPY userdata.go userdata.go
34+
COPY userdata_test.go userdata_test.go
3335

3436
RUN gofmt -l -d $(find . -type f -name '*.go' -not -path "./vendor/*")
3537

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,28 @@ 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+
86+
> **Important**: The proxy protocol configuration is applied when the tunnel exit server VM is provisioned and **cannot be changed afterwards**. If you need to change the proxy protocol setting for an existing service, you must delete the service (which will delete the tunnel and VM), then recreate it with the new annotation.
87+
6688
## Using IPVS for your Kubernetes networking?
6789

6890
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 := MakeExitServerUserdata(tokenValue, inletsVersion, proxyProto)
753763

754764
var host provision.BasicHost
755765

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

userdata.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) inlets Author(s) 2019. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
package main
5+
6+
// MakeExitServerUserdata makes a user-data script in bash to setup inlets
7+
// PRO with a systemd service and the given version. If proxyProto is non-empty,
8+
// the PROXY_PROTO environment variable is set in the service configuration.
9+
func MakeExitServerUserdata(authToken, version, proxyProto string) string {
10+
return `#!/bin/bash
11+
export AUTHTOKEN="` + authToken + `"
12+
export IP=$(curl -sfSL https://checkip.amazonaws.com)
13+
export PROXY_PROTO="` + proxyProto + `"
14+
15+
curl -SLsf https://github.com/inlets/inlets-pro/releases/download/` + version + `/inlets-pro -o /tmp/inlets-pro && \
16+
chmod +x /tmp/inlets-pro && \
17+
mv /tmp/inlets-pro /usr/local/bin/inlets-pro
18+
19+
cat > /etc/systemd/system/inlets-pro.service <<EOF
20+
[Unit]
21+
Description=inlets TCP server
22+
After=network.target
23+
24+
[Service]
25+
Type=simple
26+
Restart=always
27+
RestartSec=2
28+
StartLimitInterval=0
29+
EnvironmentFile=/etc/default/inlets-pro
30+
ExecStart=/usr/local/bin/inlets-pro tcp server --auto-tls --auto-tls-san="\${IP}" --token="\${AUTHTOKEN}" --proxy-protocol="\${PROXY_PROTO}"
31+
32+
[Install]
33+
WantedBy=multi-user.target
34+
EOF
35+
36+
echo "AUTHTOKEN=$AUTHTOKEN" >> /etc/default/inlets-pro && \
37+
echo "IP=$IP" >> /etc/default/inlets-pro && \
38+
echo "PROXY_PROTO=$PROXY_PROTO" >> /etc/default/inlets-pro && \
39+
systemctl daemon-reload && \
40+
systemctl start inlets-pro && \
41+
systemctl enable inlets-pro
42+
`
43+
}

userdata_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright (c) inlets Author(s) 2019. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
package main
5+
6+
import (
7+
"testing"
8+
)
9+
10+
func Test_MakeUserdata_InletsPro(t *testing.T) {
11+
userData := MakeExitServerUserdata("auth", "0.7.0", "")
12+
13+
wantUserdata := `#!/bin/bash
14+
export AUTHTOKEN="auth"
15+
export IP=$(curl -sfSL https://checkip.amazonaws.com)
16+
export PROXY_PROTO=""
17+
18+
curl -SLsf https://github.com/inlets/inlets-pro/releases/download/0.7.0/inlets-pro -o /tmp/inlets-pro && \
19+
chmod +x /tmp/inlets-pro && \
20+
mv /tmp/inlets-pro /usr/local/bin/inlets-pro
21+
22+
cat > /etc/systemd/system/inlets-pro.service <<EOF
23+
[Unit]
24+
Description=inlets TCP server
25+
After=network.target
26+
27+
[Service]
28+
Type=simple
29+
Restart=always
30+
RestartSec=2
31+
StartLimitInterval=0
32+
EnvironmentFile=/etc/default/inlets-pro
33+
ExecStart=/usr/local/bin/inlets-pro tcp server --auto-tls --auto-tls-san="\${IP}" --token="\${AUTHTOKEN}" --proxy-protocol="\${PROXY_PROTO}"
34+
35+
[Install]
36+
WantedBy=multi-user.target
37+
EOF
38+
39+
echo "AUTHTOKEN=$AUTHTOKEN" >> /etc/default/inlets-pro && \
40+
echo "IP=$IP" >> /etc/default/inlets-pro && \
41+
echo "PROXY_PROTO=$PROXY_PROTO" >> /etc/default/inlets-pro && \
42+
systemctl daemon-reload && \
43+
systemctl start inlets-pro && \
44+
systemctl enable inlets-pro
45+
`
46+
47+
if userData != wantUserdata {
48+
t.Errorf("want: %s, but got: %s", wantUserdata, userData)
49+
}
50+
}
51+
52+
func Test_MakeUserdata_InletsPro_WithProxyProto(t *testing.T) {
53+
userData := MakeExitServerUserdata("auth", "0.7.0", "v1")
54+
55+
wantUserdata := `#!/bin/bash
56+
export AUTHTOKEN="auth"
57+
export IP=$(curl -sfSL https://checkip.amazonaws.com)
58+
export PROXY_PROTO="v1"
59+
60+
curl -SLsf https://github.com/inlets/inlets-pro/releases/download/0.7.0/inlets-pro -o /tmp/inlets-pro && \
61+
chmod +x /tmp/inlets-pro && \
62+
mv /tmp/inlets-pro /usr/local/bin/inlets-pro
63+
64+
cat > /etc/systemd/system/inlets-pro.service <<EOF
65+
[Unit]
66+
Description=inlets TCP server
67+
After=network.target
68+
69+
[Service]
70+
Type=simple
71+
Restart=always
72+
RestartSec=2
73+
StartLimitInterval=0
74+
EnvironmentFile=/etc/default/inlets-pro
75+
ExecStart=/usr/local/bin/inlets-pro tcp server --auto-tls --auto-tls-san="\${IP}" --token="\${AUTHTOKEN}" --proxy-protocol="\${PROXY_PROTO}"
76+
77+
[Install]
78+
WantedBy=multi-user.target
79+
EOF
80+
81+
echo "AUTHTOKEN=$AUTHTOKEN" >> /etc/default/inlets-pro && \
82+
echo "IP=$IP" >> /etc/default/inlets-pro && \
83+
echo "PROXY_PROTO=$PROXY_PROTO" >> /etc/default/inlets-pro && \
84+
systemctl daemon-reload && \
85+
systemctl start inlets-pro && \
86+
systemctl enable inlets-pro
87+
`
88+
89+
if userData != wantUserdata {
90+
t.Errorf("want: %s, but got: %s", wantUserdata, userData)
91+
}
92+
}

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())

0 commit comments

Comments
 (0)