Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Difference with pusher oauth proxy and how to validate a request directly from an application #74

Closed
butsjoh opened this issue Feb 8, 2019 · 29 comments · Fixed by #104
Labels

Comments

@butsjoh
Copy link

butsjoh commented Feb 8, 2019

Hi,

We are experimenting with vouch to secure some of our internal services and are wondering the reasons behind your approach to build this library. After some searching we also found https://github.com/pusher/oauth2_proxy which is quite similar but seems to be more mature. Could you explain more the rationale around vouch proxy and maybe the differences with other solutions. We are just curious :)

@bnfinet
Copy link
Member

bnfinet commented Feb 8, 2019

When I started the code base more than two years ago https://github.com/bitly/oauth2_proxy/ was fallow and difficult to install. I notice that Bitly has just archived the code base on Jan 23rd. I'm glad to see pusher commit to taking over the code base! But I haven't evaluated that solution in over two years.

The small Vouch Proxy roadmap at the moment is...

  • easy install, ease of use
  • testing infra
  • keep it tight, battle feature creep
  • maintain less than 1ms for /validate

I expect there to be a UI for visibility into the activity at some point but that will be a separate tool, probably little more than a log receiver initially.

You mention "some concerns", what are your concerns?

@butsjoh
Copy link
Author

butsjoh commented Feb 8, 2019

Sorry i forgot to remove the "some concerns" from the title but i could share them anyways :) Although i am not sure this i a real concern for vouch but your opinion is welcome.

We know that it relies on nginx auth request module to work but are wondering what would happen to the service that should be protected if nginx would be configured badly (hack, mistake, whatever reason) and the request module part would be removed. How would that service verify that request that come in are still authenticated. Again this might not be a concern for vouch perse but it raise the question where authentication should be configured. It would be nice that the protected service would have a way to validate as well that request are authenticated. Since each request goes through the validate phase some kinda of token could be set that can be verified on the service as well as a second verification? Not sure if this is possible or makes sense at all but your view on it would be great. Keep up the good work at least we see where you want to take this project.

@bnfinet
Copy link
Member

bnfinet commented Feb 8, 2019

I'd expect native SSL keys to become a config option for vouch proxy at some point and then adding a client cert to nginx with proxy_ssl_certificate would be an option. That's slightly orthogonal to your ask but if vouch is going to have any part of verifying clients that will need to be in place first. At that point perhaps client registration or offering an additional client token becomes something to consider
https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ssl_certificate

You could approach the problem from topology with Vouch Proxy in front (not today, it doesn't do that), but I'm not sure if it makes sense for Vouch Proxy to become the https termination point with all traffic going to vouch proxy first, then to nginx. That's not on the roadmap today, though with SSL keys going in it might not be too much of a stretch to support a public (letsencrypt) wildcard cert for each configured domain (*.private.yourdomain.com)

You could fake that setup right now with something like

+-----------+     +-------------+ +-------+     +-------------+
| NginxSSL  |     | VouchProxy  | | Nginx |     | PrivateSite |
+-----------+     +-------------+ +-------+     +-------------+
      |                  |            |                |
      | /validate        |            |                |
      |----------------->|            |                |
      |                  |            |                |
      | (authorized traffic)          |                |
      |------------------------------>|                |
      |                  |            |                |
      |                  |            | (proxy)        |
      |                  |            |--------------->|
      |                  |            |                |

Of course, if your nginx's configuration is getting changed/hacked then pandora's box is already open. I'd hope your intrusion detection system is in a position to notice and alert. Your traffic could be intercepted and sent to anywhere which is visible from that node. With Direct Service Return you may not even be able to see the response! But that feels like something would be wrong with your deployment or configuration management.
https://www.nginx.com/blog/ip-transparency-direct-server-return-nginx-plus-transparent-proxy/

@aaronpk
Copy link
Collaborator

aaronpk commented Feb 8, 2019

@butsjoh I think I understand your concern. Vouch/Nginx can be configured to pass thru the user ID in an http header to your backend. At that point your backend can allow the request only if it's set. So if for some reason your Nginx gets misconfigured and removes Vouch, the header will no longer be set and your backend can recognize its missing and not serve the page. Does that make sense?

@butsjoh
Copy link
Author

butsjoh commented Feb 8, 2019

@aaronpk that is exactly what i meant :)

@butsjoh
Copy link
Author

