Nginx Ingress Controller trailling slash with HTTPS redirect - kubernetes-ingress

Nginx Ingress Controller trailling slash with HTTPS redirect
I'm trying to redirect requests from HTTP to HTTPS using an Ingress with Nginx Ingress Controller. My app is written in Django v3.0.7, my Nginx Controller is v0.46.0 and k8s v1.19.8.
I have the following ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: INGRESS-NAME
namespace: INGRESS-NS
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/rewrite-target: /api/v1/$1/
cert-manager.io/cluster-issuer: "ISSUER-NAME"
nginx.ingress.kubernetes.io/permanent-redirect-code: '308'
spec:
tls:
...
rules:
- host: MY-DOMAIN
http:
paths:
- path: /api/v1/?(.*)
pathType: Prefix
backend:
service:
name: SVC-NAME
port:
number: SVC-PORT
Requests at https://.../api/v1/get-token/, raise this error:
[05/May/2021:20:39:49 +0000] "POST /api/v1/get-token// HTTP/1.1" 404 => POST get an extra / at the end. But the same request with HTTP or https://.../api/v1/get-token (no trailing /) is fine.
If I remove the
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /api/v1/$1/
The redirect removes trailing / and it causes POST to turn into GET in all HTTP POST request causing an 403 - Method not allowed as shown in Nginx Logs:
[05/May/2021:20:54:52 +0000] "POST /api/v1/get-token HTTP/1.1" 308 164
[05/May/2021:20:54:53 +0000] "POST /api/v1/get-token HTTP/1.1" 301 0
[05/May/2021:20:54:53 +0000] "GET /api/v1/get-token/ HTTP/1.1" 405
but HTTP POST request works fine with http://.../api/v1/get-token// (two trailing /).
Is there a way to solve this problem? The 308 HTTP -> HTTPS redirect is important, so I can't remove it, but is there a way to force requests to have one, and only one, trailing /? Thanks.

There are two problems here
Problem #1
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /api/v1/$1/
causes requests sent to https://.../api/v1/get-token/ to end with HTTP 404 Not Found, but https://.../api/v1/get-token woks fine.
Why?
Because trailing / at the end of nginx.ingress.kubernetes.io/rewrite-target: /api/v1/$1/ rewrite is added to the URL, and /api/v1/get-token// leads to a resource that does not exist.
What to do about it?
Change path key to /api/v1/?(.*\b)/. I'm not 100% sure it will work, but it's worth a try.
or
Remove trailing / from rewrite.
Now, doing that causes problem #2.
Problem #2
Requests to https://.../api/v1/get-token ends with 405 Method Not Allowed.
Why?
First redirection works fine (HTTP 308), however request is redirected again with HTTP 301.
MDN article on HTTP 301 states:
Even if the specification requires the method (and the body) not to be altered when the redirection is performed, not all user-agents align with it - you can still find this type of bugged software out there. It is therefore recommended to use the 301 code only as a response for GET or HEAD methods and to use the 308 Permanent Redirect for POST methods instead, as the method change is explicitly prohibited with this status.
Basically HTTP 301 causes POST to become GET, and GET is not allowed, hence HTTP 405.
What to do about it?
Make sure not to redirect requests twice, especially with HTTP 301.

Related

Path (AKS Application Gateway Ingress Controller)

