Hosting webapp with relative URLs behind Kubernetes NGINX ingress controller - kubernetes-ingress

I am hosting a web application inside a Kubernetes 1.13 cluster behind an NGINX ingress controller. The Ingress specifies path: /my-webapp/?(.*) and annotations:
{ nginx.ingress.kubernetes.io/rewrite-target: /$1 } such that the webapp can be reached from outside the cluster at http://my-cluster/my-webapp. (The ingress controller is exposed as my-cluster.)
One remaining problem is that the webapp contains "relative" URLs that refer e.g. to CGI scripts and to CSS stylesheets. For instance, the stylesheet in <link rel="stylesheet" type="text/css" href="/my-stylesheet.css" /> currently does not load. I assume this is because the browser requests it at the wrong URL (http://my-cluster/my-webapp/my-stylesheet.css would be right) and that some more annotations are needed.
What is the correct configuration is such a case?
UPDATE The inspector reveals that the browser currently requests the stylesheet from URL http://my-cluster/my-stylesheet.css, which is indeed wrong and needs to be fixed.
UPDATE This looks like a related problem with an NGINX reverse proxy in general, not a Kubernetes NGINX ingress controller in particular. I wonder if and how the suggested recipes can also serve in this particular case. For a start, I have tried switching to a relative URL for referencing the stylesheet (per recipe One in the accepted answer), but this also has not worked so far:
<link rel="stylesheet" type="text/css" href="my-stylesheet.css" />, the browser apparently still tries to get the stylesheet from http://my-cluster/my-stylesheet.css, although it displays http://my-cluster/my-webapp in the URL bar and the inspector reports the same URL as baseURI.

Now this combination of annotations seems to do the trick:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
proxy_set_header Accept-Encoding "";
sub_filter '<head>' '<head> <base href="/my-webapp/">';
nginx.ingress.kubernetes.io/rewrite-target: /$1
nginx.ingress.kubernetes.io/use-regex: "true"
I am currently using this version of the ingress controller:
-------------------------------------------------------------------------------
NGINX Ingress controller
Release: 0.23.0
Build: git-be1329b22
Repository: https://github.com/kubernetes/ingress-nginx
-------------------------------------------------------------------------------

In my situation, I must replace URL-s from wsdl service page and this my solution code that was written thanks to the above code
nginx.ingress.kubernetes.io/configuration-snippet: |
sub_filter 'http://example.com:80/project/domain/' 'http://example.com:80${PUBLISH_PATH}/project/domain/';
sub_filter_once off;
sub_filter_types text/xml;
${PUBLISH_PATH} - is the kuber domain specific

Related

Setting custom Request Headers through nginx ingress controller

I have a kubernetes cluster using nginx controller to proxy requests to the backend. There is an LB in the front.
LB <-> Nginx Ingress <-> WLS in K8s
When I terminate the SSL at the LB, and the backend sends a redirect it will send the redirect with location that starts with http. However, WebLogic recognizes WL-PROXY-SSL request header to send a https redirect.
I am trying to set the request header on the Nginx Ingress controller for a specific URL patterns only.
Tried using
nginx.ingress.kubernetes.io/configuration-snippet: |
proxy_set_header WL-PROXY-SSL: "true";
It didn't work.
Even tried ....
more_set_headers "WL-PROXY-SSL: true";
nginx.org/location-snippets: |
proxy_set_header "WL-PROXY-SSL: true";
Also tried the custom-headers module but it sets for all resources. While I see the entry in the nginx.conf, it is not taking effect even with global custom-headers configMap also.
Is there any good example of adding this header to the request ?
Thanks in advance.

Ingress with and without host

It is really getting hard to understand and debug the rules for ingress. Can anyone share a good reference?
The question is how the ingress works without specifying the host?
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/force-ssl-redirect: \"false\"
name: my-app
spec:
rules:
http:
paths:
- backend:
path: /
serviceName: my-app
servicePort: http
Upon assigning a host (e.g.- host: aws-dsn-name.org) it doesn't work.
Upon changing the path to path: /v1/ it also doesn't work :( .
How can I debug/check whether the mapping is correctly done?
Additionally, when to use extensions/v1beta1 or networking.k8s.io/v1beta1
There is pretty good documentation available here for getting started. It may not cover all aspects but it does answer your questions. Ingress controller is basically a reverse proxy and follows similar ideas.
The snippet you have shared is called single backend or single service ingress. / Path would be default. It's the only entry so every request on the exposed port will be served by the tied service.
Host entry; host: aws-dns-name.org should work as long as your DNS is resolving aws-dns-name.org to the IP of a node in the cluster or the LB fronting the cluster. Do a ping to that DNS entry and see if it's resolving to the target IP correctly. Try curl -H 'Host: aws-dns-name.org' IP_Address to verify if ingress responding correctly. NGINX is using Host header to decide which backend service to use. If you are sending traffic to IP with a different Host entry, it will not connect to the right service and will serve default-backend.
If you are doing path based routing, which can be combined with host based routing as well, NGINX will route to the correct backend service based on the intercepted path. However, just like any other reverse proxy, it will send the request to the specified path (http://service:80/v1/). Your application may not be listening on /v1/ path so you will end up with a 404. Use the rewrite-target annotation to let NGINX know that you serving at /.
API resources versions do switch around in K8s and can be hard to keep up with. The correct annotation now is networking.k8s.io/v1beta1 (networking.k8s.io/v1 starting 1.19) even though the old version is working but eventually will stop working. I have seen cluster upgrades break applications because somebody forgot to update the API version.

SPA applications (Vue, React, Angular) not working properly behind Nginx ingress controller on Kubernetes

We are using AKS (Azure Kubernetes Service) for managed Kubernetes clusters and for the biggest part we are happy with the benefit the platform brings but we face some issues as well.
On AKS if you host a service of LoadBalancer type it automatically creates a new dynamic IP address (Azure resource) and assigns it to the service. This is not very optimal if you want to whitelist and simply does not make sense hence we switched to Nginx ingress controller (no particular reason to choose Nginx). We have a lot of apps - APIs, SPAs, 1 ingress controller for the whole cluster and separate cluster per environment - QA/Sta/Prod etc.. So we need to manage routing somehow and the ingress path parameter felt like the way to go. Example:
http://region.azurecloud.com/students/
http://region.azurecloud.com/courses/
where students and courses are the ingress paths and then you can add /api/student for example to access a particular API. The result would be http://region.azurecloud.com/students/api/student/1 which is not perfect but does the job for now.
This is how the ingress looks like:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: students-api-ingress
namespace: university
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
rules:
- host: https://region.azurecloud.com
http:
paths:
- backend:
serviceName: students-api-service
servicePort: 8001
path: /students(/|$)(.*)
This however does not work very well with SPA applications such as React, Vue or Angular. We face the same problem regardless of technology. They are hosted behind Nginx in docker so this is how the Dockerfile looks like:
# build environment
FROM node:12.2.0-alpine as build
WORKDIR /app
COPY package*.json /app/
RUN npm install --silent
COPY . /app
RUN npm run build
# production environment
FROM nginx:1.16.0-alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
And here is the nginx.conf file:
server {
listen 80;
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html =404;
index index.html index.htm;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin: $http_origin');
add_header 'Access-Control-Allow-Origin: GET, POST, DELETE, PUT, PATCH, OPTIONS');
add_header 'Access-Control-Allow-Credentials: true');
add_header 'Vary: Origin');
}
add_header 'Access-Control-Allow-Origin' "$http_origin" always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, PATCH, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With' always;
}
include /etc/nginx/extra-conf.d/*.conf;
}
The problem comes when assets such as .js files or images are accessed by the application. It creates the url in the format ingress.host/asset.name such as http://region.azurecloud.com/2342424ewfwer.js instead of including the ingress path as well which would look http://region.azurecloud.com/spa/2342424ewfwer.js
and the result is a 404 not found error for all assets.
The applications work properly if the ingress path is just set to / without any rewrite annotations but this is a problem because you cannot have multiple applications using the base ingress host. One solution is to use a separate ingress controller for each SPA application but this brings us back to the initial issue with load balancers - separate load balancer and IP address for each SPA app which is what we want to avoid here.
I guess I am not the only person who is hosting SPA applications behind nginx ingress controller on Kubernetes but all similar topics I managed to find ended pretty much nowhere with no clear solution what should be done or the suggestions did not work for us. I wonder where does the problem come from - the nginx web server or the ingress controller and are ingress controllers generally the way to go for managing application routing on Kubernetes. I would appreciate any help or advice on this.
Thank you,
R
The way I usually deal with this for SPAs is to have different hostnames for each SPA. For example, in a non-production cluster having two SPAs named student-portal and teacher-portal, I would create DNS records for student-portal.mydomain.com, teacher-portal.mydomain.com pointing to the public IP of the cluster load balancer.
Include the domain name in the rules of the ingress resource.
I find this is the most efficient way and avoids needing to deal with each SPA framework individually.

Kuberenetes Ingress - Whitelist particular APIs

We have a host of microservices all being served via a single api-gateway service, in Kubernetes, with an ingress to forward to the same that looks like below ->
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: beta-https
namespace: beta
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
tls:
- hosts:
- beta.xyz.com
secretName: beta-secret
rules:
- host: beta.xyz.com
http:
paths:
- path: /api/(.*)
backend:
serviceName: api-svc
servicePort: 8443
Now we have a new requirement, wherein a subset of the apis - /api/secure , must be IP restricted. Any ideas on how to achieve this?
I am assuming I can use nginx.ingress.kubernetes.io/whitelist-source-range, in a new config to foroward traffic to /api/secure, but how do I ensure the above config does not server /api/secure?
so for someone looking into doing something similar, I was able to get this working by using nginx.ingress.kubernetes.io/server-snippet to add a snippet and block the traffic
location ~ "^/api/secure/(.*)" {
deny all;
return 403;
}
From what I see you are trying to create two separate paths where /api/secure will be accesible only for specific ip addresses.
I have replicated your problem, made some tests and found a solution.
When creating two ingress objects like yours, which differ on path field e.g.
one has path: /api/(.*) and second has path: /api/secure
nginx will generate the following configuration (output is shortened):
server {
server_name beta.xyz.com ;
listen 80 ;
listen 443 ssl http2 ;
...
location ~* "^/api/secure" {
...
}
location ~* "^/api/(.*)" {
...
}
and in nginx documentation you can read:
To find location matching a given request, nginx first checks locations
defined using the prefix strings (prefix locations). Among them, the location with the longest matching prefix is selected and remembered. Then regular expressions
are checked, in the order of their appearance in the configuration file. The search of
regular expressions terminates on the first match, and the corresponding configuration is used.
meaning: NGINX always fulfills requests using the most specific match
Based on this information, creating two separate ingresses, just like you mentioned,
should solve your problem, because /api/secure will always be more specific path than /api/(.*).
Let me know if that helped.
in my case i have a multi subdomain host plateform and i am trying configs such as
nginx.ingress.kubernetes.io/server-snippet: |
location ~* "^/api/architect/(.*)" {
allow all;
}
location ~* "^/(.*)" {
deny all;
allow 149.74.110.92;
allow 85.138.230.206;
}
but doesn't seems working so far. I want to block the main url and only access a certain endpoint...

How to enable CORS with ingress without using nginx?

I'm trying to setup RESTful API application with Kubernetes. I have a barebones setup with a cluster, static IP address, app deployed with exposed service of type NodePort, and an ingress configured with a managed certificate for SSL. I need to enable CORS and I am not yet using nginx. Is it possible, or do I need to install nginx instead of the default gce class?
Here is my ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: artsdata-ingress
annotations:
kubernetes.io/ingress.global-static-ip-name: "artsdasta-static-ip"
networking.gke.io/managed-certificates: artsdata-certificate
ingress.kubernetes.io/enable-cors: "true"
spec:
backend:
serviceName: artsdata-kg
servicePort: 80
To check I am using curl as follows:
curl -H "Access-Control-Request-Method: GET" -H "Origin: http://localhost" --head http://db.artsdata.ca
I am expecting the response to include Access-Control-Allow-*
Currently CORS mechanism is not supported in GCP L7 load balancer, therefore ingress-gce ingress controller does contain appropriate annotation to accomplish this functionality, find here related Stack thread.
If you consider replacing native GCP Ingress class by Nginx Ingress Controller in order to enable Cross-origin requests then you might have to include at least two annotations in the origin Ingress resource definition:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/enable-cors: "true"
I've found a great guideline through GCP community tutorials that explains Nginx Ingress Controller implementation procedure in GKE.
There are also the other L7 proxy frameworks available on the market that can leverage CORS requests like Traefik, Skipper, etc.