butsjoh commented Feb 8, 2019

@aaronpk My point was the moment my backend only check for "a header" it doomed to fail if the nginx gets misconfigured. I can easily fake a header with curl so all of a sudden the backend is open ... That's why i would like to have something to prevent that and hence the question :)

@aaronpk
Copy link
Collaborator

aaronpk commented Feb 8, 2019

IMO as soon as you add another check of some sort of token in your backend service, you are probably better off not using Vouch and doing this entirely differently. The main reason for using Vouch is it lets you transparently add authentication in front of services that may not even have any concept of authentication themselves.

@butsjoh
Copy link
Author

butsjoh commented Feb 8, 2019

I understand and hence also my initial hesitation to ask for it :)

But i can imagine in some situations that you might not be in control of the nginx layer where your service is hosted on and then i am forced to move the authentication to the app anyways. I know the use case is really specific and then vouch might not be the correct tool for this. But it would be interesting though if you would consider it.

@bnfinet
Copy link
Member

bnfinet commented Feb 8, 2019

@aaronpk thanks for clarifying, I misunderstood the concern

@butsjoh Your app could call the /validate enpoint of Vouch directly and confirm the JWT Cookie it receives. Would that work for your use case?

@butsjoh
Copy link
Author

butsjoh commented Feb 8, 2019

@bnfinet Can you give some pointers on how that would work? I was also thinking in the same way but am not sure what the flows (requests) for that would be.

@bnfinet
Copy link
Member

bnfinet commented Feb 8, 2019

After the cookie (default name VouchCookie) is set in the /auth request it is then passed in the http headers of every request. Your app would take all the headers of the request (or even the whole request) and send them as is to /validate. If you get a response with http status code 200, then you're good, if you get 401 then it didn't validate.

I've done this but it's not the use case I test for most of the time.

I'd be very excited to hear if you got such a setup working for your app :)

@butsjoh
Copy link
Author

butsjoh commented Feb 8, 2019

Ok i will look into this and keep you posted. Will be for next week though.

@butsjoh
Copy link
Author

butsjoh commented Feb 8, 2019

Just another question. In the config you can specify a secret token for the JWT. As far as i understand how this works is the value in the VouchCookie is the encrypted JWT token. Would there not be a way for the backend to verify or decrypt this token if the secret would be shared with the backend service? Or am i not understanding something :)

@bnfinet
Copy link
Member

bnfinet commented Feb 8, 2019

That's what I understand too, though I've never done that with a secret and JWT from Vouch in an app.

I mention sending all of the headers to /validate since this is the current implied SLA between nginx and Vouch and is what I can say is forward compatible. There are already a few X-Vouch-* headers that get passed at this point and there may be other aspects of validation that occur against the headers in the future.

Best of luck!

@bnfinet bnfinet changed the title Difference with pusher oauth proxy and some concerns Difference with pusher oauth proxy and how to validate a request directly from an application Feb 8, 2019
@butsjoh
Copy link
Author

butsjoh commented Feb 8, 2019

Gonna try the following. I will try to decrypt the value of VouchCookie using https://jwt.io. If i fill the secret from the config then i should be able to read it. According to

token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), claims)
it seems to be possible.

@butsjoh
Copy link
Author

butsjoh commented Feb 9, 2019

Ok, i am already able to decompress and unencrypt the cookie value to the JWT token for the backend. Now my goal is to replace the entire nginx auth request module and try to move that part to the backend. Could you confirm if the following steps would work.

  1. A request hits the backend. It will try to read the VouchCookie and decrypt it to know if it is already expired or not. In case its expired or not set i will forward to https://vouch.oursites.com/login?url=https://mybackend (kinda the same behavior that nginx auth request does)

  2. I will go through the auth steps and login to google etc. which will result setting the VouchCookie again with new information and i will forward back to my backend with the new VouchCookie.

Does the above makes sense? Effectively i am moving the validation outside of nginx (remove nginx auth request) to the backend. In this situation i am only using vouch for the auth flow and setting the correct cookies but not actually for validation.

Ofcourse i need to share the JWT key and issuer information with the backend.

Do you see any security issues with this? As far as i know cookies are pretty secure if https is used. What i can teach the backend as well is to periodically make a request to the vouch validate endpoint with the Cookie and see if i get a 200 back.

@butsjoh
Copy link
Author