I have a cluster in AKS with AGIC... the system has changed the route system so that the url appears, for example, table.system.com/login or table.system.com/box.
If I go to table.system.com it automatically redirects me to table.system.com/login without any problem, but if I press F5 there it throws me a "404 Not Dound" or if I write directly in the url table.system.com/login I get the same "404 not found" error (which is an external request and goes through the ingress).
I thought a path / and "Prefix" should take everything after / of my url ( /* doesn't work)
The same thing happens with all the internal links, if I browse from the web there is no problem, but if I refresh the page or if I write the url manually, it gives me the 404 error.
my ingress.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: system-ingress
namespace: test
annotations:
kubernetes.io/ingress.class: azure/application-gateway
appgw.ingress.kubernetes.io/ssl-redirect: "true"
appgw.ingress.kubernetes.io/appgw-ssl-certificate: "cert-system.cl"
spec:
rules:
- host: table.system.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: test-service
port:
number: 80

Ingress rewrite string is being ignored

The requirement is to access the burger service in https://meals.food.com/burger2.
The context path within the app is /burger.
Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /burger/$2
spec:
rules:
- host: meals.food.com
http:
paths:
- backend:
service:
name: burger
port:
number: 80
path: /burger2(/|$)(.*)
pathType: Prefix
Upon checking the ingress controller logs:
[05/Jan/2022:13:54:11 +0000] "GET // HTTP/1.1" 304 0 "-" "Mozilla/5.0
(X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/96.0.4664.110 Safari/537.36" 957 0.002 [anotherservice-80] []
x.x.x.x:80 0 0.002 304 230200x023
Is my ingress config correct?
My suspicion is that something is altering the request between my request from the browser to ingress-controller.
Is my ingress config correct? My suspicion is that something is altering the request between my request from the browser to ingress-controller.
Your ingress config looks OK. I do not see any errors in it. He will act as follows:
The example address meals.food.com/burger2/blah-blah-blah will be rewirted to meals.food.com/burger/blah-blah-blah. If that was your intention then config is fine.
However you have got 304 HTTP code.
The HTTP 304 Not Modified client redirection response code indicates that there is no need to retransmit the requested resources. It is an implicit redirection to a cached resource. This happens when the request method is safe, like a GET or a HEAD request, or when the request is conditional and uses a If-None-Match or a If-Modified-Since header.
The equivalent 200 OK response would have included the headers Cache-Control, Content-Location, Date, ETag, Expires, and Vary.
In other words
When the browser receives a request, but does not know whether it has the latest version of a write, it sends a conditional validation request, communicating the last modified date and time to the server via the If-Modified-Since or If-None-Match header.
The server then checks these headers and determines if their values are the same. If so - the server will send back the HTTP 304 code and the browser will use the cached copy of the resource. If not, it means that the file has been modified, so the browser will save a new copy by sending HTTP 200 code.
In your case it looks as if someone tried to download the same (unchanged) resource multiple times and therefore got the code 304. If so, everything is fine.

Block country by geoip with nginx ingress controller and modsecurity

I'm trying to block one country on nginx ingress controller with modsecurity enabled but still no luck. My configuration is:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/modsecurity-snippet: |
SecRuleEngine On
SeqRequestBodyAccess On
SecAuditEngine On
SecAuditLogParts ABIJDEFHZ
SecAuditLog /var/log/modsec_audit.log
SecGeoLookupDb /etc/nginx/geoip/GeoIP.dat
SecRule REMOTE_ADDR "#geoLookup" "chain,id:22,drop,msg:WrongIP"
SecRule GEO:COUNTRY_CODE "#streq GR"
nginx.ingress.kubernetes.io/whitelist-source-range: 0.0.0.0/0
when I try to access URL with curl I'm getting HTTP 200 instead of HTTP 403.
Start by validating that your solution works at all:
Try changing your condition from:
"#streq GR"
to:
"!#streq GR"
and testing again. If your rules are actually working then the result you get should flip: 200 to 403 or 403 to 200.

Nginx ingress returns 502 after POST with redirect

My hosting provider is DigitalOcean. Main page (e.g. /) requires user to be authenticated. If user is not authenticated he is redirected to identity server. Once user enters credentials POST request is sent to application as last step of OAuth flow.
Application receives this request and handles it correctly. This fact was verified by logs produced by application. It performs redirect to main page https://ui.example.com (302 status code + some cookies).
User sees 502 error message issues by gateway.
Ingress configuration is very simple (and it works for GET requests):
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: rie-ui-ingress
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- ui.example.com
secretName: rie-ui-prd-tls
rules:
- host: ui.example.com
http:
paths:
- backend:
serviceName: rie-ui-svc
servicePort: 9000
I'm wondering what could be wrong with this configuration?
UPDATE 1 The following log message was found in log stream of Ingress controller:
2019/11/20 05:33:09 [error] 1465#1465: *813467 upstream sent too big header while reading response header from upstream, client: 10.131.18.136, server: ui.example.com, request: "POST /signin-oidc HTTP/2.0", upstream: "http://10.244.1.228:9000/signin-oidc", host: "ui.example.com"
I have impression that this is something to fix with Nginx controller ingress settings?
ANSWER:
After studying documentation the following changes where made to Ingress definition:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: rie-ui-ingress
annotations:
...
nginx.ingress.kubernetes.io/proxy-buffer-size: "64k"
nginx.ingress.kubernetes.io/proxy-buffers-number: "8"
These settings increase buffer sizes. More details can be found on the following page.

how to make ingress nginx return 200 on each request

I want my ingress nginx to return 200 on every request.
If I had access to the nginx configuration, I would have done something like this:
location = /health {
return 200;
}
But I'm not sure how to do it in ingress configuration YAML
Consider that the Kubernetes' ingress object, when using the Nginx controller, is mostly meant to do routing instead of serving requests by itself. What is actually serving them is the backend deployed in the cluster and, this part is what is returning the status codes, not the ingress.
The controller has a feature something similar to what you want, but for errors only. This only makes the ingress to add some headers so that, a backend can interpret them an return some non-standard code response.
It might be possible to make them respond 200 if you modify this backend. However, I find less disruptive and more straight-forward to just "catch-all" all the incoming requests in the ingress to redirect them to a custom Nginx backend that always responds 200 (you already have the Nginx configuration for that):
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/use-regex: "true"
labels:
app: all-good
name: happy-ingress
spec:
rules:
- host: "*"
http:
paths:
- path: /(.*)
backend:
serviceName: ok-status-test
servicePort: 80
With this approach, you can even add non-200's backends to it, and match them using regex, so the ingress can be fully reusable.