Files
cftunnel/cmd/add.go
T
晴天 266b978371 feat: 内置鉴权代理 --auth 密码保护
在 cloudflared 和用户服务之间插入鉴权反向代理中间件,
用户通过 --auth user:pass 即可启用密码保护。

- 新增 internal/authproxy 包(代理核心/端口探测/登录页)
- quick 和 add 命令支持 --auth flag
- up 命令启动前自动为有 Auth 的路由启动代理
- 登录页深色主题,底部链接官网
- 官网新增访问保护特性卡片/对比表/命令速查
2026-02-25 03:31:23 +08:00

123 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package cmd
import (
"context"
"encoding/hex"
"fmt"
"strings"
"github.com/qingchencloud/cftunnel/internal/authproxy"
"github.com/qingchencloud/cftunnel/internal/cfapi"
"github.com/qingchencloud/cftunnel/internal/config"
"github.com/spf13/cobra"
)
var addDomain string
var addAuth string
func init() {
addCmd.Flags().StringVar(&addDomain, "domain", "", "完整域名 (如 webhook.example.com)")
addCmd.MarkFlagRequired("domain")
addCmd.Flags().StringVar(&addAuth, "auth", "", "启用密码保护 (格式: 用户名:密码)")
rootCmd.AddCommand(addCmd)
}
// pushIngress 推送当前所有路由的 ingress 配置到远端
func pushIngress(client *cfapi.Client, ctx context.Context, cfg *config.Config) error {
var rules []cfapi.IngressRule
for _, r := range cfg.Routes {
rules = append(rules, cfapi.IngressRule{Hostname: r.Hostname, Service: r.Service})
}
return client.PushIngressConfig(ctx, cfg.Tunnel.ID, rules)
}
// findZoneForDomain 通过遍历账户 Zone 列表匹配域名(支持多级 TLD)
func findZoneForDomain(client *cfapi.Client, ctx context.Context, domain string) (*cfapi.ZoneInfo, error) {
zoneList, err := client.ListZones(ctx)
if err != nil {
return nil, err
}
for _, z := range zoneList {
if domain == z.Name || strings.HasSuffix(domain, "."+z.Name) {
return &cfapi.ZoneInfo{ID: z.ID, Name: z.Name}, nil
}
}
return nil, fmt.Errorf("未找到域名 %s 对应的 Zone,请确认域名已添加到 Cloudflare", domain)
}
var addCmd = &cobra.Command{
Use: "add <名称> <端口>",
Short: "添加路由(自动创建 CNAME + 更新 ingress",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
name, port := args[0], args[1]
service := "http://localhost:" + port
cfg, err := config.Load()
if err != nil {
return err
}
if cfg.Tunnel.ID == "" {
return fmt.Errorf("请先运行 cftunnel init && cftunnel create <名称>")
}
if cfg.FindRoute(name) != nil {
return fmt.Errorf("路由 %s 已存在", name)
}
client := cfapi.New(cfg.Auth.APIToken, cfg.Auth.AccountID)
ctx := context.Background()
// 查找域名对应的 Zone(支持多级 TLD)
zone, err := findZoneForDomain(client, ctx, addDomain)
if err != nil {
return err
}
// 创建 CNAME
target := cfg.Tunnel.ID + ".cfargotunnel.com"
fmt.Printf("正在创建 DNS 记录 %s → %s\n", addDomain, target)
recordID, err := client.CreateCNAME(ctx, zone.ID, addDomain, target)
if err != nil {
return err
}
// 构建路由配置
route := config.RouteConfig{
Name: name,
Hostname: addDomain,
Service: service,
ZoneID: zone.ID,
DNSRecordID: recordID,
}
// 如果指定了 --auth,填充鉴权配置
if addAuth != "" {
user, pass, err := parseAuth(addAuth)
if err != nil {
return err
}
route.Auth = &config.AuthProxy{
Username: user,
Password: pass,
SigningKey: hex.EncodeToString(authproxy.RandomKey()),
}
fmt.Printf("已启用密码保护: %s\n", addDomain)
}
// 保存路由
cfg.Routes = append(cfg.Routes, route)
if err := cfg.Save(); err != nil {
return err
}
// 推送 ingress 配置到远端
fmt.Println("正在同步 ingress 配置...")
if err := pushIngress(client, ctx, cfg); err != nil {
return fmt.Errorf("推送 ingress 失败: %wDNS 记录已创建,请排查后重试 add 或手动删除 DNS 记录)", err)
}
fmt.Printf("路由已添加: %s → %s (%s)\n", addDomain, service, name)
return nil
},
}