kubernetes ingress nginx rewrite partially working - sometimes wrong context path - url-rewriting

I'm setting up simple Kubernetes cluster using GKE. I've set up Java Spring boot app as Kubernetes Deployment and Service (with type Load Balancer, exposed on port 80). This app is working perfectly fine when accessed directly using external IP for the service.
I've also installed nginx-ingress for GKE and provided following Ingress resource:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/enable-rewrite-log: "true"
nginx.ingress.kubernetes.io/rewrite-target: /$1
name: rewrite
namespace: default
spec:
rules:
- host: "myhost.ddns.net"
http:
paths:
- backend:
serviceName: app-java
servicePort: 80
path: /app-java/?(.*)
From now on, funny things occur:
Problem 1: if I access site at http://myhost.ddns.net/app-java - I get only HTML file, all other resources have 404 (download attempt from http://myhost.ddns.net/foo.js instead of http://myhost.ddns.net/app-java/foo.js.
Problem 2: If I access site at http://myhost.ddns.net/app-java/ - I get both HTML file and all scripts and images. However, when I dig deeper, sending form content via POST fails. It's HTML code is:
<form action="/select" method="post">
Which tries to access http://myhost.ddns.net/select instead of http://myhost.ddns.net/app-java/select.
I found out that it might me strongly connected to this question: Thymeleaf template (in Spring boot application) behind reverse proxy not forming url's correctly. SOlution provided there is to write following nginx reverse proxy configuration:
location /app-java {
proxy_pass http://10.0.0.0:80;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Prefix /app-java;
}
However, I have no idea how to translate it to ingress resource yaml.
How can I fix this url-rewrite problem using ingress?
Edit: After hints from #HelloWorld I've reorganized application to serve content on /app-java context path (using Spring Boot 2 property server.servlet.context-path=/app-java). After that, I've changed rewrite-target to:
nginx.ingress.kubernetes.io/rewrite-target: /app-java/$1
And now it's working fine.

I see you are trying to make your browser to add a prefix to all paths in flight.
I believe you must have misunderstood the idea of X-Forwarded-Prefix and whatever was described in Thymeleaf template (in Spring boot application) behind reverse proxy not forming url's correctly, where community member described how to pass this header to your application. If you want to try it and see how it works you can achieve this by simply adding this annotation to your ingress object:
nginx.ingress.kubernetes.io/x-forwarded-prefix: "/app-java"
but notice this is not going to solve your problem.
Of course you can also add these headers to the response from application so that they get sent to your browser (what I believe you were expecting to achieve) but browsers don't care about these headers so there is no point in doing it.
If you still want to try it yourself and prove it, you can add this annotation to the ingress object:
nginx.ingress.kubernetes.io/configuration-snippet: |
more_set_headers "X-Forwarded-Prefix: /app-java";
The only way as far as I know to solve this issue is to modify your application code so that it's responding with correct paths.
You can find that many applications use customizable base path, configurable either by env variables, config files or by cli arguments.

Related

Extending Nginx config on Beanstalk doesn't rewrite urls properly

