5
votes

I have deployed few services in kubernetes and using NGINX ingress to access outside.(Using EC2 instance for all cluster setup). Able to access service through host tied with ingress. Now instead of accessing the svc directly I am trying to add authentication and before accessing the service. And redirecting to login page , user enters credentials and should redirect to the asked page. The following code snipet I I tried so far. Please guide to find solution.

my-ingress.yml

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: my-ingress
  namespace: mynamespace  
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/affinity: cookie
    nginx.ingress.kubernetes.io/session-cookie-name: JSESSIONID
    nginx.ingress.kubernetes.io/ssl-passthrough: "false"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    kubernetes.io/ingress.class: "nginx"
    kubernetes.io/ingress.allow-http: "false"
    nginx.ingress.kubernetes.io/auth-signin: "https://auth.mysite.domain/api/auth/login"  #will show login page
    nginx.ingress.kubernetes.io/auth-url: "https://auth.mysite.domain/api/auth/token/validate"
    nginx.ingress.kubernetes.io/auth-response-headers: "authorization"    
    
spec:
  tls:
  - hosts: 
    - mysite.domain
    #secretName: ${TLS_TOKEN_NAME}
  rules:
  - host: a.mysite.domain
    http:
      paths:
        - path: /
          backend:
            serviceName: myservice1
            servicePort: 9090 
 

so first it will call "/token/validate" and will get unauthorized then got to "auth/login" and login page will show after entering credentials going to "/token/validate" and again login page. Actually should redirect to the called page.

How to achieve this?[If after successful auth if we can add header in called ling I think can solve but not sure how]

backend: Java Spring

@RequestMapping("login")  
public String login() {  
    return "login.html";  
} 

login.html

    <form action="validate-user" method="post" enctype="application/x-www-form-urlencoded">  
        <label for="username">Username</label>  
        <input type="text" id="username" value="admin" name="username" autofocus="autofocus" />  <br>
        <label for="password">Password</label>  
        <input type="password" id="password" value="password" name="password" />  <br>
        
        <input id="submit" type="submit" value="Log in" />  
    </form> 

backend: Java Spring

@PostMapping("validate-user")
@ResponseBody
public ResponseEntity<?> validateUser(HttpServletRequest request, HttpServletResponse response) throws Exception {
                ...
                
    HttpStatus httpStatus=HttpStatus.FOUND;
    //calling authentication api and validating
        
    //else
    httpStatus=HttpStatus.UNAUTHORIZED;
    HttpHeaders responseHeaders= new HttpHeaders();
    responseHeaders.set("Authoriztion", token);
                
    //responseHeaders.setLocation(new URI("https://a.mysite.domain")); ALSO TRIED BUT NOT WORKED
    return new ResponseEntity<>(responseHeaders,httpStatus);        
        
        }

UPDATE1: I am using my own custom auth api, if I am hitting the url with custom header "Authorization":"bearer token" from postman then response is ok, but from from browser not possible, So from upstream svc only(after successfull login) the header should include in redirect page that how can we do?
ANY ANNOTATION AM I MISSING?

