Kubernetes minikube nginx Ingress with basic auth, cors and session returns 403

4/15/2020

I'm playing with Minikube to deploy a simple spring-boot application you can find here: https://github.com/shopKubernetesSpringboot

minikube v1.9.2 on Ubuntu 18.04
Kubernetes v1.17.0 on Docker 19.03.5 ...

I'm using cors, session, basic auth. It works outside the Minikube cluster (with the embedded spring-boot netty server), but I can't make it work with nginx-ingress.

It is basically two microservices products and cart. Product & cart list (GET) works. But I can't make cart add (POST) work, it always returns the following in the browser:

XHRPOST http://192.168.39.94/cart/add
[HTTP/1.1 403 Forbidden 37ms]
CSRF Token has been associated to this client

The back-end is receiving the X-XSRF-TOKEN:

[or-http-epoll-3] o.s.w.s.adapter.HttpWebHandlerAdapter : [e8b45b1f-2] HTTP POST "/cart/add", headers=[Host:"192.168.39.94", X-Request-ID:"dc741d2765b9d0c585b7f73d0e2bab95", X-Real-IP:"192.168.39.1", X-Forwarded-For:"192.168.39.1", X-Forwarded-Host:"192.168.39.94", X-Forwarded-Port:"80", X-Forwarded-Proto:"http", X-Scheme:"http", Content-Length:"120", User-Agent:"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0", Accept:"application/json, text/plain, /", Accept-Language:"en-GB,en;q=0.5", Accept-Encoding:"gzip, deflate", Content-Type:"application/json;charset=utf-8", Authorization:"Basic dXNlcjp1c2Vy", X-XSRF-TOKEN:"17e1b013-98b3-4064-8173-bf3af4ce8bc7", Origin:"http://localhost:3000", Referer:"http://localhost:3000/"]

[or-http-epoll-3] o.s.w.s.adapter.HttpWebHandlerAdapter : [e8b45b1f-2] Completed 403 FORBIDDEN, headers=[Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", Access-Control-Allow-Origin:"http://localhost:3000", Access-Control-Allow-Credentials:"true", Content-Type:"text/plain", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Content-Type-Options:"nosniff", X-Frame-Options:"DENY", X-XSS-Protection:"1 ; mode=block", Referrer-Policy:"no-referrer", content-length:"45"]

My ingress looks like that:

    apiVersion: networking.k8s.io/v1beta1
    kind: Ingress
    metadata:
      name: shop-ingress
      annotations:
        kubernetes.io/ingress.class: nginx
        nginx.ingress.kubernetes.io/ssl-redirect: "false"
        nginx.ingress.kubernetes.io/affinity: cookie
        nginx.ingress.kubernetes.io/session-cookie-name: session
        nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
        nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"
        nginx.ingress.kubernetes.io/enable-cors: "true"
        nginx.ingress.kubernetes.io/cors-allow-origin: "http://localhost:3000"
        nginx.ingress.kubernetes.io/cors-allow-headers: X-XSRF-TOKEN,Accept,Accept-Encoding,Accept-Language,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,Origin,Connection,Content-Length,Content-Type,Host,Referer,User-Agent
        nginx.ingress.kubernetes.io/cors-allow-credentials: "true"
        nginx.ingress.kubernetes.io/session-cookie-path: /cart
    spec:
      rules:
        - http:
            paths:
              - path: /product
                backend:
                  serviceName: product-svc
                  servicePort: 80
              - path: /cart
                backend:
                  serviceName: cart-svc
                  servicePort: 80

Cart service & pod

apiVersion: v1
kind: Service
metadata:
  name: cart-svc
  labels:
    app: shop
    name: cart
    tier: backend
spec:
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 120
  selector:
    app: shop
    name: cart
    tier: backend
  ports:
    - protocol: TCP
      port: 80
      targetPort: rest-api-port
---
apiVersion: v1
kind: Pod
metadata:
  name: cart-pod
  labels:
    app: shop
    name: cart
    tier: backend
spec:
  containers:
    - name: cart
      image: davidgfolch/shop-cart:latest
      ports:
        - containerPort: 8080
          name: rest-api-port
      env:
        - name: SPRING_PROFILES_ACTIVE
          value: "prod"

SpringSecurityConfig.java

@Configuration
@Slf4j
public class SecurityConfig {

    @Value("${com.dgf.shopCart.cors.origins}")
    private String corsOrigins;

    @Bean
    SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
        return http
                .csrf().csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()).and()
                .cors(c -> c.configurationSource(cors()))
                .authorizeExchange()
                .anyExchange().authenticated()
                .and()
                .httpBasic()
                .and()
                .build();
    }

    private UrlBasedCorsConfigurationSource cors() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin(corsOrigins);
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return source;
    }
}

