核心设计理念

传统frp安全方案的不足

  1. 静态配置文件管理白名单IP,修改需要重启服务

  2. 分布式环境下多节点配置同步困难

  3. 缺乏实时阻断恶意IP的能力

Redis作为动态白名单存储的优势

  1. 实时生效:IP规则变更无需重启frp服务

  2. 集中管理:多台frp服务器共享同一套白名单规则

  3. 高性能验证:Redis的极速查询能力支持高频率IP检查

  4. 灵活扩展:可与安全系统集成实现动态封禁

技术实现解析

frp/server/proxy/proxy.go 文件中的 handleUserTCPConnection 方法中,增加了对 Redis 动态白名单的校验逻辑,确保只有授权 IP 可访问代理服务。

示例代码如下(仅展示关键片段):

func isIPAllowedV1(ctx context.Context, serverCfg *v1.ServerConfig, ip string) bool {

	xlog.FromContextSafe(ctx).Infof("Redis config: Addr=%s, Password=%s, DB=%d, EnableRedisIPWhitelist=%v",
serverCfg.RedisAddr,
serverCfg.RedisPassword,
serverCfg.RedisDB,
serverCfg.EnableRedisIPWhitelist,
)
if !serverCfg.EnableRedisIPWhitelist {
return true
} rdb := redis.NewClient(&redis.Options{
Addr: serverCfg.RedisAddr,
Password: serverCfg.RedisPassword,
DB: serverCfg.RedisDB,
}) xlog.FromContextSafe(ctx).Errorf("redis check isIPAllowed db %s",serverCfg.RedisDB) key := serverCfg.RedisWhitelistPrefix + ip
exists, err := rdb.Exists(ctx, key).Result()
if err != nil {
xlog.FromContextSafe(ctx).Errorf("redis check error for key [%s]: %v", key, err)
return false
}
return exists == 1
} // HandleUserTCPConnection is used for incoming user TCP connections.
func (pxy *BaseProxy) handleUserTCPConnection(userConn net.Conn) {
xl := xlog.FromContextSafe(pxy.Context())
defer userConn.Close() // 添加白名单验证
remoteIP, _, errx := net.SplitHostPort(userConn.RemoteAddr().String())
if errx != nil {
xl.Warnf("invalid remote address: %v", errx)
return
} //xl.Warnf("IP [%s] is not in whitelist, connection begin", remoteIP) if !isIPAllowedV1(pxy.ctx, pxy.serverCfg, remoteIP) { //if !isIPAllowed(pxy.ctx, &pxy.serverCfg.ServerCommon, remoteIP) {
xl.Warnf("IP [%s] is not in whitelist, connection rejected", remoteIP)
return
} xl.Warnf("IP [%s] isIPAllowed ", remoteIP) // 后续代理连接逻辑

WEB 服务端修改

  1. 前端使用VUE3,增加对应的菜单和组件

  2. 前端代码需要发到到目录assets\frps\static
  3. 服务端增加接口,文件路径 server\dashboard_api.go

