前言
最近部門有個需求,需要對一些客戶端IP做白名單,在白名單范圍內(nèi),才能做一些業(yè)務(wù)操作。按我們的部門的一貫做法,我們會封裝一個client包,提供給業(yè)務(wù)方使用。(注: 我們的項目是運行在K8S上)本以為這是一個不是很難的功能,部門的小伙伴不到一天,就把功能實現(xiàn)了,他通過本地調(diào)試,可以獲取到正確的客戶端IP,但是發(fā)布到測試環(huán)境,發(fā)現(xiàn)獲取到的客戶端IP一直是節(jié)點的IP,后面那個小伙伴排查了很久,一直沒頭緒,就找到我?guī)兔σ恢迸挪橐幌?。今天文章主要就是來?fù)盤這個過程
排查過程
首先先排查了一下他獲取客戶端IP的實現(xiàn)邏輯
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)) {
//根據(jù)網(wǎng)卡取本機配置的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;
}
}
這邏輯看著貌似沒問題,因為本地調(diào)試可以獲取到正確的客戶端IP,而測試環(huán)境獲取不到,大概率是環(huán)境有問題。于是就把方向轉(zhuǎn)為定位環(huán)境的差異性
環(huán)境定位
測試環(huán)境
我們測試環(huán)境的訪問流程為客戶端–> k8s service nodeport—>pod
通過搜索在https://kubernetes.io/zh-cn/docs/tutorials/services/source-ip/
在這篇文章找到答案。
Kubernetes Service 轉(zhuǎn)發(fā)場景下,無論使用 iptbales 或 ipvs 的負(fù)載均衡轉(zhuǎn)發(fā)模式,轉(zhuǎn)發(fā)時都會對數(shù)據(jù)包做 SNAT,即不會保留客戶端真實源 IP
整體流程
上文的鏈接也貼了解法
具體步驟就是
1、步驟一:業(yè)務(wù)pod的配置調(diào)度到指定節(jié)點
示例
spec:
nodeName: node1 #指定pod節(jié)點配置
containers:
- name: pod-name
2、步驟二:將業(yè)務(wù)的service yaml 默認(rèn)配置的externalTrafficPolicy: Cluster改為 externalTrafficPolicy: Local
示例
spec:
type: NodePort
externalTrafficPolicy: Local
3、步驟三:通過指定在pod上的node節(jié)點 + nodeport進(jìn)行訪問
示例
http://node1:nodeport
假設(shè)部署了node1和node2節(jié)點,只能通過node1:nodeport才能訪問到具體業(yè)務(wù),如果通過node2:nodeport,則請求的數(shù)據(jù)包會被拋棄
通過上述的方案,解決了在測試環(huán)境通過service nodeport獲取不到正確客戶端ip的問題
uat環(huán)境
當(dāng)測試環(huán)境沒問題后,將項目發(fā)布到UAT環(huán)境,然后不出意外的話,又出意外了。
uat的訪問流程為 客戶端- -> nginx+keepalive --> ingress --> pod
因為訪問方式不一樣,因此解法又有差異。通過搜索了解到*用戶ip的傳遞依靠的是X-Forwarded-參數(shù)。但是默認(rèn)情況下,ingress是沒有開啟的 因此我們需要開啟。開啟需要如下參數(shù)
-
use-forwarded-headers: 如果設(shè)置為True時,則將設(shè)定的X-Forwarded-* Header傳遞給后端,
當(dāng)Ingress在L7 代理/負(fù)載均衡器之后使用此選項。如果設(shè)置為 false 時,則會忽略傳入的X-Forwarded-*Header,
當(dāng) Ingress 直接暴露在互聯(lián)網(wǎng)或者 L3/數(shù)據(jù)包的負(fù)載均衡器后面,并且不會更改數(shù)據(jù)包中的源 IP請使用此選項。 - forwarded-for-header: 設(shè)置用于標(biāo)識客戶端的原始 IP 地址的 Header字段。默認(rèn)值X-Forwarded-For。如果想修改為自定義的字段名,則可以在configmap的data配置塊下添加:forwarded-for-header: “THE_NAME_YOU_WANT”
-
compute-full-forwarded-for: 將 remote address 附加到 X-Forwarded-For
Header而不是替換它。當(dāng)啟用此選項后端應(yīng)用程序負(fù)責(zé)根據(jù)自己的受信任代理列表排除并提取客戶端 IP。
詳細(xì)的介紹可以查看官網(wǎng)
https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#use-forwarded-headers?
我們在Ingress Nginx Controller 的 Configmap添加如下內(nèi)容
apiVersion: v1
kind: ConfigMap
......
data:
compute-full-forwarded-for: "true"
use-forwarded-headers: "true"
forwarded-for-header:"X-Forwarded-For"
配置后,發(fā)現(xiàn)沒鳥用,沒有效果。后面查了很多資料,發(fā)現(xiàn)網(wǎng)上都是那么配的,后面就覺得是不是nginx - keepalive 這一環(huán)節(jié)出了啥問題,于是就問了一下運維,看他nginx那邊是否有配置X-Forwarded-For,他說沒有,那我就問他能否配置一下,他的回答是因為nginx那邊啟用了 ssl_preread 模塊無法使用X-Forwarded-For
后面就問他能否改下,他回答說是后面公司要采用F5了,到時候在配置一下就好。而他目前事情比較多,沒時間幫我弄這個。
由于業(yè)務(wù)比較趕,運維又沒空搞,于是就和業(yè)務(wù)那邊溝通,采取了折中方案,就是通過自定義請求頭,我們在client包配置了一個屬性,那個屬性用來讓業(yè)務(wù)將白名單ip填進(jìn)去,示例
lybgeek:
whilte-ips: 192.168.1.1,192.168.2.1
在業(yè)務(wù)項目啟動的時候,client包會自動將配置的白名單塞入請求頭
header("x-custom-forwarded-for",whilteIps)
服務(wù)端那邊獲取客戶端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)) {
//根據(jù)網(wǎng)卡取本機配置的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;
}
}
其實做的事情,就將原來的工具類稍微重構(gòu)了一下,并加入自定義請求頭X-Custom-Forwarded-For文章來源:http://www.zghlxwxcb.cn/news/detail-500780.html
總結(jié)
這次的復(fù)盤總結(jié)就是很多東西沒那么想當(dāng)然,有些簡單的東西,里面可能也有有坑。當(dāng)遇到跨部門合作時,如果遇到一些不可抗力因素,我們除了向上反饋之外,還要有兜底方案,不然會非常被動文章來源地址http://www.zghlxwxcb.cn/news/detail-500780.html
到了這里,關(guān)于聊聊部署在K8S的項目如何獲取客戶端真實IP的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!