frp增加IP限制
核心设计理念
传统frp安全方案的不足
静态配置文件管理白名单IP,修改需要重启服务
分布式环境下多节点配置同步困难
缺乏实时阻断恶意IP的能力
Redis作为动态白名单存储的优势
实时生效:IP规则变更无需重启frp服务
集中管理:多台frp服务器共享同一套白名单规则
高性能验证:Redis的极速查询能力支持高频率IP检查
灵活扩展:可与安全系统集成实现动态封禁
技术实现解析
在 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 服务端修改
前端使用VUE3,增加对应的菜单和组件
- 前端代码需要发到到目录assets\frps\static
服务端增加接口,文件路径 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限制的更多相关文章
- LINUX修改、增加IP的方法,一张网卡绑定多个IP/漂移IP【转】
临时增加IP命令:ifconfig eth0:1 ip地址 netmask 子网码 broadcast 广播地址 gateway 网关 ifconfig eth0:1 10.1.104.65 net ...
- fiddler增加ip以及响应时间列
最近打算看一下移动端app的响应等请求,这里打算用fillder来查看appium的模拟出发请求的操作来查看结果, 所以我们需要在左侧的面板增加我们所需要的ip,响应时间等数据以方便我们查看 fidd ...
- CentOS6修改主机名(hostname)及 修改/etc/hosts 文件,增加ip和hostname的映射关系(转)
CentOS修改主机名(hostname) 需要修改两处:一处是/etc/sysconfig/network,另一处是/etc/hosts,只修改任一处会导致系统启动异常.首先切换到root用户. ...
- 【抓包工具之Fiddler】增加IP列;session高亮
Fiddler 在处理每个session时,脚本文件CustomRules.js中的方法都会运行,该脚本使得你可以隐藏,标识或任意修改负责的session.规则脚本在运行状态下就可以修改并重新编译,不 ...
- filebeat v6.3 如何增加ip 字段
我们知道filebeat获取数据之后是会自动获取主机名的,项目上有需要filebeat送数据的时候送一个ip字段出来 方法:配置filebeat配置文件 解释一下:field 是字段模块 在这个模块下 ...
- linux双线ip设置(不需额外增加路由表)
linux 双线ip设置(不需额外增加路由表,只需修改下面就ok了)修改 vi /etc/iproute2/rt_tables (增加电信和网通两个路由表) 增加252 ...
- LoadRunner使用技巧-IP欺骗的使用
设置IP欺骗的原因 1.当某个IP的访问过于频繁,或者访问量过大是,服务器会拒绝访问请求,这时候通过IP欺骗可以增加访问频率和访问量,以达到压力测试的效果. 2.某些服务器配置了负载均 ...
- ip netns相关命令
1.增加虚拟网络命名空间 ip netns add net0 2.显示所有的虚拟网络命名空间 EULER:~ # ip netns list net0 也可通过查看/var/run/netns ...
- tcp/ip详解-ip头部选项字段
IP头部的选项字段 作用:用于网络调试和测试 IP首部的可变部分就是一个可选字段.选项字段用来支持排错.测量以及安全等措施,内容很丰富.此字段的长度可变,从1个字节到40个字节不等,取决于所选择的项目 ...
- IP包格式
网络层提供的服务就是在不同网段之间转发数据包. Ip包结构 1,格式(每行4byte*5) 2,版本 V4 V6 3,首部长度 20(固定)+可变长度 ,区分服务 Win2008开始:gpedit. ...
随机推荐
- hbase - [05] hbase关联hive
一.配置 1.在hive的配置文件中配置HBASE_HOME(conf/hive-env.sh) export HBASE_HOME=/opt/module/hbase 2.将 conf/hive-e ...
- 读论文-序列感知推荐系统(Sequence-Aware Recommender Systems)
前言 今天读的论文为一篇于2018年发表在(ACM computing surveys (CSUR))的论文,这篇文章主要讲述了序列感知推荐系统(Sequence-Aware Recommender ...
- Angular CLI 源码分析
准备: 安装 Node.js https://nodejs.org/: 安装 VS Code https://code.visualstudio.com/: 创建文件夹 angular-cli-sou ...
- 【基础知识笔记】004 matlab-矩阵和数组的关系
之前以为是两种东西,今天看了mathworks的官网才知道 所有 MATLAB 量都是多维数组,与数据类型无关.矩阵是指通常用来进行线性代数运算的二维数组 1.数组创建 要创建每行包含四个元素的数组, ...
- Win10下子系统Unbuntu18.04安装nginx
1.Nginx的软件包在Ubuntu默认软件仓库中可用. 安装非常简单,只需键入以下命令: sudo apt update sudo apt install nginx 2.安装完成后,检查Nginx ...
- Ubuntu 下查看 ip
博客地址:https://www.cnblogs.com/zylyehuo/ ip a
- Oracle10g RAC -- Linux 集群文件系统
通常,集群只是一组作为单一系统运行的服务器( PC 或者工作站).但是,这个定义的外延不断显著扩大:集群技术现在不但是一个动态领域,而且其各种应用程序正不断吸收新的特性.此外,集群文件系统技术(无论是 ...
- 初始化参数之memory_target
一.引言: Oracle 9i引入pga_aggregate_target,可以自动对PGA进行调整: Oracle 10g引入sga_target,可以自动对SGA进行调整: Oracle 11g则 ...
- BandiZip无广告版安装
BandiZip无广告版安装 Bandizip 是一款压缩软件,它支持Zip.7-Zip 和 RAR 以及其它压缩格式.它拥有非常快速的压缩和解压缩的算法,从大学用到现在,但是现在最新的版本在每次压缩 ...
- Arrays.asList() 详解
[1. 要点] 该方法是将数组转化成List集合的方法. List list = Arrays.asList("a","b","c"); 注 ...