Supersedes Authentication and Identity Services

Overview

We need to revisit authentication in the context of our new use cases:

  • Should we support Oauth?
  • Do we need to run our own IDP?
  • TERRA-REF: Can we share authentication with Clowder?
  • iSchool Educational Workbench: TBD
  • Cyverse: Integration with CAS?

Use cases

TERRA-REF/Clowder

  • A researcher applies for a TERRA-REF/Clowder account and is approved.  The researcher attempts to launch a container via the Labs Workbench plugin or to access Labs Workbench directly.  Labs Workbench is configured to integrate with Clowder authentication. Since the user is an approved Clowder user, they are automatically provisioned with access in Labs Workbench.

Oauth2 

Cyverse/CAS

  • A research has a Cyverse account. The researcher attempts to access workbench.cyverse.org, which has a custom Cyverse authentication plugin. The user is automatically provisioned a Labs Workbench account.

Options

In ticket NDS-791, we've explored using the Kubernetes nginx ingress controller with oauth2_proxy.

Kubernetes Nginx Ingress Controller

The nginx ingress controller adds support for NGINX external auth via the following ingress annotations:

ingress.kubernetes.io/auth-signin
ingress.kubernetes.io/auth-url

 

These are not specific to Oauth, but can be used in conjunction with the Oauth2 proxy, as described below.

Ingress + Oauth2_Proxy

The following loadbalancer.yaml demonstrates how to incorporate oauth2_proxy into the Kubernetes nginx ingress controller using nginx-ingress-controller:0.9.0-beta.3.  This includes a pod running the oauth2_proxy and the ingress annotations pointing to the signin and auth URLs.  

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    ingress.kubernetes.io/auth-signin: https://www.{{ DOMAIN }}/oauth2/sign_in
    ingress.kubernetes.io/auth-url: https://www.{{ DOMAIN }}/oauth2/auth
  name: ndslabs-ingress
spec:
  tls:
  - hosts:
    - www.{{ DOMAIN }}
    secretName: ndslabs-tls-secret
  rules:
  - host: www.{{ DOMAIN }}
    http:
      paths:
      - path: /api
        backend:
          serviceName: ndslabs-apiserver
          servicePort: 30001
      - path: /
        backend:
          serviceName: ndslabs-webui
          servicePort: 80

---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: oauth2-proxy
spec:
  rules:
  - host: www.{{ DOMAIN }}
    http:
      paths:
      - backend:
          serviceName: oauth2-proxy
          servicePort: 4180
        path: /oauth2
  tls:
  - hosts:
    - www.{{ DOMAIN }}
    secretName: ndslabs-tls-secret
---
apiVersion: v1
kind: Service
metadata:
  name: default-http-backend
  labels:
    app: default-http-backend
spec:
  selector:
    app: default-http-backend
  ports:
    - port: 8080
      protocol: TCP
---
apiVersion: v1
kind: ReplicationController
metadata:
  name: default-http-backend
spec:
  replicas: 1
  selector:
    app: default-http-backend
  template:
    metadata:
      labels:
        app: default-http-backend
    spec:
      terminationGracePeriodSeconds: 60
      containers:
      - name: default-http-backend
        # Any image is permissable as long as:
        # 1. It serves a 404 page at /
        # 2. It serves 200 on a /healthz endpoint
        image: gcr.io/google_containers/defaultbackend:1.0
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 30
          timeoutSeconds: 5
        ports:
        - containerPort: 8080
        resources:
          limits:
            cpu: 10m
            memory: 20Mi
          requests:
            cpu: 10m
            memory: 20Mi
---
apiVersion: v1
data:
  server-name-hash-bucket-size: "512"
  ssl-protocols: "TLSv1.2"
  proxy-read-timeout: "300"
  proxy-send-timeout: "300"
kind: ConfigMap
metadata:
  name: nginx-ingress-conf
---
apiVersion: v1
kind: ReplicationController
metadata:
  name: nginx-ilb-rc
  labels:
    app: nginx-ilb
spec:
  replicas: 1
  selector:
    app: nginx-ingress
  template:
    metadata:
      labels:
        app: nginx-ingress
    spec:
      containers:
      - image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.3
        imagePullPolicy: Always
        name: nginx-ingress
        ports:
        - containerPort: 80
          hostPort: 80
        - containerPort: 443
          hostPort: 443
        env:
          - name: POD_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: POD_NAMESPACE
            valueFrom:
              fieldRef:
                fieldPath: metadata.namespace
        args:
        - /nginx-ingress-controller
        - --default-backend-service=default/default-http-backend
        - --healthz-port=9999
        - --configmap=$(POD_NAMESPACE)/nginx-ingress-conf
      hostNetwork: true


