聊聊部署在K8S的项目如何获取客户端真实IP

news2024/11/18 0:21:30

前言

最近部门有个需求,需要对一些客户端IP做白名单,在白名单范围内,才能做一些业务操作。按我们的部门的一贯做法,我们会封装一个client包,提供给业务方使用。(注: 我们的项目是运行在K8S上)本以为这是一个不是很难的功能,部门的小伙伴不到一天,就把功能实现了,他通过本地调试,可以获取到正确的客户端IP,但是发布到测试环境,发现获取到的客户端IP一直是节点的IP,后面那个小伙伴排查了很久,一直没头绪,就找到我帮忙一直排查一下。今天文章主要就是来复盘这个过程

排查过程

首先先排查了一下他获取客户端IP的实现逻辑

public class IpUtils {
    private static Logger logger = LoggerFactory.getLogger(IpUtils.class);
    private static final String IP_UTILS_FLAG = ",";
    private static final String UNKNOWN = "unknown";
    private static final String LOCALHOST_IP = "0:0:0:0:0:0:0:1";
    private static final String LOCALHOST_IP1 = "127.0.0.1";

    /**
     * 获取IP地址
     *
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ip = null;
        try {
            //以下两个获取在k8s中,将真实的客户端IP,放到了x-Original-Forwarded-For。而将WAF的回源地址放到了 x-Forwarded-For了。
            ip = request.getHeader("X-Original-Forwarded-For");
            System.out.println("X-Original-Forwarded-For:" + ip);
            if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("X-Forwarded-For");
            }
            //获取nginx等代理的ip
            if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("x-forwarded-for");
                System.out.println("x-forwarded-for:" + ip);
            }
            if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_CLIENT_IP");
            }
            if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            }
            //兼容k8s集群获取ip
            if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
                System.out.println("getRemoteAddr:" + ip);
                if (LOCALHOST_IP1.equalsIgnoreCase(ip) || LOCALHOST_IP.equalsIgnoreCase(ip)) {
                    //根据网卡取本机配置的IP
                    InetAddress iNet = null;
                    try {
                        iNet = InetAddress.getLocalHost();
                    } catch (UnknownHostException e) {
                        logger.error("getClientIp error: {}", e);
                    }
                    ip = iNet.getHostAddress();
                }
            }
        } catch (Exception e) {
            logger.error("IPUtils ERROR ", e);
        }
        //使用代理,则获取第一个IP地址
        if (!StringUtils.isEmpty(ip) && ip.indexOf(IP_UTILS_FLAG) > 0) {
            ip = ip.substring(0, ip.indexOf(IP_UTILS_FLAG));

        }

        return ip;
    }

}

这逻辑看着貌似没问题,因为本地调试可以获取到正确的客户端IP,而测试环境获取不到,大概率是环境有问题。于是就把方向转为定位环境的差异性

环境定位

测试环境

我们测试环境的访问流程为客户端–> k8s service nodeport—>pod

通过搜索在https://kubernetes.io/zh-cn/docs/tutorials/services/source-ip/
在这篇文章找到答案。

Kubernetes Service 转发场景下,无论使用 iptbales 或 ipvs 的负载均衡转发模式,转发时都会对数据包做 SNAT,即不会保留客户端真实源 IP

整体流程

上文的链接也贴了解法


具体步骤就是

1、步骤一:业务pod的配置调度到指定节点

示例

spec:
   nodeName: node1    #指定pod节点配置
   containers:
      - name: pod-name

2、步骤二:将业务的service yaml 默认配置的externalTrafficPolicy: Cluster改为 externalTrafficPolicy: Local

示例

spec:
  type: NodePort
  externalTrafficPolicy: Local

3、步骤三:通过指定在pod上的node节点 + nodeport进行访问

示例

http://node1:nodeport

假设部署了node1和node2节点,只能通过node1:nodeport才能访问到具体业务,如果通过node2:nodeport,则请求的数据包会被抛弃


通过上述的方案,解决了在测试环境通过service nodeport获取不到正确客户端ip的问题

uat环境

当测试环境没问题后,将项目发布到UAT环境,然后不出意外的话,又出意外了。

uat的访问流程为 客户端- -> nginx+keepalive --> ingress --> pod

因为访问方式不一样,因此解法又有差异。通过搜索了解到*用户ip的传递依靠的是X-Forwarded-参数。但是默认情况下,ingress是没有开启的 因此我们需要开启。开启需要如下参数

  • use-forwarded-headers: 如果设置为True时,则将设定的X-Forwarded-* Header传递给后端,
    当Ingress在L7 代理/负载均衡器之后使用此选项。如果设置为 false 时,则会忽略传入的X-Forwarded-*Header,
    当 Ingress 直接暴露在互联网或者 L3/数据包的负载均衡器后面,并且不会更改数据包中的源 IP请使用此选项。
  • forwarded-for-header: 设置用于标识客户端的原始 IP 地址的 Header字段。默认值X-Forwarded-For。如果想修改为自定义的字段名,则可以在configmap的data配置块下添加:forwarded-for-header: “THE_NAME_YOU_WANT”
  • compute-full-forwarded-for: 将 remote address 附加到 X-Forwarded-For
    Header而不是替换它。当启用此选项后端应用程序负责根据自己的受信任代理列表排除并提取客户端 IP。

详细的介绍可以查看官网

https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#use-forwarded-headers​

我们在Ingress Nginx Controller 的 Configmap添加如下内容

apiVersion: v1
kind: ConfigMap
......
data:
  compute-full-forwarded-for: "true"
  use-forwarded-headers: "true"
  forwarded-for-header:"X-Forwarded-For"

配置后,发现没鸟用,没有效果。后面查了很多资料,发现网上都是那么配的,后面就觉得是不是nginx - keepalive 这一环节出了啥问题,于是就问了一下运维,看他nginx那边是否有配置X-Forwarded-For,他说没有,那我就问他能否配置一下,他的回答是因为nginx那边启用了 ssl_preread 模块无法使用X-Forwarded-For

后面就问他能否改下,他回答说是后面公司要采用F5了,到时候在配置一下就好。而他目前事情比较多,没时间帮我弄这个。

由于业务比较赶,运维又没空搞,于是就和业务那边沟通,采取了折中方案,就是通过自定义请求头,我们在client包配置了一个属性,那个属性用来让业务将白名单ip填进去,示例

lybgeek:
  whilte-ips: 192.168.1.1,192.168.2.1

在业务项目启动的时候,client包会自动将配置的白名单塞入请求头

   header("x-custom-forwarded-for",whilteIps)

服务端那边获取客户端ip做如下改造

@Slf4j
public final class IPHelper {

    private IPHelper(){}

    private static final String IP_UTILS_FLAG = ",";
    private static final String UNKNOWN = "unknown";
    private static final String LOCALHOST_IP = "0:0:0:0:0:0:0:1";
    private static final String LOCALHOST_IP1 = "127.0.0.1";


   private static final String[] headersToTry = {
           //在k8s中,将真实的客户端IP,放到了x-Original-Forwarded-For。而将WAF的回源地址放到了 x-Forwarded-For了。
            "X-Original-Forwarded-For",
            "X-Forwarded-For",
            "Proxy-Client-IP",
            "WL-Proxy-Client-IP",
            "HTTP_X_FORWARDED_FOR",
            "HTTP_X_FORWARDED",
            "HTTP_X_CLUSTER_CLIENT_IP",
            "HTTP_CLIENT_IP",
            "HTTP_FORWARDED_FOR",
            "HTTP_FORWARDED",
            "HTTP_VIA",
            "REMOTE_ADDR",
            // 自定义请求头
            "X-Custom-Forwarded-For",
    };

        /**
         * 获取用户的真正IP地址
         *
         * @param request request对象
         * @return 返回用户的IP地址
         */
         @SneakyThrows
         public static String getIpAddr(HttpServletRequest request) {
             String ip = null;
            for (String header : headersToTry) {
                ip = request.getHeader(header);
                if (StringUtils.hasText(ip) && !UNKNOWN.equalsIgnoreCase(ip)){
                    log.info("hit the target client ip -> 【{}】 by header --> 【{}】",ip,header);
                    return ip;
                }
            }
             //兼容k8s集群获取ip
             if (org.springframework.util.StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
                 ip = request.getRemoteAddr();
                 if (LOCALHOST_IP1.equalsIgnoreCase(ip) || LOCALHOST_IP.equalsIgnoreCase(ip)) {
                     //根据网卡取本机配置的IP
                     InetAddress iNet = null;
                     try {
                         iNet = InetAddress.getLocalHost();
                     } catch (UnknownHostException e) {
                         log.error("getIpAddr error: {}", e);
                     }
                     ip = iNet.getHostAddress();
                 }
                 log.info("hit the target client ip -> 【{}】 by method 【getRemoteAddr】 ",ip);
             }

             //使用代理,则获取第一个IP地址
             if (!org.springframework.util.StringUtils.isEmpty(ip) && ip.indexOf(IP_UTILS_FLAG) > 0) {
                 ip = ip.substring(0, ip.indexOf(IP_UTILS_FLAG));
             }
             return ip;
        }





}