butsjoh commented Feb 9, 2019

And just to be clear i do not expect vouch to support this at all :) It is just interesting to figure out if parts of it can be use in different use cases.

@bnfinet
Copy link
Member

bnfinet commented Feb 9, 2019 via email

@simongottschlag
Copy link
Contributor

Hi!

Read the thread and thought I could add some additional information. If your backend supports OpenID Connect (or the service mesh does, like Istio) you could pass a JWT as a header (Authorization: Bearer ). Lots of services work like this today, with Kubernetes Dashboard as an example.

There is an open enhancement request for this (#43) and I’ve been able to do it in my environment using a few tweaks.

You shouldn’t try to engineer a wheel when a great solution like OIDC already exists. Instead of the third party application validating through vouch-proxy /validate after the traffic is passed to it using nginx - let vouch-proxy be the part that adds this JWT to the request between nginx and the third party app.

This means that your app is only expecting a token, which may be provided manually or through vouch-proxy (which makes it 100 times easier, since no one wants to extract an is_token manually each time they access a service).

Just my 5 cents 👌

@butsjoh
Copy link
Author

butsjoh commented Feb 10, 2019

@simongottschlag Sorry but i am not getting your point. Probably it is my lack of understanding how all of this works. You say that my backend service should only expect a token coming in through a header? How can my backend validate then that this is a valid token/request? Is that what the openid connect support does? No idea how that works. Care to explain how that makes it more secure because faking headers with a token is not that hard ...

My use case is that i want to avoid using the nginx auth request module to secure my backend service. The reason for that is i might have no control of the nginx instance that is configured for my app. Also if misconfiguration would happen the service might be open to the world. You could argue that you kinda need to move the authentication part to the app and that is 100% reasonable argument.

Not using the nginx auth request module (the /validate part) and sharing the jwt secret with my backend service allows me to decrypt the cookievalue that vouch sets. The value of this cookie is the JWT token which i can decode using the secret. That way i am sure it is coming from the source i trust. Faking cookies is harder then a header an hence i my understanding (correct me if i am wrong) more secure.

@simongottschlag
Copy link
Contributor

Hi!

May have been my incoherence late at night (and maybe a few beers). What you can assume is that the token generated by an OpenID Provider is a cryptographically signed object with information about the request (at least the id_token is, access_token is a little different). By using OIDC, your backend knows where to find the public key that is used to verify the signature on the token. As long as the token is signed by the correct provider and isn’t expired, it’s valid. You can learn more here: https://connect2id.com/learn/openid-connect

But with the assumption that OIDC is secure, and you have implemented a way to grab the public key (this is done using a discovery process) you can also assume that if you have a valid token the user should be able to access the backend.

What is great with vouch is that it provides a pre-authentication to your backend. This means that if you have a vulnerable system (for any reason), it will not be possible to exploit it without first being authenticated. This will protect the backend from a lot of threats - but not all.

Another thing vouch does for you is that if your backend only expects an authorization header but no mechanism for actually helping the end user to get the token, vouch will do that for you.

Keep in mind that the functionality to forward the id_token using a header is still pre-alpha and written by an illiterate (me) - but with the help for the great people of this project it should be possible to use it with confidence quite soon.

I do recommend you reading up on OIDC and how it is used for authentication and authorization. Everyone uses it today in one way or another.

Good luck! 👍

@simongottschlag
Copy link
Contributor

If you want a great presentation about oauth2/OIDC - watch this: https://youtu.be/GyCL8AJUhww

@butsjoh
Copy link
Author

butsjoh commented Feb 11, 2019

Ok. Thnx for the info. Since you are looking into this a lot longer then me what is the state of vouch proxy to support this? I guess #43 needs to be implemented first?

If i compare this how pusher oauth2_proxy does this (https://github.com/pusher/oauth2_proxy/blob/master/README.md#openid-connect-provider), they seem to use another tool called dex (https://github.com/dexidp/dex) which takes all the heavy lifting to support it for most of the providers. How does this compare with how vouch is/wants todo it?

Is my understanding also correct that my backend would need an openid connect client which is able to fetch the public keys to verify the id token? Who is then providing these keys? Would vouch have an url for that?

@simongottschlag
Copy link
Contributor

Hi,

I can't speak for pusher oauth2_proxy or Dex, but I tried Dex together with ADFS since ADFS is weird is doing everything a little bit different it wasn't possible to extract the information needed to get Dex/ADFS/ to work together. In my case, vouch-proxy fulfilled this. When it comes to ETAs, you have to wait for a reply for the maintainers.

The OpenID Provider has the public keys that should be fetched: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig

Right now, this should be the OP used by vouch. But in the future, maybe vouch could expose this as well (#41).

@butsjoh
Copy link
Author

butsjoh commented Feb 11, 2019

Ok thnx. We like to understand the full picture before we take any action that might lock us in to solution A or B. I am starting to see the differences now between vouch and pusher oauth2_proxy.

I am assuming that your PR #71 will be needed in order to support more of the oidc abilities, if the backend service needs to get to more information (it needs the access token). The access token will most likely also be needed to do a logout, in b6fb4b9#diff-04c6e90faac2675aa89e2176d2eec7d8R146 you can provide a url but for example in the google situation you would need to pass the access token. Correct?

@bnfinet
Copy link
Member

bnfinet commented Feb 19, 2019

@butsjoh I believe you do get logged out by the IdP (Google in this case) even if you don't provide the access token, but it will result in an error and it won't honor the 302 redirect request of a post_logout_redirect_uri

https://openid.net/specs/openid-connect-session-1_0.html#RPLogout

@bnfinet
Copy link
Member

bnfinet commented Feb 25, 2019

@butsjoh are you still actively pursuing this integration? How's that going?

@butsjoh
Copy link
Author

butsjoh commented Feb 25, 2019

I have code that i can use on my backend to validate the incoming cookie set by vouch. See the following ruby code below (sorry not know about go that much) but it essentially is a reverse engineering of what your code does to encrypt and compress the cookie.

require 'base64'
require 'jwt'
require 'zlib'
require 'stringio'

# The value as specified in the vouch config
secret = "xxxx"
 # The value of issuer in the vouch config
issuer = "Vouch"

# Set this to 9999999 if expired to still get output
exp_leeway = 0

# The value of the vouch cookie in the brower
cookie_value = "*********"

def urlsafe_decode64(str)
  str = str.tr("-_", "+/")
  str = str.ljust((str.length + 3) & ~3, '=')
  Base64.strict_decode64(str)
end

def verify_vouch_cookie(cookie, secret, issuer, exp_leeway)
  compressed_token = urlsafe_decode64(cookie)
  sio = StringIO.new(compressed_token)
  gz = Zlib::GzipReader.new(sio)
  encoded_token = gz.read.to_s

  puts "JWT token:"
  puts encoded_token

  puts "Base 64 decoded:"
  puts Base64.decode64(encoded_token)

  begin
    decoded = JWT.decode(encoded_token, secret, true, { algorithm: 'HS256', iss: issuer,  exp_leeway: exp_leeway })

    [200, { 'Content-Type' => 'text/plain' }, ["Valid token: #{decoded.inspect}"]]
  rescue JWT::ExpiredSignature
    [403, { 'Content-Type' => 'text/plain' }, ['The token has expired.']]
  rescue JWT::InvalidIssuerError
    [403, { 'Content-Type' => 'text/plain' }, ['The token does not have a valid issuer.']]
  rescue JWT::InvalidIatError
    [403, { 'Content-Type' => 'text/plain' }, ['The token does not have a valid "issued at" time.']]
  rescue JWT::DecodeError
    [401, { 'Content-Type' => 'text/plain' }, ['A token must be passed.']]
  end
end

result = verify_vouch_cookie(cookie_value, secret, issuer, exp_leeway)
puts result.inspect

I did not implement this yet in my backend but the goal is to still replace the necessity for the nginx_auth_request module which should be easy with the logic above.

I am also waiting on the proposal in #71. If the accesstoken could get passed in the cookie that would be useful cause that way i could retrieve more information about the logged in user (would like more information then just username) . Unless you tell me that would already be possible?

And additionally also waiting for
#43 cause maybe i can use an oidc client library on my backend todo the verification of Bearer token that would come from vouch. Since it has the mechanism to discover the signing process by getting the key (oidc spec) it seems secure enough to me.

@bnfinet
Copy link
Member

bnfinet commented May 22, 2019

@butsjoh this was implemented in #104, but the cookies got large with the additional IDs so they have been split up as per #109, therefor your ruby code will need to conform to Vouch_1of3 naming convention for the cookies.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants