feat: add ZeroTier for remote access

- Fully open source
- Has free hosted version (my.zerotier.com)
- Can be automated with Terraform
- Pretty good performance with UDP hole punching
This commit is contained in:
Khue Doan 2022-08-27 13:24:57 +07:00
parent 1d2d73a7a3
commit 8447502d54
12 changed files with 220 additions and 8 deletions

View File

@ -47,6 +47,7 @@ More information can be found in [the roadmap](#roadmap) below.
- [x] Modular architecture, easy to add or remove features/components
- [x] Automated certificate management
- [x] Automatically update DNS records for exposed services
- [x] VPN
- [x] Expose services to the internet securely with [Cloudflare Tunnel](https://www.cloudflare.com/products/tunnel/)
- [x] CI/CD platform
- [x] Private container registry
@ -195,6 +196,11 @@ They can't capture all the project's features, but they are sufficient to get a
<td><a href="https://www.vaultproject.io">Vault</a></td>
<td>Secrets and encryption management system</td>
</tr>
<tr>
<td><img width="32" src="https://docs.zerotier.com/img/ZeroTierIcon.png"></td>
<td><a href="https://zerotier.com">ZeroTier</a></td>
<td>VPN without port-forwarding</td>
</tr>
</table>
## Get Started

View File

@ -12,6 +12,7 @@ Below is a list of external resources and why we need them (also see some [alter
| Terraform Cloud | Workspace | Terraform state backend |
| Cloudflare | DNS | DNS and [DNS-01 challenge](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge) for certificates |
| Cloudflare | Tunnel | Public services to the internet without port-forwarding |
| ZeroTier | Virtual network | Use as VPN to access home network from anywhere (with [UDP hole punching](https://en.wikipedia.org/wiki/UDP_hole_punching)) |
<!-- | Minio | Bucket | Onsite backup | -->
<!-- | AWS | S3 Glacier | Offsite backup | -->
@ -49,6 +50,11 @@ If you decide to use a [different Terraform backend](https://www.terraform.io/la
<!-- └── Is in - 117.xxx.xxx.xxx, 2402:xxx:xxx:xxx:xxx:xxx:xxx:xxx -->
<!-- ``` -->
### ZeroTier
- Create a ZeroTier account <https://my.zerotier.com>
- Generate a new API Token at <https://my.zerotier.com/account>
<!-- ### Create Minio keys -->
<!-- TODO: skip this for now -->
@ -59,7 +65,21 @@ If you decide to use a [different Terraform backend](https://www.terraform.io/la
## Alternatives
- Terraform Cloud: any other [Terraform backends](https://www.terraform.io/language/settings/backends)
- Cloudflare DNS: see [manual DNS setup](../../tutorials/manual-dns-setup.md)
- Cloudflare Tunnel: you can create a small VPS in the cloud and utilize Wireguard and HAProxy to route traffic via it, or just use simple port-forwarding if it's available (see also [awesome tunneling](https://github.com/anderspitman/awesome-tunneling))
To avoid vendor lock-in, each external provider must have an equivalent alternative that is easy to replace:
- Terraform Cloud:
- Any other [Terraform backends](https://www.terraform.io/language/settings/backends)
- Cloudflare DNS:
- Update cert-manager and external-dns to use a different provider
- [Manual DNS setup](../../tutorials/manual-dns-setup.md)
- Cloudflare Tunnel:
- Use port-forwarding if it's available
- Create a small VPS in the cloud and utilize Wireguard and HAProxy to route traffic via it
- Access everything via VPN
- See also [awesome tunneling](https://github.com/anderspitman/awesome-tunneling)
- ZeroTier virtual network:
- [Host your own ZeroTier](https://docs.zerotier.com/self-hosting/introduction)
- [Tailscale](https://tailscale.com) (closed source, but you can use [Headscale](https://github.com/juanfont/headscale) to host your own Tailscale control server)
- [Netmaker](https://www.netmaker.org) (there's no hosted version, you'll need to host your own server)
- Wireguard server (requires port-forwarding)
<!-- - Minio and S3 Glacier: any S3 compatible object storage, such as Backblaze B2, Minio... -->

View File

@ -4,3 +4,11 @@ module "cloudflare" {
cloudflare_email = var.cloudflare_email
cloudflare_api_key = var.cloudflare_api_key
}
module "zerotier" {
source = "./modules/zerotier"
zerotier_central_token = var.zerotier_central_token
bridged_routes = [
"192.168.1.0/24" # TODO add this to configure script
]
}

View File

@ -0,0 +1,53 @@
locals {
router_ip = cidrhost(var.managed_route, 1) # Use the second IP in the VPN subnet as the router
}
resource "zerotier_network" "network" {
name = var.name
description = var.description
private = true
route {
target = var.managed_route
}
dynamic "route" {
for_each = var.bridged_routes
content {
target = route.value
via = local.router_ip
}
}
assignment_pool {
start = cidrhost(var.managed_route, 0)
end = cidrhost(var.managed_route, -1)
}
}
resource "zerotier_identity" "router" {}
resource "zerotier_member" "router" {
network_id = zerotier_network.network.id
name = "router"
member_id = zerotier_identity.router.id
allow_ethernet_bridging = true
no_auto_assign_ips = true
ip_assignments = [
local.router_ip
]
}
resource "kubernetes_secret" "router" {
metadata {
name = "zerotier-router"
namespace = "zerotier"
}
data = {
ZEROTIER_NETWORK_ID = zerotier_network.network.id
ZEROTIER_IDENTITY_PUBLIC = zerotier_identity.router.public_key
ZEROTIER_IDENTITY_SECRET = zerotier_identity.router.private_key
}
}

View File

@ -0,0 +1,34 @@
variable "zerotier_central_url" {
description = "ZeroTier Central API endpoint"
type = string
default = "https://my.zerotier.com/api" # https://github.com/zerotier/go-ztcentral/blob/4d397d1e82c043a5376789177ad55536044d69ce/client.go#L44
}
variable "zerotier_central_token" {
description = "ZeroTier Central API Token"
type = string
sensitive = true
}
variable "name" {
description = "Network name"
type = string
default = "homelab"
}
variable "description" {
description = "Network description"
type = string
default = "Homelab network"
}
variable "managed_route" {
description = "ZeroTier managed route"
type = string
default = "10.147.17.0/24"
}
variable "bridged_routes" {
description = "List of bridged routes" # TODO
type = list(string)
}

View File

@ -0,0 +1,13 @@
terraform {
required_providers {
zerotier = {
source = "zerotier/zerotier"
version = "~> 1.2.0"
}
}
}
provider "zerotier" {
zerotier_central_url = var.zerotier_central_url
zerotier_central_token = var.zerotier_central_token
}

View File

@ -12,3 +12,4 @@
- external-dns
- k8up-operator
- tekton-pipelines
- zerotier

View File

@ -1,3 +1,4 @@
cloudflare_email = "{{ cloudflare_email }}"
cloudflare_api_key = "{{ cloudflare_api_key }}"
cloudflare_account_id = "{{ cloudflare_account_id }}"
zerotier_central_token = "{{ zerotier_central_token }}"

View File

@ -9,6 +9,8 @@
- name: cloudflare_account_id
prompt: Enter Cloudflare account ID
private: false
- name: zerotier_central_token
prompt: Enter ZeroTier Central API Token
tasks:
- name: Render environment file
template:

View File

@ -10,3 +10,7 @@ variable "cloudflare_api_key" {
variable "cloudflare_account_id" {
type = string
}
variable "zerotier_central_token" {
type = string
}

View File

@ -0,0 +1,65 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: zerotier-router
namespace: zerotier
labels:
app: zerotier-router
spec:
selector:
matchLabels:
app: zerotier-router
replicas: 1
template:
metadata:
labels:
app: zerotier-router
spec:
containers:
- name: zerotier-router
image: zerotier/zerotier:latest # TODO use tag
command:
- "sh"
- "-c"
args: # TODO optimize this
- |
# TODO install this on upstream image?
apt-get install -y iptables
# TODO is there a better way to get the interface name?
export PHY_IFACE="$(ip route | grep ${POD_IP} | cut -d ' ' -f 3)"
export ZT_IFACE=zt0
# Override the default random interface name
mkdir -p /var/lib/zerotier-one
echo "${ZEROTIER_NETWORK_ID}=${ZT_IFACE}" >> /var/lib/zerotier-one/devicemap
iptables -t nat -A POSTROUTING -o $PHY_IFACE -j MASQUERADE
iptables -A FORWARD -i $PHY_IFACE -o $ZT_IFACE -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -i $ZT_IFACE -o $PHY_IFACE -j ACCEPT
/entrypoint.sh "${ZEROTIER_NETWORK_ID}"
resources:
requests:
cpu: 100m
memory: 128Mi
env:
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
envFrom:
- secretRef:
name: zerotier-router
securityContext:
capabilities:
add:
- NET_ADMIN
volumeMounts:
- name: dev-net-tun
mountPath: /dev/net/tun
volumes:
- name: dev-net-tun
hostPath:
path: /dev/net/tun
type: CharDevice

View File

@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml