Regex paths in kubernetes Ingress not getting created as regex location in Nginx Ingress Controller - kubernetes-ingress

I am using Nginx Ingress Controller helm chart 0.6.0 and nginx ingress controller tag 1.8.0, kubernetes 1.15.
When I define a path in my Ingress which uses a regex, it does not work since it does not get specified as a regex in the Nginx controller's configuration file location section.
I need to use a version of nginx ingress controller which additionally supports VirtualServer and VirtualServerRoutes.
kind: Ingress
metadata:
name: test-ingress
namespace: ingress-test
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/use-regex: "true"
spec:
rules:
- host: test.any-xyz.com
http:
paths:
- path: /quack5/(.*)
backend:
serviceName: test
servicePort: 80
When I apply this, the nginx ingress controller has set up the location as a non-regex expression
$ kubectl -n nginx-ingress-controller exec nginx-ingress-controller-nginx-ingress-79984b9747-8pl4w -- grep -R quack /etc/nginx/conf.d
$ /etc/nginx/conf.d/ingress-test-test-ingress.conf: location /quack5/(.*) {
I expect to see location ~* /quack5/(.*) {

Try to add nginx.ingress.kubernetes.io/rewrite-target annotation to your Nginx Ingress Controller definition file.
Delete nginx.ingress.kubernetes.io/use-regex: "true" annotation.
EDIT:
I think the problem might be in the buildLocation function (&& location.Rewrite.Target != path). If the path and the nginx.ingress.kubernetes.io/rewrite-target are equal the nginx Location is not prefixed with ~* and handled as regex.
if len(location.Rewrite.Target) > 0 && location.Rewrite.Target != path {
if path == slash {
return fmt.Sprintf("~* %s", path)
}
// baseuri regex will parse basename from the given location
baseuri := `(?<baseuri>.*)`
if !strings.HasSuffix(path, slash) {
// Not treat the slash after "location path" as a part of baseuri
baseuri = fmt.Sprintf(`\/?%s`, baseuri)
}
return fmt.Sprintf(`~* ^%s%s`, path, baseuri)
}
return path
I would suggest a directive to specify that the spec.rules.http.paths.path should end up as a regex Location, regardless of what nginx.ingress.kubernetes.io/rewrite-target might be (even if it not specified). currently the only way to get the Location to be threatened as regex (added ~*) is to define a nginx.ingress.kubernetes.io/rewrite-target that is different from spec.rules.http.paths.path.
Please take a look: ingress-regex, ingress-path-matching, path-not-threated-as-regex.

Related

Ingress creates location / (root path match all) in nginx config on specific servername, avoiding path to be found on default servername

I am running into this problem (or behave) of Ingress I can't understand:
If I have a path: /api1 configed in ingess without a specified host name, like this:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: api1
namespace: somenamespace
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/proxy-body-size: 100m
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"
spec:
rules:
- http:
paths:
- path: /api1
pathType: ImplementationSpecific
backend:
serviceName: service-for-api1
servicePort: 1234
Then I can access this path in any hostname or IP address as long as it points to my server, e.g. my-server.com/api1 or 1.2.3.4/api1
And If I added another ingress config with a specified host name, like: /api2
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: api2
namespace: somenamespace
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/proxy-body-size: 100m
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"
spec:
rules:
- host: my-server.com # Only difference is this one has a host field
http:
paths:
- path: /api2
pathType: ImplementationSpecific
backend:
serviceName: service-for-api2
servicePort: 1234
Then I can access my-server.com/api2 all right, But when accessing my-server.com/api1, I got a 404 not found error. If try to access through IP, 1.2.3.4/api1, It works all right.
I read the nginx config generated by ingress, found out when no host is specified in ingress config, the path rule (/api1) will be added to servername _ which is the default server of nginx. And once a hostname is provided in ingress config, It (/api2) will be added to servername hostname block in nginx configfile. Also there will be a location / rule to match all path in this hostname, preventing all paths fallback to default server. So when access my-server.com/api2, the route is okey but when access my-server.com/api1, It matches location / in servername my-server.com block, preventing default server functioning at all.
Why do ingress do this??
I was believing not specify any hostname in ingress config means host: *, thus any host can match the rule. But it behaves like only hostname unknown to ingress can match host: *.
The nginx config I looked at is like this (forgive my for only copying part of it):
## start server _
server {
server_name _ ;
listen 80 default_server reuseport backlog=65535 ;
listen [::]:80 default_server reuseport backlog=65535 ;
listen 443 default_server reuseport backlog=65535 ssl http2 ;
listen [::]:443 default_server reuseport backlog=65535 ssl http2 ;
set $proxy_upstream_name "-";
...
location /api1 {
...
}
...
}
## end server _
## start server my-server.com
server {
server_name my-server.com
listen 80 ;
listen [::]:80 ;
listen 443 ssl http2 ;
listen [::]:443 ssl http2 ;
set $proxy_upstream_name "-";
location /api2 {
....
}
location / {
set $namespace "somenamespace";
set $ingress_name "service-for-api2";
set $service_name "";
set $service_port "";
set $location_path "/";
set $global_rate_limit_exceeding n;
...
# In case of errors try the next upstream server before returning an error
proxy_next_upstream error timeout;
proxy_next_upstream_timeout 0;
proxy_next_upstream_tries 3;
...
}
}
## end server my-server.com
When copying I noticed the "try the next upstream server before returning an error", Is it not work properly so I got a 404 instead of actually trying servername _ ? When I looked the nginx log, it said the 404 request is sent to service [upstream-default-backend], I don't know what it means.
After I discover this problem I deleted the specified hostname in /api2 and it work allright. But I jsut can't help wondering why ingress does this behave...

How to add multiple path in kubernetes-nginx-ingress-server-snippet-annotation to deny the traffic

I've the following code in the my ingress config, I would like to add more path(s) to deny the hits from Hackers. Is there any best practices to be followed to ensure no automated scans made by hackers?
---
kind: Ingress
metadata:
name: example
labels:
app: example
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "200"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/server-snippet: |
location ~* "^/boaform" {
deny all;
return 403;
}
location ~* "^/api" {
deny all;
return 403;
}
location ~* "^/user" {
deny all;
return 403;
}
namespace: example-ns
Rather than block some common URLs from hackers, you could do your best so they cannot even start the request. Good IP restrictions should be better to security than some blocked paths.
If you really need to listen to the world, then you could do yourself several kinds of implementations (request rate, geoIP, search for injections, etc) according to your application needs.
If you want no trouble (or less trouble) with these rules, you could try a WAF, like Cloudflare

Ingress-Nginx regex routing rule with named capture group in path

I'm creating Kubernetes Ingress resources for an nginx Ingress Controller. The paths that I'll be using have an ID that I need to extract into a variable that may be in a different location in the path. E.g.:
http://myservice/environment/<someenv>/room/<roomid>
http://myservice/join/<roomid>
If I use a non-named capture group in the regex then the first path roomid will be $2 and for the second it'll be $1. Ideally I would be able to use a named capture group to assign it to a specific variable that I can easily use but creating an Ingress with the following path with a named capture group results in an error
# ingresses.networking.k8s.io "nginx-consistent" was not valid:
# * spec.rules[0].http.paths[0].path: Invalid value: "/join/(?<roomid>.*)": must be a valid regex
Is there a supported way to use named capture groups?
The current configuration doesn't have much interesting in it. The following is a trimmed down config.
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: {{.Release.Name}}-nginx-route-by-roomid
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/upstream-hash-by: "roomid-$roomid"
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/configuration-snippet: |
proxy_set_header ingress-handler nginx-route-by-roomid;
spec:
rules:
- http:
paths:
- backend:
serviceName: {{ .Values.service.serviceName }}
servicePort: {{ .Values.service.servicePort }}
path: /api/v1/rooms/(?<roomid>[^/]*)
- backend:
serviceName: {{ .Values.service.serviceName }}
servicePort: {{ .Values.service.servicePort }}
path: /api/v1/players/([^/]*)/rooms/(?<roomid>[^/]*)

Custom response for Ingress-Nginx External Authentication

I have deployed my Kubernetes cluster on EKS. I have an ingress-nginx which is exposed via load balancer to route traffic to different services. In ingress-nginx first request goes to auth service for authentication and if it is a valid request then I allow it to move forward. This is done using ingress-nginx annotation nginx.ingress.kubernetes.io/auth-url.
Auth service is developed using FastAPI. In case of 401 response from fastAPI look like this
FASTAPI
But when I use ingress-nginx the response look like this
INGRESS_NGINX
Is there a way to get JSON respone from Ingress-nginx?
Ingress File
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ingress-service
annotations:
kubernetes.io/ingress.class: 'nginx'
nginx.ingress.kubernetes.io/use-regex: 'true'
nginx.ingress.kubernetes.io/rewrite-target: /$1
nginx.ingress.kubernetes.io/auth-response-headers: item_id
nginx.ingress.kubernetes.io/auth-method: POST
nginx.ingress.kubernetes.io/auth-url: http://pth-auth.default.svc.cluster.local:8000/item/1
# UPDATE THIS LINE ABOVE
spec:
rules:
- http:
paths:
- path: /?(.*)
# UPDATE THIS LINE ABOVE
backend:
serviceName: client-cluster-ip-service
servicePort: 3000
- path: /api/?(.*)
# UPDATE THIS LINE ABOVE
backend:
serviceName: server-cluster-ip-service
servicePort: 5000
- path: /pth-auth/?(.*)
# UPDATE THIS LINE ABOVE
backend:
serviceName: pth-auth
servicePort: 8000
Here's a solution that worked for me. It allows the auth service to return a custom error message for each request.
The caveat is that because nginx can't access auth response body, the pth-auth service needs to put the data in Pth-Auth-Error header (base64-encoded).
This example handles 401, 500, and a special case when pth-auth service is unavailable.
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ingress-service
annotations:
kubernetes.io/ingress.class: 'nginx'
nginx.ingress.kubernetes.io/use-regex: 'true'
nginx.ingress.kubernetes.io/rewrite-target: /$1
nginx.ingress.kubernetes.io/auth-response-headers: item_id
nginx.ingress.kubernetes.io/auth-method: POST
nginx.ingress.kubernetes.io/auth-url: http://pth-auth.default.svc.cluster.local:8000/item/1
# UPDATE THIS LINE ABOVE
nginx.ingress.kubernetes.io/configuration-snippet: |
# Redirect auth errors to custom named locations
error_page 401 = #ingress_service_custom_error_401;
error_page 500 = #ingress_service_custom_error_500;
# Grab data from auth error response
auth_request_set $pth_auth_error $upstream_http_pth_auth_error;
auth_request_set $pth_auth_error_content_type $upstream_http_content_type;
auth_request_set $pth_auth_status $upstream_status;
nginx.ingress.kubernetes.io/server-snippet: |
location #ingress_service_custom_error_401 {
internal;
# Decode auth response header
set_decode_base64 $pth_auth_error_decoded $pth_auth_error;
# Return the error from pth-auth service if any
if ($pth_auth_error_decoded != ""){
add_header Content-Type $pth_auth_error_content_type always;
return 401 $pth_auth_error_decoded;
}
# Fall back to default nginx response
return 401;
}
location #ingress_service_custom_error_500 {
internal;
# Decode auth response header
set_decode_base64 $pth_auth_error_decoded $pth_auth_error;
# Return the error from pth-auth service if any
if ($pth_auth_error_decoded != ""){
add_header Content-Type $pth_auth_error_content_type always;
return 500 $pth_auth_error_decoded;
}
# Return a hardcoded error in case no pth-auth pods are available
if ($pth_auth_status = 503){
add_header Content-Type application/json always;
return 503 "{\"msg\":\"pth-auth service is unavailable\"}";
}
# Fall back to default nginx response
return 500;
}
spec:
rules:
- http:
paths:
- path: /?(.*)
# UPDATE THIS LINE ABOVE
backend:
serviceName: client-cluster-ip-service
servicePort: 3000
- path: /api/?(.*)
# UPDATE THIS LINE ABOVE
backend:
serviceName: server-cluster-ip-service
servicePort: 5000
- path: /pth-auth/?(.*)
# UPDATE THIS LINE ABOVE
backend:
serviceName: pth-auth
servicePort: 8000
Inspired by: https://stackoverflow.com/a/31485557/99237
Troubleshooting tips:
Here's the template nginx ingress uses when transforming the ingress annotations into nginx config file.
Connect to the ingress controller pod and look at /etc/nginx/nginx.conf to view the generated nginx config.
This worked for me, took reference from here
https://github.com/kubernetes/ingress-nginx/issues/2292
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ingress-service
annotations:
kubernetes.io/ingress.class: 'nginx'
nginx.ingress.kubernetes.io/use-regex: 'true'
nginx.ingress.kubernetes.io/rewrite-target: /$1
nginx.ingress.kubernetes.io/auth-response-headers: item_id
nginx.ingress.kubernetes.io/auth-method: POST
nginx.ingress.kubernetes.io/auth-url: http://pth-auth.default.svc.cluster.local:8000/items/1
nginx.ingress.kubernetes.io/server-snippet: |
location = /error/401 {
proxy_method POST;
proxy_pass http://pth-auth.default.svc.cluster.local:8000/error/401;
}
location = /error/403 {
proxy_method POST;
proxy_pass http://pth-auth.default.svc.cluster.local:8000/error/403;
}
nginx.ingress.kubernetes.io/configuration-snippet: |
error_page 401 /error/401;
error_page 403 /error/403;
# UPDATE THIS LINE ABOVE
spec:
rules:
- http:
paths:
- path: /?(.*)
# UPDATE THIS LINE ABOVE
backend:
serviceName: client-cluster-ip-service
servicePort: 3000
- path: /api/?(.*)
# UPDATE THIS LINE ABOVE
backend:
serviceName: server-cluster-ip-service
servicePort: 5000
- path: /pth-auth/?(.*)
# UPDATE THIS LINE ABOVE
backend:
serviceName: pth-auth
servicePort: 8000
You just need to tell nginx in case of error route traffic to this location and their your function will handle specific errors. In my case, function is error/{error_code}.