其实做的事情,就将原来的工具类稍微重构了一下,并加入自定义请求头X-Custom-Forwarded-For

总结

这次的复盘总结就是很多东西没那么想当然,有些简单的东西,里面可能也有有坑。当遇到跨部门合作时,如果遇到一些不可抗力因素,我们除了向上反馈之外,还要有兜底方案,不然会非常被动

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/592467.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LAMP的实战应用之部署wordpress论坛,并实现正常访问登录论坛

目录 环境配置: 一、安装配置基础环境 步骤一:配置本地yun 步骤二:安装Remi 仓库配置包(清华源获取) 步骤三:配置Remi 仓库配置包 步骤四:安装php的加速器配置 步骤五:启动服…

Cesium实战 - 实现大气云层效果

Cesium实战 - 实现大气云层效果 Cesium 实现大气云层效果主要思路核心代码在线示例 Cesium 实现大气云层效果 在实际开发中,一般会有天气的效果,雨雪雾比较常见,相关的博客也很多,但是关于云层的天气效果还是比较少,而…

【调峰】储能辅助电力系统调峰的容量需求研究(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

经典神经网络(6)ResNet及其在Fashion-MNIST数据集上的应用

经典神经网络(6)ResNet及其在Fashion-MNIST数据集上的应用 1 ResNet的简述 ResNet 提出了一种残差学习框架来解决网络退化问题,从而训练更深的网络。这种框架可以结合已有的各种网络结构,充分发挥二者的优势。 ResNet以三种方式挑战了传统的神经网络架…

【LeetCode】342. 4的幂

342. 4的幂(简单) 方法一:二进制 思路 首先考虑一个数字是不是 2 的整数次方:如果一个数字 n 是 2 的整数次方,那么它的二进制一定是 0...010...0 这样的形式,将它和 -n 按位与的结果一定是它本身。如果 …

前沿质谱应用沙龙分享会暨苏州百趣落成仪式即将开幕!

质谱作为一项医学检验新技术,凭借高特异性、高灵敏度、多指标检测等优势,成为了体外诊断领域最富生命力的新技术之一。目前质谱技术能够准确的测定多种生物小分子代谢物,且质谱在大分子物质例如蛋白质方面也应用的非常广泛。目前,…

要电脑重装系统装在哪个盘最好

在进行电脑重装系统时,选择一个合适的系统安装盘是非常重要的。本文将为您介绍如何选择最佳的系统安装盘,以确保系统性能和稳定性的最佳表现。 工具/原料: 系统版本:windows系统 品牌型号:华硕VivoBook14 软件版本…

张小龙发明了小程序,是否意味着失败?

今天微信小程序上线,从开发到上线仅仅用了四天时间,这是一个了不起的成就。 小程序诞生以来,一直存在着一种声音:它是张小龙“伟大的发明”,是微信“伟大的创新”。然而,张小龙在小程序发布会上宣布&#…

Spark SQL概述、数据帧与数据集

文章目录 一、准备工作1、准备数据文件2、启动Spark Shell 二、加载数据为Dataset1、读文件得数据集 三、给数据集添加元数据信息1、定义学生样例类2、导入隐式转换3、将数据集转换成学生数据集4、对学生数据集进行操作(1)显示数据集内容(2&a…

认识熟悉 Stable Diffusion(SD)基本参数

界面样式 界面参数 界面参数说明prompt希望生成的图片的描述negative prompt不希望在图片中出现的描述Batch size每次生成的图片个数Width图片宽度Height图片高度 这里需要注意的就是尺寸,尺寸并非越大越好,需要根据自己的配置和需求适当调整&#xff…

node.js+vue学生读书笔记共享分享系统

从上面的描述中可以基本可以实现软件的功能: 1、开发实现读书笔记共享平台的整个系统程序; 2、管理员;首页、个人中心、用户管理、笔记分享管理、个人笔记管理、管理员管理、交流互动、系统管理等。 3、用户:首页、个人中心、笔记分享管理、个人笔记管理、我的收藏管理。 4、前…

Window10配置Maven详细教程

文章目录 一、Maven概述二、Maven下载三、配置Maven环境变量四、查看Maven是否配置成功五、为Maven配置本地仓库以及指定远程仓库5.1 Maven构件搜索顺序5.2 Maven配置本地仓库5.3 Maven指定远程仓库 一、Maven概述 Maven是专门管理和构建Java项目的工具,Maven的主要…

Linux nohup-后台挂起运行程序神器

一. 场景描述 天黑了,我得离开实验室去吃饭了。为了环保,我必须关闭电脑,减少不必要的浪费!正常情况下当我关闭终端或电脑时,上面运行的任务代码即会自动停止,但我依旧希望保持代码的正常运行,此…

Android 更新后跑不起来?快来适配 AGP8 和 Flamingo/JDK 17

随着 Android Studio Flamingo 正式版的发布,AGP 8(Android Gradle Plugin 8)也正式进入大家的视野,这次 AGP 8 相关更新属于「断代式」更新,同时如果想体验 AGP 8,就需要升级到 Android Studio Flamingo 版…

揭秘速卖通卖家成功的绝佳秘籍,助您打造畅销店铺!

在竞争激烈的速卖通市场中,如何让您的店铺脱颖而出并实现畅销?林哥今天就跟大家讲一讲一些成功速卖通卖家的绝佳秘籍,帮助您引导高流量和高转化率,成就一个畅销的店铺。 ​一、精确定位目标受众 成功的速卖通店铺离不开精确的目标…

自动生成作文的软件有哪些?盘点五种自动生成作文软件

写作是一项需要花费大量时间和精力的任务,而自动生成作文的软件可以帮助我们节省大量的时间。这些软件通过分析和归纳大量的素材和语言模型,能够快速生成高质量的文章。相比于传统的写作方式,使用自动生成作文软件可以更快地完成文章&#xf…

一套完整的客户管理系统应该包含哪些模块呢?

一套完整的客户管理系统应该包含哪些模块呢? 想要弄清楚一个完整的客户管理系统应该具备哪些功能,首先得清楚系统使用者、使用场景以及主要功能这三个因素。 以我们公司为例: 主要使用者:运营人员、市场人员、产品人员。主要目…

Android Settings中Preference的理解以及使用

Preference 是Android App 中重要的控件之一,Settings 模块大部分都是通过 Preference 实现 优点: Preference 可以自动显示我们上次设置的数据,Android提供preference这个键值对的方式来处理这种情况,自动保存这些数据&#xff…

链接生成二维码怎么弄?这些制作方法分享给大家

在现代社会中,链接生成二维码已经成为了一个非常实用的工具。通过将链接转换为二维码,我们可以将它们轻松地分享给朋友、家人或同事,而无需手动输入URL或复制粘贴。这使得信息的传播变得更加快捷和高效。例如,你正在计划一个聚会&…

Spring第三方bean管理

文章目录 1.第三方bean管理1.1 Bean1.2 小结 2.第三方bean依赖注入2.1 简单类型:成员变量2.2 引用类型:方法形参2.3 小结 3.总结 1.第三方bean管理 1.1 Bean 首先看一下目录结构,APP里面就初始化了SpringConfig文件 SpringConifg中就一句话…