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
- Use case outlined in Authentication and Identity Services
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
- 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.
- 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
- Goto https://www.cwdev.ndslabs.org/
- Prompted for login (test /200)
NGINX External Auth
This might be an option for supporting external authentication via plugin, such as Clowder/Cyverse cases.
- NGINX allows you to pass authentication to 3rd party via "subrequest"
- https://www.nginx.com/resources/admin-guide/restricting-access-auth-request/
- https://www.nginx.com/blog/nginx-plus-authenticate-users/
https://www.stavros.io/posts/writing-an-nginx-authentication-module-in-lua/
LDAP example: https://github.com/nginxinc/nginx-ldap-auth
Keycloak
- Looks like a better and Docker-based replacement for WSO2
- Allows us to run our own IDP