背景

Pangolin和NetBird都是开源的、现代的、基于WireGuard的零信任网络解决方案,在开源社区坐拥上万Star。

在前文中,我介绍了NetBird的部署方法,截至目前,运行良好。然而,还是遇到了两个问题导致使用体验不高。

  • 一个是国内网络带来的体验问题。每次启动NetBird的manager容器需要从官网拉取GeoIP的数据库,导致服务卡住,具体信息请参阅NetBird文章的填坑章节。
  • 另一个是NetBird IOS的客户端的更新频率远远落后桌面客户端,功能上有缺失。比如在IOS上无法通过域名资源访问其他Peer的服务,使得对于IOS内部服务访问基本不可用。

正好最近遇到了和别人共享自建服务的需求,索性研究一下Pangolin,如果可行就进行替换。

介绍

Pangolin的官方介绍如下

Pangolin 是一个自托管隧道反向代理服务器,具有身份和上下文感知的访问控制,旨在轻松地暴露和保护任何地方运行的应用程序。Pangolin 充当中央枢纽,通过加密隧道连接隔离的网络——即使那些在严格的防火墙后面——从而无需打开端口或需要 VPN 就能轻松访问远程服务。

简单来说,就是整合了

  • Traefik反向代理:服务暴露和用户入口
  • 单点登录认证:核心用户登录和安全认证
  • WireGuard安全通讯:被代理的服务可通过WireGuard互联,因此对于家庭内部的服务来说,无需DDNS或者内网穿透服务了

image.png

和NetBird这类纯组网工具相比,Pangolin多了一个反向代理组件,从而替换了传统VPN必备的客户端组件(当然也可选),让自托管服务的管理从网络层来到了应用层。

对于自己在家里折腾HomeLab或者NAS的同学来说,使用Pangolin后,自己和家人、朋友可以像访问互联网网站(必应、百度等)一样直接访问家里的服务了!

部署

因为我的云主机使用1Panel进行管理,已经部署了捆绑的反向代理服务OpenResty,并且预置了SSL证书管理,所以,下面的部署过程适用于和我场景类似的同学,即:拥有其他反向代理且自行管理SSL证书。安装步骤参考官方手动安装指南

如果你希望完全由Pangolin托管(比如全新的服务器环境,自己懒得配置反代、SSL),可以参考官方快速安装指南,一行命令完成系统部署。

准备

你需要:

  • 一个有公网IP的服务器/VPS/云主机,用于部署并提供公网访问。系统已经具有正常工作的反向代理和SSL证书。系统能够顺利拉取Docker镜像。
  • 一个域名,已经完成配置DNS指向公网IP,用于反向代理的请求路由。如果你从未接触过反向代理和DNS配置,请学习了解一下DNS泛解析的概念,十分有用。
  • 在防火墙中打开端口:51820 (UDP), 以及 21820 (UDP 用于客户端)。假设80 (TCP), 443 (TCP)端口由反向代理服务提供
  • 准备两个用于替换Pangolin反代组件80 (TCP), 443 (TCP)的端口,本文使用了1180和11443端口作为替代

安装过程

创建一个全新的文件夹用于存放部署相关的文件,然后进入该文件夹

执行以下命令初始化文件夹结构

mkdir -p config/traefik config/db config/letsencrypt config/logs data

创建空白的配置文件

touch docker-compose.yml config/config.yml config/traefik/traefik_config.yml config/traefik/dynamic_config.yml config/config.yml

修改配置文件docker-compose.yml,这里替换了默认的80、443端口。

services:
  pangolin:
    image: fosrl/pangolin:ee-latest # https://github.com/fosrl/pangolin/releases
    container_name: pangolin
    restart: unless-stopped
    volumes:
      - ./config:/app/config
      - ./data:/var/certificates
      - ./data:/var/dynamic
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"]
      interval: "3s"
      timeout: "3s"
      retries: 15

  gerbil:
    image: fosrl/gerbil:latest # https://github.com/fosrl/gerbil/releases
    container_name: gerbil
    restart: unless-stopped
    depends_on:
      pangolin:
        condition: service_healthy
    command:
      - --reachableAt=http://gerbil:3004
      - --generateAndSaveKeyTo=/var/config/key
      - --remoteConfig=http://pangolin:3001/api/v1/
    volumes:
      - ./config/:/var/config
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    ports:
      - 51820:51820/udp
      - 21820:21820/udp
      - 11443:443 # Port for traefik because of the network_mode
      - 1180:80 # Port for traefik because of the network_mode

  traefik:
    image: traefik:v3.4.0
    container_name: traefik
    restart: unless-stopped
    network_mode: service:gerbil # Ports appear on the gerbil service
    depends_on:
      pangolin:
        condition: service_healthy
    command:
      - --configFile=/etc/traefik/traefik_config.yml
    volumes:
      - ./config/traefik:/etc/traefik:ro # Volume to store the Traefik configuration
      - ./config/letsencrypt:/letsencrypt # Volume to store the Let's Encrypt certificates
      # Shared volume for certificates and dynamic config in file mode
      - ./data:/var/certificates:ro
      - ./data:/var/dynamic:ro

networks:
  default:
    driver: bridge
    name: pangolin

修改配置文件config/traefik/traefik_config.yml,如果需要由Pangolin的Traefik反向代理服务维护SSL证书,则需要修改admin@example.com为你自己的邮箱。

api:
  insecure: true
  dashboard: true

providers:
  http:
    endpoint: "http://pangolin:3001/api/v1/traefik-config"
    pollInterval: "5s"
  file:
    filename: "/etc/traefik/dynamic_config.yml"

experimental:
  plugins:
    badger:
      moduleName: "github.com/fosrl/badger"
      version: "v1.2.0"