// /api/redis
func (svr *Service) apiRedisWhitelist(w http.ResponseWriter, r *http.Request) {
res := GeneralResponse{Code: 200}
defer func() {
log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
w.WriteHeader(res.Code)
if len(res.Msg) > 0 {
_, _ = w.Write([]byte(res.Msg))
}
}() log.Infof("http request: [%s]", r.URL.Path) // 初始化 Redis 客户端
cfg := svr.cfg // 假设 svr.cfg 是你的 *ServerConfig
rdb := redis.NewClient(&redis.Options{
Addr: cfg.RedisAddr,
Password: cfg.RedisPassword,
DB: cfg.RedisDB,
})
ctx := context.Background() // 扫描符合前缀的所有键
var cursor uint64
var ipList []IPItem
prefix := cfg.RedisWhitelistPrefix for {
keys, newCursor, err := rdb.Scan(ctx, cursor, prefix+"*", 100).Result()
if err != nil {
res.Code = 500
res.Msg = "redis scan error: " + err.Error()
return
}
for _, key := range keys {
// 提取 IP
ip := strings.TrimPrefix(key, prefix) // 获取过期时间
ttl, err := rdb.TTL(ctx, key).Result()
if err != nil {
continue
} var expireAt string
if ttl > 0 {
expireAt = time.Now().Add(ttl).UTC().Format(time.RFC3339)
} else if ttl == -1 {
expireAt = "永不过期" // 永不过期
} else {
// 已过期或无效
continue
} ipList = append(ipList, IPItem{
IP: ip,
ExpireAt: expireAt,
})
}
if newCursor == 0 {
break
}
cursor = newCursor
} // 构建响应 JSON
result := map[string]interface{}{
"status": "success",
"whitelist": ipList,
} buf, _ := json.Marshal(result) // 构造静态响应数据
// svrResp := map[string]interface{}{
// "status": "success",
// "whitelist": []IPItem{
// {
// IP: "192.168.1.100",
// ExpireAt: "2025-06-01T12:00:00Z",
// },
// {
// IP: "10.0.0.0/24",
// ExpireAt: "2025-06-10T00:00:00Z",
// },
// {
// IP: "127.0.0.1",
// ExpireAt: "9999-12-31T23:59:59Z", // 永久有效
// },
// },
// } // buf, _ := json.Marshal(&svrResp)
res.Msg = string(buf)
} // /api/addip
func (svr *Service) apiRedisAddIp(w http.ResponseWriter, r *http.Request) {
res := GeneralResponse{Code: 200}
defer func() {
log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
w.WriteHeader(res.Code)
if len(res.Msg) > 0 {
_, _ = w.Write([]byte(res.Msg))
}
}() log.Infof("http request: [%s]", r.URL.Path)
// 解析参数
var req struct {
IP string `json:"ip"`
ExpireDays int `json:"expire_days"` // 0 表示永不过期
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
res.Code = 400
res.Msg = "invalid json"
return
}
if strings.TrimSpace(req.IP) == "" {
res.Code = 400
res.Msg = "ip is empty"
return
} // Redis
cfg := svr.cfg
rdb := redis.NewClient(&redis.Options{
Addr: cfg.RedisAddr,
Password: cfg.RedisPassword,
DB: cfg.RedisDB,
})
ctx := context.Background() key := cfg.RedisWhitelistPrefix + req.IP
var expiration time.Duration
if req.ExpireDays <= 0 {
expiration = 0 // 永久
} else {
expiration = time.Duration(req.ExpireDays) * 24 * time.Hour
} err := rdb.Set(ctx, key, "", expiration).Err()
if err != nil {
res.Code = 500
res.Msg = "redis set error: " + err.Error()
return
} res.Msg = `{"status":"ok"}`
} // /api/delip
func (svr *Service) apiRedisDelIp(w http.ResponseWriter, r *http.Request) {
res := GeneralResponse{Code: 200}
defer func() {
log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
w.WriteHeader(res.Code)
if len(res.Msg) > 0 {
_, _ = w.Write([]byte(res.Msg))
}
}() var req struct {
IP string `json:"ip"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || strings.TrimSpace(req.IP) == "" {
res.Code = 400
res.Msg = "invalid request"
return
} cfg := svr.cfg
rdb := redis.NewClient(&redis.Options{
Addr: cfg.RedisAddr,
Password: cfg.RedisPassword,
DB: cfg.RedisDB,
})
ctx := context.Background() key := cfg.RedisWhitelistPrefix + req.IP
if err := rdb.Del(ctx, key).Err(); err != nil {
res.Code = 500
res.Msg = "delete redis key failed: " + err.Error()
return
} res.Msg = `{"status":"deleted"}`
}

  

结语

frp-redis 项目通过结合 frp 的安全特性和 Redis 的灵活性,提供了一种相对安全的远程访问方案。开源这个项目是希望帮助更多开发者避免我遇到的这些问题,同时也欢迎社区贡献更好的安全实践。

在网络安全形势日益严峻的今天,作为开发者我们必须时刻保持警惕,采取纵深防御策略保护我们的服务和数据。frp-redis 只是这个过程中的一个小小尝试,但安全无小事,每一个环节都值得认真对待。

项目地址:https://github.com/wx37668827/frp-redis

frp增加IP限制的更多相关文章

  1. LINUX修改、增加IP的方法,一张网卡绑定多个IP/漂移IP【转】

    临时增加IP命令:ifconfig eth0:1 ip地址 netmask 子网码 broadcast 广播地址 gateway 网关  ifconfig eth0:1 10.1.104.65 net ...

  2. fiddler增加ip以及响应时间列

    最近打算看一下移动端app的响应等请求,这里打算用fillder来查看appium的模拟出发请求的操作来查看结果, 所以我们需要在左侧的面板增加我们所需要的ip,响应时间等数据以方便我们查看 fidd ...

  3. CentOS6修改主机名(hostname)及 修改/etc/hosts 文件,增加ip和hostname的映射关系(转)

    CentOS修改主机名(hostname)  需要修改两处:一处是/etc/sysconfig/network,另一处是/etc/hosts,只修改任一处会导致系统启动异常.首先切换到root用户. ...

  4. 【抓包工具之Fiddler】增加IP列;session高亮

    Fiddler 在处理每个session时,脚本文件CustomRules.js中的方法都会运行,该脚本使得你可以隐藏,标识或任意修改负责的session.规则脚本在运行状态下就可以修改并重新编译,不 ...

  5. filebeat v6.3 如何增加ip 字段

    我们知道filebeat获取数据之后是会自动获取主机名的,项目上有需要filebeat送数据的时候送一个ip字段出来 方法:配置filebeat配置文件 解释一下:field 是字段模块 在这个模块下 ...

  6. linux双线ip设置(不需额外增加路由表)

    linux 双线ip设置(不需额外增加路由表,只需修改下面就ok了)修改   vi /etc/iproute2/rt_tables              (增加电信和网通两个路由表) 增加252  ...

  7. LoadRunner使用技巧-IP欺骗的使用

    设置IP欺骗的原因         1.当某个IP的访问过于频繁,或者访问量过大是,服务器会拒绝访问请求,这时候通过IP欺骗可以增加访问频率和访问量,以达到压力测试的效果. 2.某些服务器配置了负载均 ...

  8. ip netns相关命令

    1.增加虚拟网络命名空间   ip netns add net0   2.显示所有的虚拟网络命名空间 EULER:~ # ip netns list net0 也可通过查看/var/run/netns ...

  9. tcp/ip详解-ip头部选项字段

    IP头部的选项字段 作用:用于网络调试和测试 IP首部的可变部分就是一个可选字段.选项字段用来支持排错.测量以及安全等措施,内容很丰富.此字段的长度可变,从1个字节到40个字节不等,取决于所选择的项目 ...

  10. IP包格式

    网络层提供的服务就是在不同网段之间转发数据包. Ip包结构 1,格式(每行4byte*5) 2,版本 V4 V6 3,首部长度 20(固定)+可变长度 ,区分服务 Win2008开始:gpedit. ...

随机推荐

  1. zabbix - [03] 安装部署

    参考:https://www.yuque.com/fenghuo-tbnd9/ffmkvs zabbix6要求操作系统为Centos8,所以一开始安装部署的时候发现少了zabbix-server-my ...

  2. vmware workstation 17 pro激活密钥

    vmware workstation 17 pro激活密钥,通用批量永久激活许可 17:JU090-6039P-08409-8J0QH-2YR7F 16:ZF3R0-FHED2-M80TY-8QYGC ...

  3. DeFi(去中心化金融)的硬核知识

    1. ​DeFi流动性挖矿:躺着赚利息的"矿工"​ 简单来说,流动性挖矿就像你往银行存钱赚利息,但这里存的是加密货币,利息更高,还能随时提现.比如你往Uniswap这样的去中心化交 ...

  4. websocket 后台新订单通知 —— Laravel 5.8 Laravel-echo-server教程

    websocket 后台新订单通知 -- Laravel 5.8 workman PHPSocket.IO教程 环境要求: Laravel 框架 (5.8 版本) Redis 服务 1.安装 lara ...

  5. 往EXCEL粘贴超长整数字段

    写一个表格的HTML <table border="1"> <tr> <td>1</td> <td>1234567890 ...

  6. 通过百度地图 API V2.0 版本,进行地图坐标系转换

    注意 先阅读参考链接 瞭月 的文章,再阅读本文. 其中,请求参数中 model 的含义: amap/tencent - 即:GCJ02 火星坐标系,由中国国家测绘局制订的地理信息系统的坐标系统. 由 ...

  7. 如何在 Git 书写良好的 Commit Messages

    如何在 Git 书写良好的 Commit Messages Why(为什么编写) | How(如何编写) Why A diff will tell you what changed, but only ...

  8. 用于线程同步的Interlocked系列函数主要有哪些

    原子访问 通过Interlocked系列函数是 Windows API 提供的一组原子操作函数,用于在多线程环境中安全地操作共享变量.当我们执行这些Interlocked系列函数的时候 ,函数会对总线 ...

  9. Spring AI与DeepSeek实战三:打造企业知识库

    一.概述 企业应用集成大语言模型(LLM)落地的两大痛点: 知识局限性:LLM依赖静态训练数据,无法覆盖实时更新或垂直领域的知识: 幻觉:当LLM遇到训练数据外的提问时,可能生成看似合理但错误的内容. ...

  10. halcon 入门教程(三) 边缘检测

    原文作者:aircraft 原文链接:halcon 入门教程(三) 边缘检测 有兴趣可以多看其他的halcon教程 halcon 学习教程目录 本篇讲一下边缘检测(边缘提取),因为这个我发现也是比较常 ...