Envoy: OAuth HTTP filter

Created on 10 Oct 2019  路  22Comments  路  Source: envoyproxy/envoy

Title: OAuth HTTP filter

Description:
We have an internal HTTP filter that implements OAuth 2.0 (RFC 6749). I wanted to gauge community interest in upstreaming this. We've been running it for all of our internal sites for about a year now. cc @bplotnick who I think had interest.

design proposal no stalebot

Most helpful comment

Also a big +1 for this. I鈥檝e utilized the separate proxy flow before and IMO it鈥檚 more error prone then if the client-side logic was handled by an envoy filter.

All 22 comments

We were mostly using the Identity Token portion of OpenID Connect. We just were relying on the IdP to give us some signed token and it just happened to be a JWT. We ignored everything else about OIDC and OAuth 2.0 (standard OAuth 2.0 does not include Identity Tokens IIRC). From everyone we talked to, it seemed like it was a pretty unique use-case, though I guess we'll see when we present at KubeCon.

I assume your filter implements the "Client" part of a given OAuth 2.0 flow. Like redirecting the user and then exchanging the authorization code for a token when the user is redirected back: https://developer.okta.com/blog/2018/04/10/oauth-authorization-code-grant-type#the-authorization-code-flow

That seems pretty useful, though i'd never personally want to implement OAuth 馃槃

CC: @kaisen and @danielpops

@bplotnick gotcha, yeah you're correct that our filter implements the client-side workflow as described in that link.

This would be quite amazing as we are also looking into implementing such a filter. We currently have to run a separate gatekeeper proxy that handles the auth flow and having this feature would allow us to secure webapps from within envoy rather than within the webapp itself or by using a separate proxy.

In general I would love to see this happen as I think this is a pretty big gap in the ecosystem right now. With that said, is there plan to OSS the backend (token storage, etc.)? Is there a design doc for what this would cover?

@Barborica-Alexandru great to hear! We were also in the "separate proxy" situation, using OpenResty with a Lua script to provide OAuth for webapps, so it was great to consolidate it to Envoy.

@mattklein123 the way the filter works currently is that it makes an HTTP request to a CDS-defined cluster for the actual authentication so it's bring-your-own-authentication-service so that it can plug into whatever backend (for us it's LDAP). The workflow we have is

  1. get your client ID & secret from your auth service and configure it in the filer (we have an integration with our internal secret storage, Knox, which reminds of a separate conversation I wanted to have [1]).
  2. When the filter gets requests it validates the authenticity of the standard OAuth cookies or redirects the user to the auth service to start an OAuth flow. The filter knows how to handle the callbacks and set cookies, or reject if credentials are invalid.

I'll put together a design document.

[1] We didn't want to write the secret in plaintext for obvious reasons. Any thoughts on expanding SDS to cover _general_ secrets so things like access tokens could be fed from the control plane? Happy to open another issue if you think it's worth a discussion. I'm not finding an existing issue of similar topic.

Definitely ... we are only using NGINX Ingress Controller on our UIs because they are fronted by SSO (using Dex and oauth2 proxy) if Envoy made this simple then we could have a truly Envoy'd platform.

+1 for this. We too are in the "separate proxy" scenario and it is requiring us to keep around an NGINX ingress controller we'd like to remove.

Also a big +1 for this. I鈥檝e utilized the separate proxy flow before and IMO it鈥檚 more error prone then if the client-side logic was handled by an envoy filter.

In the istio community we've been building an OIDC integration much like what's being suggested above. Previously we built an Envoy filter that contained all logic to mediate the retrieval of an access or identity token and place the token in a cookie (see https://github.com/thales-e-security/envoy/tree/newoidc/source/extensions/filters/http/oidc) but then moved towards a separate process that implements the Envoy external auth service API for providing the same functionality (see https://github.com/istio-ecosystem/authservice).

Our design docs can be found at:
https://docs.google.com/document/d/1yAI6kNrl285TlzX7eHEdejIY7IAmw3_tqSsQ62w8UWs/edit?usp=sharing

https://docs.google.com/document/d/1mGpUsRgmA9wPB73trfTiB9YUuwYh-31iulYg9USxe0Y/edit?usp=sharing

If there's anything we can share or collaborate on that would be great.

@nickrmc83 thanks for all the resources! Reading through the design & code.

Circling back here, I have a doc nearly complete - hoping to share tomorrow or Monday, just needs some cleanup/diagrams.

I've spent a significant time reviewing the OAuth and OIDC specs and after much consideration, the doc will only propose a design for implementing the Authorization Code OAuth workflow for now, with open discussion points for OIDC and improving the jwt_authn filter to support Discovery now that we have a dynamic forward proxy. The reason this was taking a lot of time (besides other distractions 馃槵) was trying to figure out how to fit OIDC, but I'd like to start with just OAuth for 2 reasons:
1) It provides a reasonable 1st milestone for an MVP that can be extended to support OIDC at a later time since OIDC is roughly a superset of features on top of OAuth.
2) OIDC has a bit more state management for validating the ID token. The Authorization Code OAuth workflow can be completely implemented within an Envoy filter by leveraging secure cookies for handling the state parameter. But OIDC additionally has an optional nonce, (as called out by the Istio folks in their initial design document, and I'm guessing was a motivating factor for moving to ext_authz + authservice, granted nonce is an optional feature).
3) it provides a nice building block that could be combined with jwt_authn or ext_authz.

