mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-23 14:39:40 -05:00
docs: add Helm chart for VPN connectivity (#2577)
Co-authored-by: 3u13r <lc@edgeless.systems>
This commit is contained in:
parent
968cdc1a38
commit
284c7e99d1
23
dev-docs/howto/vpn/.helmignore
Normal file
23
dev-docs/howto/vpn/.helmignore
Normal file
@ -0,0 +1,23 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
7
dev-docs/howto/vpn/Chart.yaml
Normal file
7
dev-docs/howto/vpn/Chart.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
apiVersion: v2
|
||||
name: vpn
|
||||
description: A VPN server for Constellation
|
||||
|
||||
type: application
|
||||
|
||||
version: 0.1.0
|
36
dev-docs/howto/vpn/README.md
Normal file
36
dev-docs/howto/vpn/README.md
Normal file
@ -0,0 +1,36 @@
|
||||
# Constellation VPN
|
||||
|
||||
This Helm chart deploys a VPN server to your Constellation cluster.
|
||||
|
||||
## Installation
|
||||
|
||||
1. Create and populate the configuration.
|
||||
|
||||
```sh
|
||||
helm inspect values . >config.yaml
|
||||
```
|
||||
|
||||
2. Install the Helm chart.
|
||||
|
||||
```sh
|
||||
helm install -f config.yaml vpn .
|
||||
```
|
||||
|
||||
3. Follow the post-installation instructions displayed by the CLI.
|
||||
|
||||
## Architecture
|
||||
|
||||
The VPN server is deployed as a `StatefulSet` to the cluster. It hosts the VPN frontend component, which is responsible for relaying traffic between the pod and the on-prem network, and the routing components that provide access to Constellation resources. The frontend supports IPSec and Wireguard.
|
||||
|
||||
The VPN frontend is exposed with a public LoadBalancer to be accessible from the on-prem network. Traffic that reaches the VPN server pod is split into two categories: pod IPs and service IPs.
|
||||
|
||||
The pod IP range is NATed with an iptables rule. On-prem worklaods can establish connections to a pod IP, but the Constellation workloads will see the client IP translated to that of the VPN frontend pod.
|
||||
|
||||
The service IP range is handed to a transparent proxy running in the VPN frontend pod, which relays the connection to a backend pod. This is necessary because of the load-balancing mechanism of Cilium, which assumes service IP traffic to originate from the Constellation cluster itself. As for pod IP ranges, Constellation pods will only see the translated client address.
|
||||
|
||||
## Limitations
|
||||
|
||||
* Service IPs need to be proxied by the VPN frontend pod. This is a single point of failure, and it may become a bottleneck.
|
||||
* IPs are NATed, so the Constellation pods won't see the real on-prem IPs.
|
||||
* NetworkPolicy can't be applied selectively to the on-prem ranges.
|
||||
* No connectivity from Constellation to on-prem workloads.
|
11
dev-docs/howto/vpn/files/strongswan/charon-logging.conf
Normal file
11
dev-docs/howto/vpn/files/strongswan/charon-logging.conf
Normal file
@ -0,0 +1,11 @@
|
||||
charon {
|
||||
filelog {
|
||||
stderr {
|
||||
time_format = %b %e %T
|
||||
ike_name = yes
|
||||
default = 1
|
||||
ike = 2
|
||||
flush_line = yes
|
||||
}
|
||||
}
|
||||
}
|
13
dev-docs/howto/vpn/files/strongswan/entrypoint.sh
Normal file
13
dev-docs/howto/vpn/files/strongswan/entrypoint.sh
Normal file
@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
# The charon binary is not included in the PATH generated by nixery.dev, find it manually.
|
||||
charon="$(dirname "$(readlink -f "$(command -v charon-systemd)")")/../libexec/ipsec/charon"
|
||||
|
||||
"${charon}" &
|
||||
|
||||
while ! swanctl --stats > /dev/null 2> /dev/null; do
|
||||
sleep 1
|
||||
done
|
||||
swanctl --load-all
|
||||
|
||||
wait
|
38
dev-docs/howto/vpn/files/tproxy-setup.sh
Normal file
38
dev-docs/howto/vpn/files/tproxy-setup.sh
Normal file
@ -0,0 +1,38 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
### Pod IPs ###
|
||||
|
||||
# Pod IPs are just NATed.
|
||||
|
||||
iptables -t nat -N VPN_POST || iptables -t nat -F VPN_POST
|
||||
|
||||
for cidr in ${VPN_PEER_CIDRS}; do
|
||||
iptables -t nat -A VPN_POST -s "${cidr}" -d "${VPN_POD_CIDR}" -j MASQUERADE
|
||||
done
|
||||
|
||||
iptables -t nat -C POSTROUTING -j VPN_POST || iptables -t nat -A POSTROUTING -j VPN_POST
|
||||
|
||||
### Service IPs ###
|
||||
|
||||
# Service IPs need to be connected to locally to trigger the cgroup connect hook, thus we send them to the transparent proxy.
|
||||
|
||||
# Packets with mark 1 are for tproxy and need to be delivered locally.
|
||||
# For more information see: https://www.kernel.org/doc/Documentation/networking/tproxy.txt
|
||||
pref=42
|
||||
table=42
|
||||
mark=0x1/0x1
|
||||
ip rule add pref "${pref}" fwmark "${mark}" lookup "${table}"
|
||||
ip route replace local 0.0.0.0/0 dev lo table "${table}"
|
||||
|
||||
iptables -t mangle -N VPN_PRE || iptables -t mangle -F VPN_PRE
|
||||
|
||||
for cidr in ${VPN_PEER_CIDRS}; do
|
||||
for proto in tcp udp; do
|
||||
iptables -t mangle -A VPN_PRE -p "${proto}" -s "${cidr}" -d "${VPN_SERVICE_CIDR}" \
|
||||
-j TPROXY --tproxy-mark "${mark}" --on-port 61001
|
||||
done
|
||||
done
|
||||
|
||||
iptables -t mangle -C PREROUTING -j VPN_PRE || iptables -t mangle -A PREROUTING -j VPN_PRE
|
13
dev-docs/howto/vpn/files/wireguard-setup.sh
Normal file
13
dev-docs/howto/vpn/files/wireguard-setup.sh
Normal file
@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
dev=vpn_wg0
|
||||
|
||||
ip link add dev "${dev}" type wireguard
|
||||
wg setconf "${dev}" /etc/wireguard/wg.conf
|
||||
ip link set dev "${dev}" up
|
||||
|
||||
for cidr in ${VPN_PEER_CIDRS}; do
|
||||
ip route replace "${cidr}" dev "${dev}"
|
||||
done
|
40
dev-docs/howto/vpn/templates/_helpers.tpl
Normal file
40
dev-docs/howto/vpn/templates/_helpers.tpl
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
{{- define "..name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 42 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "..fullname" -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 42 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 42 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "..chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 42 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "..labels" -}}
|
||||
helm.sh/chart: {{ include "..chart" . }}
|
||||
{{ include "..selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "..selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "..name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "..commonEnv" -}}
|
||||
- name: VPN_PEER_CIDRS
|
||||
value: {{ join " " .Values.peerCIDRs | quote }}
|
||||
- name: VPN_POD_CIDR
|
||||
value: {{ .Values.podCIDR | quote }}
|
||||
- name: VPN_SERVICE_CIDR
|
||||
value: {{ .Values.serviceCIDR | quote }}
|
||||
{{- end }}
|
27
dev-docs/howto/vpn/templates/configmaps.yaml
Normal file
27
dev-docs/howto/vpn/templates/configmaps.yaml
Normal file
@ -0,0 +1,27 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "..fullname" . }}-tproxy
|
||||
labels: {{- include "..labels" . | nindent 4 }}
|
||||
data:
|
||||
{{ (.Files.Glob "files/tproxy-setup.sh").AsConfig | indent 2 }}
|
||||
---
|
||||
{{- if .Values.wireguard.enabled }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "..fullname" . }}-wg
|
||||
labels: {{- include "..labels" . | nindent 4 }}
|
||||
data:
|
||||
{{ (.Files.Glob "files/wireguard-setup.sh").AsConfig | indent 2 }}
|
||||
{{- end }}
|
||||
---
|
||||
{{ if .Values.ipsec.enabled }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "..fullname" . }}-strongswan
|
||||
labels: {{- include "..labels" . | nindent 4 }}
|
||||
data:
|
||||
{{ (.Files.Glob "files/strongswan/*").AsConfig | indent 2 }}
|
||||
{{- end }}
|
21
dev-docs/howto/vpn/templates/secrets.yaml
Normal file
21
dev-docs/howto/vpn/templates/secrets.yaml
Normal file
@ -0,0 +1,21 @@
|
||||
{{- if .Values.wireguard.enabled }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "..fullname" . }}-wg
|
||||
labels:
|
||||
{{- include "..labels" . | nindent 4 }}
|
||||
data:
|
||||
wg.conf: {{ include "wireguard.conf" . | b64enc }}
|
||||
{{- end }}
|
||||
---
|
||||
{{ if .Values.ipsec.enabled }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "..fullname" . }}-strongswan
|
||||
labels:
|
||||
{{- include "..labels" . | nindent 4 }}
|
||||
data:
|
||||
swanctl.conf: {{ include "strongswan.swanctl-conf" . | b64enc }}
|
||||
{{- end }}
|
26
dev-docs/howto/vpn/templates/service.yaml
Normal file
26
dev-docs/howto/vpn/templates/service.yaml
Normal file
@ -0,0 +1,26 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "..fullname" . }}-lb
|
||||
labels:
|
||||
{{- include "..labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
selector:
|
||||
{{- include "..selectorLabels" . | nindent 4 }}
|
||||
component: frontend
|
||||
externalTrafficPolicy: Local
|
||||
ports:
|
||||
{{- if .Values.ipsec.enabled }}
|
||||
- name: isakmp
|
||||
protocol: UDP
|
||||
port: 500
|
||||
- name: ipsec-nat-t
|
||||
protocol: UDP
|
||||
port: 4500
|
||||
{{- end }}
|
||||
{{- if .Values.wireguard.enabled }}
|
||||
- name: wg
|
||||
protocol: UDP
|
||||
port: {{ .Values.wireguard.port }}
|
||||
{{- end }}
|
26
dev-docs/howto/vpn/templates/strongswan-secret.tpl
Normal file
26
dev-docs/howto/vpn/templates/strongswan-secret.tpl
Normal file
@ -0,0 +1,26 @@
|
||||
{{- define "strongswan.swanctl-conf" }}
|
||||
connections {
|
||||
net-net {
|
||||
remote_addrs = {{ .Values.ipsec.peer }}
|
||||
local {
|
||||
auth = psk
|
||||
}
|
||||
remote {
|
||||
auth = psk
|
||||
}
|
||||
children {
|
||||
net-net {
|
||||
local_ts = {{ .Values.podCIDR }},{{ .Values.serviceCIDR }}
|
||||
remote_ts = {{ join "," .Values.peerCIDRs }}
|
||||
start_action = trap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
secrets {
|
||||
ike {
|
||||
secret = {{ quote .Values.ipsec.psk }}
|
||||
}
|
||||
}
|
||||
{{- end }}
|
78
dev-docs/howto/vpn/templates/strongswan-statefulset.yaml
Normal file
78
dev-docs/howto/vpn/templates/strongswan-statefulset.yaml
Normal file
@ -0,0 +1,78 @@
|
||||
{{ if .Values.ipsec.enabled -}}
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: {{ include "..fullname" . }}-frontend
|
||||
labels: {{- include "..labels" . | nindent 4 }}
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "..selectorLabels" . | nindent 6 }}
|
||||
component: frontend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "..selectorLabels" . | nindent 8 }}
|
||||
component: frontend
|
||||
spec:
|
||||
hostNetwork: false
|
||||
initContainers:
|
||||
- name: tproxy-setup
|
||||
image: nixery.dev/busybox/iptables
|
||||
command: ["/bin/sh", "-x", "/entrypoint.sh"]
|
||||
env: {{- include "..commonEnv" . | nindent 10 }}
|
||||
securityContext:
|
||||
capabilities:
|
||||
add: ["NET_ADMIN"]
|
||||
volumeMounts:
|
||||
- name: tproxy-setup
|
||||
mountPath: "/entrypoint.sh"
|
||||
subPath: "tproxy-setup.sh"
|
||||
readOnly: true
|
||||
containers:
|
||||
- name: tproxy
|
||||
# Image source: github.com/burgerdev/go-tproxy
|
||||
image: ghcr.io/burgerdev/go-tproxy:latest
|
||||
command: ["/tproxy", "--port=61001", "--nat=true"]
|
||||
securityContext:
|
||||
capabilities:
|
||||
add: ["NET_RAW"]
|
||||
- name: strongswan
|
||||
image: "nixery.dev/shell/strongswan"
|
||||
command: ["/bin/sh", "-x", "/entrypoint.sh"]
|
||||
securityContext:
|
||||
capabilities:
|
||||
add: ["NET_ADMIN"]
|
||||
volumeMounts:
|
||||
- name: strongswan
|
||||
mountPath: "/entrypoint.sh"
|
||||
subPath: "entrypoint.sh"
|
||||
readOnly: true
|
||||
- name: strongswan
|
||||
mountPath: "/etc/strongswan.d/charon-logging.conf"
|
||||
subPath: "charon-logging.conf"
|
||||
readOnly: true
|
||||
- name: strongswan
|
||||
mountPath: "/etc/swanctl/swanctl.conf"
|
||||
subPath: "swanctl.conf"
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: tproxy-setup
|
||||
configMap:
|
||||
name: {{ include "..fullname" . }}-tproxy
|
||||
- name: strongswan
|
||||
projected:
|
||||
sources:
|
||||
- secret:
|
||||
name: {{ include "..fullname" . }}-strongswan
|
||||
items:
|
||||
- key: swanctl.conf
|
||||
path: swanctl.conf
|
||||
- configMap:
|
||||
name: {{ include "..fullname" . }}-strongswan
|
||||
items:
|
||||
- key: entrypoint.sh
|
||||
path: entrypoint.sh
|
||||
- key: charon-logging.conf
|
||||
path: charon-logging.conf
|
||||
{{- end }}
|
14
dev-docs/howto/vpn/templates/wireguard-secret.tpl
Normal file
14
dev-docs/howto/vpn/templates/wireguard-secret.tpl
Normal file
@ -0,0 +1,14 @@
|
||||
{{- define "wireguard.conf" }}
|
||||
[Interface]
|
||||
ListenPort = {{ .Values.wireguard.port }}
|
||||
PrivateKey = {{ .Values.wireguard.private_key }}
|
||||
[Peer]
|
||||
PublicKey = {{ .Values.wireguard.peer_key }}
|
||||
AllowedIPs = {{ join "," .Values.peerCIDRs }}
|
||||
{{- if .Values.wireguard.endpoint }}
|
||||
Endpoint = {{- .Values.wireguard.endpoint }}
|
||||
{{- end }}
|
||||
{{- if .Values.wireguard.keepAlive }}
|
||||
PersistentKeepalive = {{- .Values.wireguard.keepAlive }}
|
||||
{{- end }}
|
||||
{{ end }}
|
68
dev-docs/howto/vpn/templates/wireguard-statefulset.yaml
Normal file
68
dev-docs/howto/vpn/templates/wireguard-statefulset.yaml
Normal file
@ -0,0 +1,68 @@
|
||||
{{ if .Values.wireguard.enabled -}}
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: {{ include "..fullname" . }}-frontend
|
||||
labels: {{- include "..labels" . | nindent 4 }}
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "..selectorLabels" . | nindent 6 }}
|
||||
component: frontend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "..selectorLabels" . | nindent 8 }}
|
||||
component: frontend
|
||||
spec:
|
||||
hostNetwork: false
|
||||
initContainers:
|
||||
- name: tproxy-setup
|
||||
image: nixery.dev/busybox/iptables
|
||||
command: ["/bin/sh", "-x", "/entrypoint.sh"]
|
||||
env: {{- include "..commonEnv" . | nindent 10 }}
|
||||
securityContext:
|
||||
capabilities:
|
||||
add: ["NET_ADMIN"]
|
||||
volumeMounts:
|
||||
- name: tproxy-setup
|
||||
mountPath: "/entrypoint.sh"
|
||||
subPath: "tproxy-setup.sh"
|
||||
readOnly: true
|
||||
- name: wg-setup
|
||||
image: "nixery.dev/busybox/wireguard-tools"
|
||||
command: ["/bin/sh", "-x", "/etc/wireguard/wireguard-setup.sh"]
|
||||
env: {{- include "..commonEnv" . | nindent 10 }}
|
||||
securityContext:
|
||||
capabilities:
|
||||
add: ["NET_ADMIN"]
|
||||
volumeMounts:
|
||||
- name: wireguard
|
||||
mountPath: "/etc/wireguard"
|
||||
readOnly: true
|
||||
containers:
|
||||
- name: tproxy
|
||||
# Image source: github.com/burgerdev/go-tproxy
|
||||
image: ghcr.io/burgerdev/go-tproxy:latest
|
||||
command: ["/tproxy", "--port=61001", "--nat=true"]
|
||||
securityContext:
|
||||
capabilities:
|
||||
add: ["NET_RAW"]
|
||||
volumes:
|
||||
- name: tproxy-setup
|
||||
configMap:
|
||||
name: {{ include "..fullname" . }}-tproxy
|
||||
- name: wireguard
|
||||
projected:
|
||||
sources:
|
||||
- secret:
|
||||
name: {{ include "..fullname" . }}-wg
|
||||
items:
|
||||
- key: wg.conf
|
||||
path: wg.conf
|
||||
- configMap:
|
||||
name: {{ include "..fullname" . }}-wg
|
||||
items:
|
||||
- key: wireguard-setup.sh
|
||||
path: wireguard-setup.sh
|
||||
{{- end }}
|
39
dev-docs/howto/vpn/values.yaml
Normal file
39
dev-docs/howto/vpn/values.yaml
Normal file
@ -0,0 +1,39 @@
|
||||
|
||||
# Constellation Pod IP range to expose via VPN. The default is for GCP.
|
||||
podCIDR: "10.10.0.0/16"
|
||||
|
||||
# Constellation Service IPs to expose via VPN. The default is for GCP.
|
||||
serviceCIDR: "10.96.0.0/12"
|
||||
|
||||
# on-prem IP ranges to expose to Constellation. Must contain at least one CIDR.
|
||||
peerCIDRs: []
|
||||
|
||||
|
||||
# The sections below configure the VPN connectivity to the Constellation
|
||||
# cluster. Exactly one `enabled` must be set to true.
|
||||
|
||||
# IPSec configuration
|
||||
ipsec:
|
||||
enabled: false
|
||||
# pre-shared key used for authentication
|
||||
psk: ""
|
||||
# Address of the peer's gateway router.
|
||||
peer: ""
|
||||
|
||||
# Wireguard configuration
|
||||
wireguard:
|
||||
enabled: false
|
||||
|
||||
# If Wireguard is enabled, these fields for the Constellation side must be populated.
|
||||
private_key: ""
|
||||
peer_key: ""
|
||||
|
||||
# Listening port of the Constellation Wireguard.
|
||||
port: 51820
|
||||
|
||||
# Optional host:port of the on-prem Wireguard.
|
||||
endpoint: ""
|
||||
|
||||
# Optional interval for keep-alive packets in seconds. Setting this helps the on-prem server to
|
||||
# discover a restarted Constellation VPN frontend.
|
||||
keepAlive: ""
|
Loading…
Reference in New Issue
Block a user