log:
  level: "INFO"
  format: "common"

certificatesResolvers:
  letsencrypt:
    acme:
      httpChallenge:
        entryPoint: web
      email: admin@example.com # REPLACE WITH YOUR EMAIL
      storage: "/letsencrypt/acme.json"
      caServer: "https://acme-v02.api.letsencrypt.org/directory"

entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"
    transport:
      respondingTimeouts:
        readTimeout: "30m"
    http:
      tls:
        certResolver: "letsencrypt"

serversTransport:
  insecureSkipVerify: true

ping:
    entryPoint: "web"

修改配置文件config/traefik/dynamic_config.yml,将example.com修改成你的域名

http:
  middlewares:
    redirect-to-https:
      redirectScheme:
        scheme: https

  routers:
    # HTTP to HTTPS redirect router
    main-app-router-redirect:
      rule: "Host(`pangolin.example.com`)" # REPLACE WITH YOUR DOMAIN
      service: next-service
      entryPoints:
        - web
      middlewares:
        - redirect-to-https

    # Next.js router (handles everything except API and WebSocket paths)
    next-router:
      rule: "Host(`pangolin.example.com`) && !PathPrefix(`/api/v1`)" # REPLACE WITH YOUR DOMAIN
      service: next-service
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt

    # API router (handles /api/v1 paths)
    api-router:
      rule: "Host(`pangolin.example.com`) && PathPrefix(`/api/v1`)" # REPLACE WITH YOUR DOMAIN
      service: api-service
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt

    # WebSocket router
    ws-router:
      rule: "Host(`pangolin.example.com`)" # REPLACE WITH YOUR DOMAIN
      service: api-service
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt

  services:
    next-service:
      loadBalancer:
        servers:
          - url: "http://pangolin:3002" # Next.js server

    api-service:
      loadBalancer:
        servers:
          - url: "http://pangolin:3000" # API/WebSocket server

修改配置文件config/config.yml,将example.com修改成你的域名,参考官方说明

app:
  dashboard_url: "https://pangolin.example.com"
  log_failed_attempts: true
  anonymous_usage: false
 
domains:
  domain1:
    base_domain: "pangolin.example.com"
    cert_resolver: "letsencrypt"
    prefer_wildcard_cert: true

server:
  secret: "your-strong-secret"

gerbil:
  base_endpoint: "pangolin.example.com"

flags:
  require_email_verification: false
  disable_signup_without_invite: true
  disable_user_create_org: true

启动服务

docker compose up -d

查看日志,记录下TOKEN,后面会使用

docker compose logs -f

pangolin | === SETUP TOKEN EXISTS ===
pangolin | Token: xxxxxxx
pangolin | Use this token on the initial setup page

在系统已有的反向代理中添加Pangolin网站,并设置反向代理到本地IP的端口(前文已经配置好,根据你的情况选择,代理HTTP协议就选择 80 端口的替代 1180,代理HTTPS协议则选择 443 端口的替代 11443)。

如果一切正常,前往 https://your-domain.com/auth/initial-setup 就可以完成初始设置了。

效果

基本操作

初始化后,你已经创建了管理员账户,下面是基本的使用步骤。

首先,创建组织,然后按照提示创建站点,站点可以理解成用WireGuard(当然也可以不用)连接的后端网域。

image.png

填好站点配置以后,页面最下面会生成各类安装方法的脚本,非常方便。

image.png

image.png

在你的电脑或者NAS上面部署了Newt等站点之后,可以看到连接状态,下面可以在站点中添加资源了,也就是具体的服务端点:ip+端口等。

image.png

添加资源的流程和使用Nginx、Traefik这类工具相同,如下图所示。

image.png

添加完,即可通过下图显示URL访问内部资源了!

image.png

测试了NAS上搭建的ERP系统,实测速度非常快,和使用NetBird的方案一样丝滑,真香!

申请免费企业版证书

我们使用的是免费授权的企业版(个人和小企业免费)。需要根据官方说明申请免费证书。

注册账号并登录官方的Pangolin云服务:https://app.pangolin.net/,根据下图的步骤完成证书申请

image.png

进入管理面板,添加证书。

image.png

完成!

高级用法

SSO单点登录

理想情况下,我们完成Pangolin的认证后,应该一并完成了要访问的内网服务的认证,而无需内网服务再次进行认证。

实现这个目标需要配置SSO单点登录服务,让Pangolin和所有内网服务(资源)都使用同一个认证服务和用户信息,这样用户认证令牌才能在不同系统之间互信。有两种方案:

  • 使用专业的第三方SSO服务,开源和SaaS云的选择有很多。
  • 使用Pangolin内置的SSO服务,能够满足大多数需要。

关于这部分,请参考官方认证提供者文档

安全控制

官方提供了一个常见自托管服务的访问规则列表,可以将管理相关的API阻断,用于控制用户权限,请访问Access Control部分。

总结

Pangolin的测试验证圆满完成,服务本身没有任何问题,非常稳定!唯一的问题是现在国内拉取Docker越来越困难了。

从一个人,到一家人,再到一群人,自托管服务正在发挥更大的价值,以往我们为了安全,选择VPN的安全性只能牺牲灵活性。Pangolin这类产品的诞生,可以说为家用自托管领域带来新的增长空间,从传统的系统孤岛,开始往一体化集成系统的方向发展。

安全的反向代理,解决安全和服务暴露的问题;开源的SSO认证,解决系统间用户数据和访问方法的孤立问题;而使用SSO功能的各种自托管服务,将获得生态的持续完善。

技术的发展,开始真正为HomeLab和NAS玩家提供企业级体验的家用自托管服务,展望下一代的家用自托管解决方案:将把内部资源安全、高效地放在互联网,让我们自己、家人、朋友能够触手可及!

真香!