Comparing logs with and without Kubernetes

With Kubernetes SESSION cookie is not arriving to the back-end (as shown in the logs):

HTTP POST "/cart/add", headers=[Host:"192.168.39.94", X-Request-ID:"aba957e6cc3c8803c1734a2724a75fbf", X-Real-IP:"192.168.39.1", X-Forwarded-For:"192.168.39.1", X-Forwarded-Host:"192.168.39.94", X-Forwarded-Port:"80", X-Forwarded-Proto:"http", X-Scheme:"http", Content-Length:"120", User-Agent:"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0", Accept:"application/json, text/plain, /", Accept-Language:"en-GB,en;q=0.5", Accept-Encoding:"gzip, deflate", Content-Type:"application/json;charset=utf-8", Authorization:"Basic dXNlcjp1c2Vy", X-XSRF-TOKEN:"17e1b013-98b3-4064-8173-bf3af4ce8bc7", Origin:"http://localhost:3000", Referer:"http://localhost:3000/"]

Without Kubernetes they do:

HTTP POST "/cart/add", headers=[Host:"localhost:8080", User-Agent:"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0", Accept:"application/json, text/plain, /", Accept-Language:"en-GB,en;q=0.5", Accept-Encoding:"gzip, deflate", Content-Type:"application/json;charset=utf-8", Authorization:"Basic dXNlcjp1c2Vy", X-XSRF-TOKEN:"17e1b013-98b3-4064-8173-bf3af4ce8bc7", Content-Length:"120", Origin:"http://localhost:3000", Connection:"keep-alive", Referer:"http://localhost:3000/", Cookie:"Idea-c3de9a37=a5f5270f-e0e1-4807-92e2-79c126b768fc; XSRF-TOKEN=17e1b013-98b3-4064-8173-bf3af4ce8bc7; SESSION=eddb22e9-15f7-4b28-a422-36faa8a7f285"]

Any advice? What I'm missing?

-- surfealokesea
csrf
kubernetes
nginx-ingress
session
spring-boot

1 Answer

4/17/2020

I finally found that sticky sessions don't work if not specified spec.host for nginx-ingress versions prior to v.0.27.0. See pull request.

So it should be updated nginx-ingress to version 0.27.0 to make the above configuration work or set spec.host and (i hope) enable DNS addon to make it work. Accesing via de host dns name:

    spec:
      host: myapp.example.com
      rules:
        - http:
            paths:
              - path: /product
                backend:
                  serviceName: product-svc
                  servicePort: 80
              - path: /cart
                backend:
                  serviceName: cart-svc
                  servicePort: 80

Here you can find some related links:

I will be fine-tunning this answer as soon as I get a working version.

  1. update minikube docker ingress controller to 0.27.0 (NOTE: not working yet):
#minikube docker-env
#eval $(minikube -p minikube docker-env)
#docker images | grep ingress
#docker pull quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.27.1
#minikube stop && minikube start
#eval $(minikube -p minikube docker-env)
kubectl delete -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.26.1/deploy/static/mandatory.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.27.1/deploy/static/mandatory.yaml
minikube stop && minikube start
-- surfealokesea
Source: StackOverflow