A bit inexperienced at this, so looking for some help on how I can do this! Sorry if it is unclear of what I'm looking to do.
I have an Angular front-end that is location based. I am hoping to be able to use the users public IP by taking it and using a geolocation service to give me the city/region that they are from.
From one of the answers below, I now am getting an IP address in SpringBoot, but unfortunately it is the IP address of the DigitalOcean droplet.
I am using a Spring Security Custom Filter to perform this action. This sits behind the Angular application.
I was hoping that I would be able to use the HttpServletRequest request.getRemoteAddr()
to get the IP address, but I have found that once the SpringBoot application is deployed on Kubernetes, which sits behind an NGINX proxy, the getRemoteAddr() gives me the Digital Ocean droplet IP.
Due to this, I was hoping I would be able to pass this client IP address forward as the X-Forwarded-For header, or even a custom X-Client-IP header. How would I go about this if I'm performing these actions as part of a Spring Security Filter? Is it even possible?
location / {
proxy_set_header Host $host;
proxy_set_header X-Client-IP $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Proto $scheme;
rewrite .* /index.html break;
}
private static String getClientIpAddr(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
All of these return null apart from request.getRemoteAddr() (which returns the loadbalancer IP).
kind: Deployment
apiVersion: apps/v1
metadata:
name: example-webapp-frontend-deployment
spec:
revisionHistoryLimit: 3
minReadySeconds: 30
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: example-webapp-frontend
template:
metadata:
labels:
app: example-webapp-frontend
spec:
restartPolicy: Always
containers:
- name: example-webapp-frontend
image: docker-example/example-webapp-frontend:latest
imagePullPolicy: Always
ports:
- containerPort: 80
imagePullSecrets:
- name: docker-creds
---
apiVersion: v1
kind: Service
metadata:
name: example-webapp-frontend-service
spec:
selector:
app: example-webapp-frontend
ports:
- protocol: TCP
targetPort: 80
port: 80
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: example-webapp-bff-deployment
spec:
revisionHistoryLimit: 3
minReadySeconds: 30
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: example-webapp-bff
template:
metadata:
labels:
app: example-webapp-bff
spec:
restartPolicy: Always
containers:
- name: example-webapp-bff
image: docker-example/example-webapp-bff:latest
imagePullPolicy: Always
ports:
- containerPort: 9874
imagePullSecrets:
- name: docker-creds
---
apiVersion: v1
kind: Service
metadata:
name: example-webapp-bff-service
spec:
selector:
app: example-webapp-bff
ports:
- protocol: TCP
targetPort: 9874
port: 9874
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/proxy-read-timeout: "12h"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
name: webapp-ingress
namespace: default
spec:
tls:
- hosts:
- example.com
secretName: example-tls
rules:
- host: example.com
http:
paths:
- path: /
backend:
serviceName: webapp-frontend-service
servicePort: 80
- host: example.com
http:
paths:
- path: /api/
backend:
serviceName: webapp-bff-service
servicePort: 9874
server.forward-headers-strategy=NATIVE
server.tomcat.remote-ip-header=x-forwarded-for
server.tomcat.internal-proxies="\
10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|\
192\\.168\\.\\d{1,3}\\.\\d{1,3}|\
169\\.254\\.\\d{1,3}\\.\\d{1,3}|\
127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|\
172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|\
172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|\
172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}\
172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}\
<digital-ocean-droplet-IP>\
<digital-ocean-droplet-IP>\
<digital-ocean-loadbalancer-IP>"
I have a cluster that has a single pool of two nodes. I have a Digital Ocean load balancer that sits in front of it. Currently running version 1.17.5.,
Anyone able to provide suggestions? Thanks in advance.
Spring boot contains a filter to integrate with reverse proxies out of the box and sets the remote address on the request appropriately. You may need to configure the allowed IPs to accept the header.
Here is an example:
server:
forward-headers-strategy: native
tomcat:
remoteip:
remote-ip-header: x-forwarded-for
#private: 10/8, 192.168/16, 169.254/16, 127/8, 172.16/12
internal-proxies: "\
10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|\
192\\.168\\.\\d{1,3}\\.\\d{1,3}|\
169\\.254\\.\\d{1,3}\\.\\d{1,3}|\
127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|\
172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|\
172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|\
172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}"
And here is the documentation: https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-use-behind-a-proxy-server
It is important, though, that the right x-forwarded-for header is received by the spring application. Since you are deploying on kubernetes you are most probably using an Ingress to route external requests to the right kubernetes service. Ingress is most often implemented using nginx or traefik and must be configured to accept the x-forwarded-for header, if a L7 reverse proxy is used as external load balancer and TLS termination, if applicable.
If using nginx create a config map to configure nginx
use-forwarded-headers: "true"
proxy-real-ip-cidr: "10.0.0.0/8,..."
When TLS is not terminated externally the external load balancer will not be able to add headers due to the encryption (L3 load balancer). In that case the only option is to use the PROXY protocol which adds meta data to the forwarded tcp stream containing the real remote address.
Example configuration for nginx
use-proxy-protocol: 'true'
Note that there some issues when using proxy protocol and cert-manager to manage TLS certificates with external (kubernetes) LoadBalancer
configurations. (See https://github.com/kubernetes/kubernetes/issues/66607 and https://github.com/kubernetes/ingress-nginx/issues/3996 )
Once the setup is correct in all places, the remote ip should be set correctly.
When debugging this kind of setup I recommend to begin on the most outer layer by intercepting (ngrep, tcpdump) or logging requests and move forward step by step.