核心设计理念

传统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. Linux - 配置IP&主机名的快捷操作

    nmtui 执行以下命令可以进入一个可视化界面,进行IP的可视化配置.以及网络服务的重启(注意,这个重启是停止然后启动, 如果使用xshell进行操作会失去ssh连接,直连服务器时可这直接操作).主机 ...

  2. 【自荐】Catime v1.0.4 一款贼好用的计时器

    Github: https://github.com/vladelaina/Catime 仅1.3MB!!!!! 特点 极简设计: 透明界面.点击穿透.可调大小和位置.多语言支持 丰富字体: 47种字 ...

  3. 【Unit3】社交系统模拟(JML规格化设计)-作业总结

    第三单元作业难度在OO课程中当属最低.原因在于最复杂多变(贻害无穷)的设计环节被作业接口和JML规格描述限定,我们不再需要考虑整体的构架(抽象出那些类,设置哪些方法等),唯一的能动性仅在具体实现和复杂 ...

  4. SpringSecurity5(1-快速入门)

    依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spri ...

  5. VLAN聚合技术:Super-vlan

    Super-VLAN,也称为VLAN聚合(VLAN Aggregation),是一种网络配置技术,主要用于优化IP地址资源的利用和隔离广播域. 一.定义与功能 Super-VLAN是通过将多个VLAN ...

  6. docker 版本号说明

    17.03 版本以前 Docker CE 在 17.03 版本之前叫 Docker Engine, 版本说明参考这里 => Docker Engine release notes, 可以看到 D ...

  7. AI时代:本地运行大模型ollama

    https://ollama.com/ 使用 Llama 2.Mistral.Gemma 和其他大型语言模型启动和运行. 支持windows,Linux,Mac. 支持的开源模型列表: Ollama ...

  8. Sunshine+Moonlight让安卓Pad或IPad变6ms低延迟高质量无线显示器 #串流 #无线副屏

    1.背景 最近看到多个博主在分享局域网或者公网使用Sunshine+Moonlight串流技术,将电脑画面投屏到其他屏幕(电视.安卓和苹果平板等等),宣称是个位数的延迟,并且能用平板打steam游戏( ...

  9. `QualitySettings.asyncUploadPersistentBuffer

    在 Unity 中,`QualitySettings.asyncUploadPersistentBuffer` 是一个静态属性,它控制着纹理上传到 GPU 的异步方式.当启用时(设置为 `true`) ...

  10. MongoDB导出/导入操作

    1.从测试环境数据库导出数据 2.导入数据到本地数据库 mongoimport --host localhost --port 27017 --username root --password 123 ...