UPDATE2: While redirecting after successful auth I am passing token as query string like responseHeaders.setLocation(new URI("https://a.mysite.domain/?access_token="+token) and after redirecting its going to validate. After successful validation going to downstream svc[expected]. But when that svc is routing say a.mysite.domain/route1 then query string is gone and auth svc not able to get token hence 401 again. It should be like a.mysite.domain/route1/?access_token=token. Any way is there to do that? If every route will have same query string then will work.[This is my PLAN-B...but still passwing token is header is my priority]

UPDATE3: I tried with annotations like:

nginx.ingress.kubernetes.io/auth-signin: 'https://auth.example.com/api/auth-service-ui/login'
nginx.ingress.kubernetes.io/auth-response-headers: 'UserID, Authorization, authorization'
nginx.ingress.kubernetes.io/auth-snippet: |
      auth_request_set $token $upstream_http_authorization;
      proxy_set_header Foo-Header1 $token; //not showing as request header AND this value only need LOOKS $token val is missed
      proxy_set_header Foo-Header headerfoo1; //showing as request header OK
      more_set_input_headers  'Authorization: $token';//not showing as request header AND this value only need LOOKS $token val is missed

nginx.ingress.kubernetes.io/configuration-snippet: |
      auth_request_set $token1 $upstream_http_authorization;
      add_header  authorization2 QQQAAQ1; //showing as response header OK no use
      add_header  authorization $token; //showing as response header OK how to send as request header on next call
      more_set_input_headers  'Authorization11: uuu1';//showing as request header in next call
      more_set_input_headers  'Authorization: $token1';//not showing as request header and need this val ONLY

**What annotation I missed?

UPDATE4 PLAN-C: Now trying to store jwt token in cookies.

 nginx.ingress.kubernetes.io/configuration-snippet: |
      auth_request_set $token5 $upstream_http_authorization;    
      add_header Set-Cookie "JWT_TOKEN=$token5";

In every request the same cookie is set but in browser its storing everytime. ie multiple cookies of same. How to set only once?

3
What kind of external-auth are you trying to use, basic authentication or OAuth2? Also, please provide your nginx-ingress version.Mr.KoopaKiller
@KoopaKiller image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0 /ingress-nginx/nginx-0.30.0 And using jwt token validation(custom auth api)Joe
Is there anyone who knows this concept and can share some knowledge on it?Joe
I will try reproduce the issue. It would be great if you could share the image you are using or some example.Mr.KoopaKiller
All resources are in private network so wont be available to provide. To check I just setup a cluster and deployed two svc, one auth-svc(simple and custom) another is the svc I want to access. Installed kubedash from github and nginx-ingress nginx-0.30.0. So please try to reproduce by yourself. I tried but while redirecting to page not able to set auth header. If that is done then will work fine.Joe

3 Answers

0
votes

Integrate Oauth2-proxy with nginx ingress controller to enable custom authentication to the services running behind the ingress.

Follow the link --> https://github.com/oauth2-proxy/oauth2-proxy

0
votes

Towards your PLAN-B:

passing token in header is my priority.

I don't see any issue in doing it with just a use of following annotations:

nginx.ingress.kubernetes.io/auth-url: "https://auth.mysite.domain/api/auth/token/validate"
nginx.ingress.kubernetes.io/auth-response-headers: "authorization 

Following up on the official external-auth-headers example:

  1. Modify their external-auth-service's source code, so that it introduces on successful auth. response a desired header ("Authorization": "Bearer <token>"):

       w.Header().Add("UserRole", "admin")
            w.Header().Add("Other", "not used")
            w.Header().Add("Authorization", "Bearer foobar" ) <--- here
            fmt.Fprint(w, "ok")
    
  2. Rebuild docker image & update Pod spec in Deployment to apply your custom image

  3. Ensure your Ingress definition looks similar:

    apiVersion: extensions/v1beta1
    kind: Ingress
    metadata:
      annotations:
        nginx.ingress.kubernetes.io/auth-response-headers: UserID, authorization
        nginx.ingress.kubernetes.io/auth-url: http://demo-auth- 
    service.default.svc.cluster.local?code=200
    
  4. Inspect if auth-service custom headers are injected to Ingress call to its backend (I'm using cluster deployed version of httpbin.org/headers)

  5. Content of request seen from client and Ingress's backend perspectives:

Client request:

     > GET /headers HTTP/1.1
     > Host: custom.example.com
     > User-Agent: curl/7.58.0
     > Accept: */*
     > User: centrino
     > 
     < HTTP/1.1 200 OK
     < Server: nginx/1.17.8
     < Date: Tue, 14 Jul 2020 11:55:43 GMT
     < Content-Type: application/json
     < Content-Length: 327
     < Connection: keep-alive
     < Vary: Accept-Encoding
     < Access-Control-Allow-Origin: *
     < Access-Control-Allow-Credentials: true
     < 
   

Client request seen at backend enriched with ext-auth-svc header:

     {
      "headers": {
        "Accept": "*/*", 
        "Authorization": "Bearer foobar", <---is this what you are looking for ?
        "Host": "custom.example.com", 
        "User": "centrino", 
        "User-Agent": "curl/7.58.0", 
        "Userid": "8674665223082153551", 
        "X-Forwarded-Host": "custom.example.com", 
        "X-Scheme": "http", 
        "X-Using-Nginx-Controller": "true"
      }
    } 
 

 
0
votes

the following worked for me

put in your values yaml:

nginx.ingress.kubernetes.io/auth-url: "url service here"

then for this url you must implement a GET service that returs 200 if authorization was success or 401 in other case.

I implemented in flask, with Basic Authorization, but you can use whatever you want

 def auth():
  request_path = request.headers.get("X-Auth-Request-Redirect")
  authorization_header = request.headers.get('Authorization')
          
  if ServiceImp.auth(authorization_header,request_path):
   return Response(
                response="authorized",
                status=200, 
                mimetype="application/json"
   )
  else:
   resp = Response()
   resp.headers['WWW-Authenticate'] = 'Basic'
   return resp, 401