I have an existing Laravel API application running on Beanstalk. It's been lagging in updates on EBS, so currently I'm in the process of upgrading the platforms and CI/CD for this app. There one remaining problem I'm running into, which leaves me scratching my head at the 'but it should work'-level.
What I want
All URLs containing https://example.com/index.php/endpoint to be redirected to https://example.com/endpoint and show the same content as https://example.com/index.php/endpoint would (incl. subsequent the URL's slugs)
How I'm trying to do this
Due to this wonderful answer by cnst, I have the configuration below that seems to work for many (incl. some other online sources).
server{
index index.php;
if ($request_uri ~* "^(.*/)index\.php/*(.*)$") {
return 301 $1$2;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
# Remove from everywhere index.php
if ($request_uri ~* "^(.*/)index\.php(/?)(.*)") {
return 301 $1$3;
}
}
if (!-d $request_filename) {
rewrite ^/(.+)/$ /$1 permanent;
}
if ($request_uri ~* "\/\/") {
rewrite ^/(.*) /$1 permanent;
}
}
I'm putting this configuration in a file located at my_project/.platform/nginx/conf.d/proxy.conf, which according to AWS' documentation, should upload with the project and extend the nginx configuration. As far as I can tell, it does pick it up, since any typo will result in an error after eb deploy. I can also see on the server it has been added to /etc/nginx/conf.d/proxy.conf.
The Problem
Even though the extending proxy.conf is being deployed and the configuration in it seems to be picked up, the application won't pick up the rewrite and leave the application URLs running with the index.php instead of the rewrite.
https://example.com/index.php/endpoint → works
https://example.com/endpoint → results in a server generated 404
Nginx logs show 2021/02/12 14:23:24 [error] 7523#0: *35 open() "/var/www/html/public/api" failed (2: No such file or directory) which tells me it has searched for a file and never tried running it through index.php.
The Questions
What am I missing in my configuration?
Or is it something about EBS that I overlooked or misunderstood?
Is the index.php angry since I'm trying to hide its face from public view?
Solution moved from the question to an answer:
I gave it a weekend to see if anyone would know and went back to work.
First thing, I did is see if Beanstalk was picking up any
configuration, so I put an invalid variable in and see if that would
break the server. It didn't...
Second, I checked if my Beanstalk instance was actually using Nginx
(default) or got switch to Apache (httpd) for some reason (it includes
both). Via its GUI config I could easily tell it's Nginx.
Third, I viewed the nginx.conf on the server and checked how other
.conf files were being included. Part of it is seen here;
http {
[...]
include conf.d/*.conf;
map $http_upgrade $connection_upgrade {
[...]
}
server {
listen 80 default_server;
access_log /var/log/nginx/access.log main;
[...]
# Include the Elastic Beanstalk generated locations
include conf.d/elasticbeanstalk/*.conf;
}
}
Here lays the problem; I was including a file at the conf.d/*.conf
level with a second nginx server configuration block, which is
effectively overwritten with the standard server configuration block
by Beanstalks own config.
So there's two solutions here;
override the entire nginx.conf by including a new .platform/nginx/nginx.conf in your project, where you extend the
server block with your own config
or, in my opinion more gracefully, add .platform/nginx/elasticbeanstalk/proxy.conf to your project,
extending the server block specifically (but remove any server
blocks from your own config)
Solution 2 will gard that AWS can always update its default nginx.conf
without you having to watch out for it (unless they change the
location of the elasticbeanstalk configs).
I did try putting my configuration in
.platform/nginx/elasticbeanstalk/proxy.conf before, but that would
break the server, since I was including a server block, causing it
to double nest.
Lesson here;
add .platform/nginx/nginx.conf to override your entire Beanstalk Nginx configuration
add .platform/nginx/conf.d/your_conf.conf for any extensions to the http block
add .platform/nginx/conf.d/elasticbeanstalk/your_conf.conf for any extensions to the server block (or nesting within)

HTTP404 for resources when using url rewriting on Istio gateway

I'm trying to deploy a service to a specific url address on AKS. The following yaml let's me access the service at desired address, like xxxx.europe.cloudapp.azure.com/service-a. This works great, i've managed to hide the entire service under desired url:
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: istio-gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: istio
spec:
hosts:
- "*"
gateways:
- istio-gateway
http:
- match:
- uri:
prefix: /service-a
rewrite:
uri: /
route:
- destination:
host: service-a.default.svc.cluster.local
However when the welcome page is displayed, i only see text. No css/javascript/image files are loaded. Everything that this page is trying to load still has original url address without any rewriting being made by my gateway configuration. So the home page requests this:
http://xxxxx.europe.cloudapp.azure.com/icon.jpg
instead of this:
http://xxxxx.europe.cloudapp.azure.com/service-a/icon.jpg
What is the best way to handle rewriting urls for resources and links on a page? Do i have to manually change the urls on a home page?
EDIT:
To be more clear.
Rewriting of the url works as expected, the address is exactly how i want (the entire service is hidden under "xxxx.europe.cloudapp.azure.com/service-a".
Once i enter the "xxxx.europe.cloudapp.azure.com/service-a" i see service's welcome page, but without any css/jpegs/js loaded. Also the links visible on the welcome page don't work.
for example, "icon.jpg" is not loaded. Page wants to load it from http://xxxx.europe.cloudapp.azure.com/icon.jpg but it's not there anymore. Due to rewriting it's available at http://xxxx.europe.cloudapp.azure.com/service-a/icon.jpg which is as expected.
I kind of expected that the http://xxxx.europe.cloudapp.azure.com/icon.jpg request will be automatically rewritten to http://xxxx.europe.cloudapp.azure.com/service-a/icon.jpg. But obviously i was mistaken. So i wonder how i can handle the links within the service itself in a manageable way - i mean i can modify every possible link within the app, but what if we change the url again (from /service-a to /service-b). The service is written with ASP.NET Core, i will look for some kind of internal rewriting solution that is maintainable.
The rewriting is happening because of this part of the config:
- match:
- uri:
prefix: /service-a
rewrite:
uri: /
Which results that the matched prefix is replaced with the value of the rewrite.uri property.
Example 1: (virtual service is activated)
Original: http://www.page.com/service-a/icon.jpg
^--------^
Rewritten: http://www.page.com/icon.jpg
Example 2: (this virtual service is activated)
Original: http://www.page.com/service-a/service-a/icon.jpg
^--------^
Rewritten: http://www.page.com/service-a/icon.jpg
Example 3: (this virtual service is not activated, fall back on some other virtual service, or on a default route, or blackhole which returns 404)
Original: http://www.page.com/icon.jpg
Rewriting: DOESN'T HAPPEN
For rewriting there are no recommendations and cannot be, it's dependent on your services. Istio's docs for rewriting props can be found here
If every subdomain will have it's own service then this would be an option:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: istio
spec:
hosts:
- "service-a.domain.com"
gateways:
- istio-gateway
http:
- match:
- uri:
prefix: /
rewrite:
uri: /service-a
route:
- destination:
host: service-a.default.svc.cluster.local

redirect in spring mvc under nginx work incorrect

Good day!
I have a little problem: My Spring MVC application is located by http://localhost:8080/webapp/ address. I've installed nginx for redirecting requests like somedomain.com to my app:
server{
server_name somedomain.com;
access_log /var/log/app.log;
location /{
proxy_pass http://localhost:8080/webapp/;
include /etc/nginx/proxy.conf;
}
proxy.conf:
proxy_redirect off;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
In one of controller method I use redirect, like that:
#RequestMapping(value = "/xxx", method = RequestMethod.POST)
public String xxx(ModelMap modelMap) {
return "redirect:/yyy";
}
After that, browser should open http://somedomain.com/yyy, but it looks for http://somedomain.com/webapp/yyy.
What I'm doing wrong?
Thanks for help.
I see that no one answered this but for future needs I will.
First you need to add virtual host in tomcat for your domain and map your app to it.
Next:
Change
http://localhost:8080/webapp/;
To
How it works :
When you add virtual host in tomcat in server.xml under engine tag
you say to tomcat that I want you to map this domain to my webapp. So any request comes to tomcat has the domain name in the header then tomcat will forward it to the webapp
Second
In nginx.conf
Under HTTP tag you will add server tag that listen to port 80 and a specific server name
Listen 80;
server_name somedomain.com;
So the request comes to the server tag then it will map it to the location tag
location /{
proxy_pass http://localhost:8080/webapp/; //this need to be change and remove the webapp context
include /etc/nginx/proxy.conf;
}
Last the request will be forwarded to tomcat and there the vHost will continue the job

Grails Optimum way to save users avatar photos and show them in GSP?

Which folder should I use to save users avatar photos in Tomcat container, and how can I show it in view?
I know that it must be outside of war file, but I cannot access images with image tag which is outside of extracted war folder. An example on uploading and showing user profile photos is appreciated.
If they're just small avatars, you could consider persisting them to your database. When you need to display them again you can fetch them from the db and write them to the response's outputstream.
Here's an example of how to do this:
http://grails.1312388.n4.nabble.com/Load-image-from-database-and-show-it-in-a-gsp-td4644393.html
Acually Tomcat aren't good in serving static file, so it's very common to put a "frontend" in front of your Java app. Usually it's Nginx server. If you aren't using Nginx, I strongly suggest to start using it, Nginx can give you up to 5x speedup.
With Nginx you can store/and server images from any directory:
server {
location ~ ^\/(css|js|images)\/.*\.(gif|jpg|jpeg|png|ico|css|js)$ {
root PATH_TO_WEBAPP;
}
location ~ ^\/avatar\/.*$ {
root PATH_TO_AVATARS;
}
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
Where:
PATH_TO_WEBAPP is where tomcat have extracted you app (/etc/tomcat/webapps/myapp)
PATH_TO_AVATARS is your directory for storing avatars (/etc/my_avatars)
http://127.0.0.1:8080 just forward all other requests to Tomcat on localhost:8080
So, you'll be able to store avatars in own directory from Grails, and serve them through Nginx. For end user it will be a url like /avatars/myavatar.png, so you can use
resource tag as usual:
<img src="${resource(dir: 'avatars', file: 'myavatar.png')}" />

nginx proxy_pass to Jboss+Spring/Websphere+Portal projects

I have an nginx up at the front serving as a proxy to two servers, one running Websphere Portal Server and one running Spring on a Jboss server.
I'm currently having problems with the proxying of certain requests, for instance, I have the following:
server{
listen:8080;
server_name:localhost;
location /jbossSpring/ {
proxy_pass http://177.21.1.15:9000/Spring_project/;
}
location /webspherePortal/ {
proxy_pass http://177.21.1.15:9400/Portal_project/;
}
}
Now, this does the proxy from localhost:8080/jbossSpring/ and localhost:8080/webpsherePortal/ correctly, however, the pages I get keep requesting files that are located on localhost:8080/Spring_project/ and localhost:8080/Portal_project/.
Is there anyway for me to handle these in nginx? or do I have to modify the Spring/Portal projects to get the right url? (path dependencies probably?)
You may achieve this result by using http rewrite module, documented at ngx_http_rewrite_module
To give an idea, I guess your rewrites shall look like below (I haven't validated this)
server {
...
rewrite ^/Spring_project/(.*) /jbossSpring/$1 last;
rewrite ^/Portal_project/(.*) /webspherePortal/$1 last;
...
}

Resources