Redirect specific sub directory to new url in Ingress

I need to redirect a specific URL to a new one.
I can use this code to redirect root domain but it doesn't work for subdirectory likes testdomain.com/specific-url
nginx.ingress.kubernetes.io/configuration-snippet: |
if ($host = 'testdomain.com/specific-url') {
return 308 https://testdomain.com/new-url;
}
Try to use below approach I suggested here.
And copy paste for future:
My advice is to use NGINX Plus Ingress Controller annotation functionality if you can afford it.
You can find official example here.
Example:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: cafe-ingress
annotations:
nginx.org/rewrites: "serviceName=tea-svc rewrite=/;serviceName=coffee-svc rewrite=/beans/"
spec:
rules:
- host: cafe.example.com
http:
paths:
- path: /tea/
backend:
serviceName: tea-svc
servicePort: 80
- path: /coffee/
backend:
serviceName: coffee-svc
servicePort: 80
Below are the examples of how the URI of requests to the tea-svc are rewritten (Note that the /tea requests are redirected to /tea/).
/tea/ -> /
/tea/abc -> /abc
Below are the examples of how the URI of requests to the coffee-svc are rewritten (Note that the /coffee requests are redirected to /coffee/).
/coffee/ -> /beans/
/coffee/abc -> /beans/abc