An Authorization Code OAuth filter is also what we've built internally so far at Pinterest and matches the initial motivation for opening this issue.

So TL;DR initial MVP would validate or obtain an access token from an IdP via the Authorization Code OAuth 2.0 workflow, which is stored in a secure cookie scheme similar to oauth2-proxy or Istio authn's __Secure-istio_session cookie, and can log out by clearing the cookie. The information can also be proxied through metadata or headers for subsequent filters to work with, and JWTs can be validated with jwt_authn.

Nice, also interesting in following along with this. We recently had to build a separate service that handles the authorization code flow, and speaks to envoy with the ext_authz filter. Would be cool if there was a way to do this all in envoy however.

We're also in a situation here at spotify where we'd need a filter like this for internal services behind oauth2 / okta

@derekargueta could you post link to doc/RFC here?

dev branch here: https://github.com/derekargueta/envoy/tree/oauth-filter
To keep the scope limited, I plan on initially supporting the authorization code grant for browser-based workflows, storing an HMAC'd token in browser cookies. Subsequent iterations can add support for PKCE, JWT, etc.

one open question I have for now is the handling of state - randomly generated nonce in the 302 redirects that, when redirected back, should match the initial nonce. The caveat is that when redirected back, it is not guaranteed (if anything, highly unlikely) to land back on the same Envoy that generated the nonce. One possibility is to store it in a browser cookie (httpOnly, secure, etc.) so that when the user-agent is redirected back to Envoy, it carries that cookie with it which can be validated against the query parameter.

state is optional so we could technically punt on it for now, but it is highly recommended to protect against CSRF

@derekargueta I don't see any objection to storing the state in a secure browser cookie. In fact, we do the same in the OIDC ext_authz filter that we built within our organization.

Do you also plan to work on refreshing the access token as it (nears?) expiry, using the refresh token? The refresh token would be present if the offline_access scope is requested.

Looking forward to have this handled by Envoy directly!

Hi everyone,

In case it's helpful, I wanted to add that there is a related project in the official Istio ecosystem already which can be used in either pod sidecars or at the ingress gateway to provide everything you need for your end users to acquire tokens from OIDC-compliant identity providers using the authcode flow. It can be easily combined with the existing JWT checking capabilities of Istio to require/validate the tokens that it acquires.

The service:

  1. Automatically redirects any unauthenticated end user to the configured identity provider for login
  2. After user login, receives and authcode via a redirect from the identity provider and makes a bank-end to back-end call back to the IDP to exchange the authcode for tokens. It stores the tokens on behalf of the end user.
  3. Transmits either the ID token or the access token (or both) to the downstream application via http headers, without allowing the end user's browser to see those tokens (for security)
  4. Automatically refreshes those tokens using the refresh token whenever possible

The project is open source and is a work in progress, under active daily development. Suggestions, issues, and PRs via github are welcome. https://github.com/istio-ecosystem/authservice

You can also find the development team on https://istio.slack.com in the #oidc-proposal channel.

Specifically related to @derekargueta's question about storing the state and nonce in a browser cookie: yes, this was our approach too, previously. We additionally encrypted the contents of the cookie using a secret which was only known by the backend to prevent the user from seeing or tampering with the values. However, this week we are refactoring that approach and moving the values to backend session storage, so we will not store them in cookies anymore.

@derekargueta I don't see any objection to storing the state in a secure browser cookie. In fact, we do the same in the OIDC ext_authz filter that we built within our organization.

Do you also plan to work on refreshing the access token as it (nears?) expiry, using the refresh token? The refresh token would be present if the offline_access scope is requested.

Looking forward to have this handled by Envoy directly!

Yes refresh tokens will be supported, I鈥檒l update the doc

@derekargueta I don't see any objection to storing the state in a secure browser cookie. In fact, we do the same in the OIDC ext_authz filter that we built within our organization.
Do you also plan to work on refreshing the access token as it (nears?) expiry, using the refresh token? The refresh token would be present if the offline_access scope is requested.
Looking forward to have this handled by Envoy directly!

Yes refresh tokens will be supported, I鈥檒l update the doc

will your branch be merged into master or any formal release?

This is merged. Let's open new issues for follow ups.

For anyone else stumbling across this issue (like me) the documentation of the OAuth2 filter is located here: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/oauth2/v3alpha/oauth.proto.html

Was this page helpful?
0 / 5 - 0 ratings