This method is generally effective for authentication, but we did encounter a problem with authorization.  The oauth2_proxy supports upstream services that can received proxied authentication information, such as the username, email address, etc from the oauth IDP.  However, this type of authorization support isn't provided by the ingress controller nginx configuration.

 

We were able to achieve this using two different methods, both demonstrated in https://github.com/craig-willis/oauth2_test

  1. Modify the oauth2_proxy AuthenticationOnly method to call p.Proxy(rw, req) and add an authorization service upstream.  This way, calls to /oauth2/auth will be proxied to the upstream service, which can handle the authorization check.
  2. Modify the nginx configuration to add an extra location that wraps the oauth2 targets (more below)

 

In the nginx/default.conf:

  location /autho {
     auth_request /oauth2/auth;
     proxy_pass http://127.0.0.1:4180;
  }
  location / {
     auth_request /autho;
     error_page 403 = /oauth2/sign_in;
     root   /usr/share/nginx/html;
     index  index.html index.htm;
  }

In this case, the auth_request "autho" has a sub-request that handles oauth2, then a simple proxy-pass to the upstream authorization service, which receives the oauth profile information and can check whether the user is authorized to access the requested resource.

 

And in the oauth2_proxy.cfg:

upstreams = [
     "http://127.0.0.1:8081/autho"
]

Ingress + Custom Auth

We can similarly use the auth_request directive to authenticate against any external authentication/authorization service. For example:

  location / {
     
     auth_request /cauth/auth;
     # redirect 401 to login form
     error_page 401 = /cauth/sign_in;

     root   /usr/share/nginx/html;
     index  index.html index.htm;
  }
  location /cauth/auth {
    internal;
    proxy_pass http://127.0.0.1:8081/cauth/auth;
  }

  location /cauth/ {
      proxy_pass http://127.0.0.1:8081/cauth/;
  }

In this example, the /cauth/auth request returns 200/401/403 depending on whether the user is logged in/authorized.  On 401, the user is redireted to the /cauth/sign_in form, which handles login.  Using this method, we can support external authentication and oauth2 using the new ingress annotations.

 

Here's an attempt with nginx ILB

cauth ingress:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  creationTimestamp: 2017-03-30T21:17:11Z
  generation: 1
  name: ndslabs-cauth
  namespace: default
  resourceVersion: "819165"
  selfLink: /apis/extensions/v1beta1/namespaces/default/ingresses/ndslabs-cauth
  uid: 3d124c9a-158e-11e7-aa47-fa163e1c2240
spec:
  rules:
  - host: www.cwdev.ndslabs.org
    http:
      paths:
      - backend:
          serviceName: cauth
          servicePort: 8081
        path: /cauth
  tls:
  - hosts:
    - www.cwdev.ndslabs.org
    secretName: ndslabs-tls-secret
status:
  loadBalancer:
    ingress:
    - ip: 127.0.0.1
 

 

Edited ingress for a vim application pointing to the central cauth

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    ingress.kubernetes.io/auth-signin: https://www.cwdev.ndslabs.org/cauth/sign_in
    ingress.kubernetes.io/auth-url: https://www.cwdev.ndslabs.org/cauth/auth
  creationTimestamp: 2017-03-30T21:06:20Z
  generation: 1
  name: so2j7h-vimpy-ingress
  namespace: cawillis
  resourceVersion: "820986"
  selfLink: /apis/extensions/v1beta1/namespaces/cawillis/ingresses/so2j7h-vimpy-ingress
  uid: b8f389e7-158c-11e7-aa47-fa163e1c2240
spec:
  rules:
  - host: so2j7h-vimpy.cwdev.ndslabs.org
    http:
      paths:
      - backend:
          serviceName: so2j7h-vimpy
          servicePort: 3000
        path: /
  tls:
  - hosts:
    - so2j7h-vimpy.cwdev.ndslabs.org
    secretName: cawillis-tls-secret
status:
  loadBalancer:
    ingress:
    - ip: 127.0.0.1

 

 

 

 

NGINX External Auth

This might be an option for supporting external authentication via plugin, such as Clowder/Cyverse cases.

Keycloak

  • Looks like a better and Docker-based replacement for WSO2
  • Allows us to run our own IDP
  • No labels