Closed iperdomo closed 7 years ago
I've started with this. keycloak-connect requires Node version 4.2.6 or higher, we're still using 0.10 for windshaft compatibility, so will first need to see if we can upgrade node (https://github.com/CartoDB/Windshaft/issues/519) OR we'll have to downgrade keycloack-connect, hoping they still have a node-0.10 supporting version.
@strk if we're stuck waiting for Windshaft to support a supported node LTS, we can workaround the issue by having a reverse proxy (nginx/openresty) in front of the service.
You have an example at: https://github.com/akvo/learning-sessions/tree/master/nginx-secured
My understanding is that this service will be a Bearer only, meaning that will not trigger user login and just expect an authorization header: Authorization: bearer <access_token>
, verify the token and if correct proxy_pass
to the node service.
With https://github.com/pingidentity/lua-resty-openidc we can have Windshaft as Resource Server just verifying tokens. One note that the README example of the OAuth2.0 access token mentions the need of having the public certificate as part of the configuration and i don't think that is required, as the server can discover that using the .well-known
URL.
Will try to improve the example and include a bearer only to /api
so we can confirm my assumptions.
Yes, I was thinking about plugging nginx in front too, makes sense. Will prepare an nginx docker then.
More issues, see: https://github.com/pingidentity/lua-resty-openidc/issues/34
Ok found a way of checking the token, using token introspection. Check the latest code from:
https://github.com/akvo/learning-sessions/tree/master/nginx-secured
Will that lua script also take care of injecting a session cookie ? Because the tiler is not currently doing any authentication/sessions
My idea was to have a stateless tiler (a bearer only OAuth 2.0 resource server), the Authorization
header is checked, if not present or invalid, then HTTP 403
Makes sense, but the example you provided is based on receiving a user/password in a POST with a specific Content-Type, while the tiler has different access methods for different operations (POST json to obtain a token, GET to get tiles), so a state must be stored elsewhere (nginx ?) to make this possible, no ?
I'm not sure if I follow the problem. My example is just a way to obtain a token and use it in the authorization header. The process of obtaining the token if out of the scope of this service. For demo purposes, we can use something similar to the bash script in the demo.
Not sure what state needs to be stored? It's not session based. As optimization, the Lua module caches the introspection of the token:
and the time is configured by:
lua_shared_dict introspection 10m;
-- Perhaps 10min is too much for usOk, I understand. You're saying the token is all it takes, once obtained, to authenticate. And we don't need the tiler to handle that because we'd have the nginx service do that. So for the scope of this issue there needs to be a properly setup nginx-akvo container and an additional input widget on the viewer form to enter the token, correct ?
With 57e11661f99e8175ab6a8aa6aeb14c60e9ecd1ff there's a working nginx layer in the stack, but doesn't yet check for the token. The form now asks you for a token, but the token isn't yet sent to the service by the client, and I've yet to figure out how to always fetch the token. Maybe simplest would be making it part of the api URI (appending it as a query parameter or path-info component).
I think that Authorization: Bearer <token>
header should be the way
I'm not sure how we could add that header to requests for images (which are used to fetch the actual tiles). If it's just the map creation that you want to protect, then it'd be easier.
Yeah, we can start with protecting the map creation, as is a difficult to guess URL and we'll serve over HTTPS
So 1e0c0239e67b40edfacd54a33254527e214ee639 protects POST to /layergroup
API.
Beware of caches, it's easy to be trapped there.
The frontend is not made to give nice feedback upon unauthorized access so make sure to use a browser debugger to see what's going on. I'm able to create maps with passing my token, unable w/out passing it. But it looks like any token is accepted, so the rule needs be tweaked to require a success too...
it looks like I'll need some lua
primer to deal with this next step..
After getting a primery on JWT I'm now back on this issue. I understand akvo keycloak is using asymmetric key to sign JWT tokens so we might as well for the POC hard-code the public key, would that be ok ? (postponing introspection, that is)
So with 867e129e25e45e07e2f3f87a06b514f138b15c52 it looks like we're doing proper JWT verification. There's now a util/get_token.sh shell script to obtain a token to put in the web form. I guess we could improve it to directly enter username/password in the form, and obtain the token when needed (as it expires).
Tests welcome anyway
Tested at rev 867e129e25e45e07e2f3f87a06b514f138b15c52 and authentication works!
No auth header:
Auth header:
Quick question: How did you converted from a JWK to a PEM like format?
If you mean the certificate put into nginx config file I downloaded it from the admin panel of the akvo test keycloak instance.
Note I didn't try hard to hack the service (craft a malicious JWT). If you have the tools to do that, it could be a good idea to put them in action. I made my own script to base64 decode and indent the json from the token, but don't have a script to build it back (and provide unsigned or signed with a different key).
An easy way to test is to send an expired token, by default access tokens are valid only for 5 min. Will try it out and report back.
c812a4d adds a short document about the implementation. I've tested expired JWT token myself, it works as expected (forbidden) but I don't know if the LUA script only checks expiration date w/out even trying to verify the signature.
My understanding of JWT is that it is composed of 3 parts:
The expiration time should be in the payload ("exp" field) so it doesn't take any verification to forbid. A crafted JWT would have for example just an updated expiration but the same old signature. This one I can try I guess ... will try now. For the record: this is an expired token of mine:
eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJZREFraDh6UEZxNUtkZm5xdFpabHBWRzJmYWpsTHpBY3FoN0JtVjI4OUpFIn0.eyJqdGkiOiIzMzdmMWQ2Zi0yMTg1LTQ3ZGEtYTBlNy0yYzRlNjQ1YzgxNTAiLCJleHAiOjE0ODczMjg2NTEsIm5iZiI6MCwiaWF0IjoxNDg3MzI4MzUxLCJpc3MiOiJodHRwczovL2tjLmFrdm90ZXN0Lm9yZy9hdXRoL3JlYWxtcy9ha3ZvIiwiYXVkIjoiY3VybCIsInN1YiI6IjkyZWJlOWVhLTdhNzAtNGY0Ni04NGQxLTcyOGUzNWFlZDM5NyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImN1cmwiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiJkZGM4OWRjMS1kODViLTQ1NzEtOWExNC05MDE4NzkyZjUzYjAiLCJhY3IiOiIxIiwiY2xpZW50X3Nlc3Npb24iOiJjMjYzZDMyMC02ZDRiLTQ0MzAtOWJkNi04MTUyMDE3OTQ2OTciLCJhbGxvd2VkLW9yaWdpbnMiOltdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50Iiwidmlldy1wcm9maWxlIl19fSwibmFtZSI6IlNhbmRybyBTYW50aWxsaSIsInByZWZlcnJlZF91c2VybmFtZSI6InN0cmsiLCJnaXZlbl9uYW1lIjoiU2FuZHJvIiwiZmFtaWx5X25hbWUiOiJTYW50aWxsaSIsImVtYWlsIjoic3Rya0BrYnQuaW8ifQ.GI_8VSTUQfJ8PmE2hvWDmMlX7hfFsqeQHaPyCH_ojCPB1CWEGSTfeeynjvp8CTPxu7481O7yHKxRj_G0uMdTzqGHVmLk3UXG9oRX1zwcThcRVvH_kzzSmfTJcCYGHgWesmY7rdlnCgo33KiXEK0-tmBqn4VoEHzxVPBG7tZ6Doky3uPvr1vdQqJRm7exzF3Xl_NNITYLgYVtubUkODT_22CYR5azf4HfJItZCDU8-ciWEPjVpwQP7E6kSla_dRplrloiswFJHVb3PPVNnZ614HMkN-4DtsuI0C15lFv4rmbujJC7PrQmfa65t1N8SaR15rxINQPdJtVq4Ad40XlFQw
Ok, tested: invalid token: Verification failed Used token:
eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJZREFraDh6UEZxNUtkZm5xdFpabHBWRzJmYWpsTHpBY3FoN0JtVjI4OUpFIn0K.eyJqdGkiOiIyNGNkMjdjZS1iY2VkLTRmM2YtYTViYS1mNWU1ZDgzNTQzMmQiLCJleHAiOjE0ODgyNzA3NTYsIm5iZiI6MCwiaWF0IjoxNDg4MjcwMjEyLCJpc3MiOiJodHRwczovL2tjLmFrdm90ZXN0Lm9yZy9hdXRoL3JlYWxtcy9ha3ZvIiwiYXVkIjoiY3VybCIsInN1YiI6IjkyZWJlOWVhLTdhNzAtNGY0Ni04NGQxLTcyOGUzNWFlZDM5NyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImN1cmwiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiJhZGQ3NDY5OC1lY2JiLTRkMzMtYmJiOC0wODk3NmMyOTNiNWYiLCJhY3IiOiIxIiwiY2xpZW50X3Nlc3Npb24iOiI3N2YyMDg1Yy04YTE2LTRlZGYtOTgyYi05YmJiZTI2OWQ2ZjIiLCJhbGxvd2VkLW9yaWdpbnMiOltdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiYWt2bzpsdW1lbjpsdW1lbiIsInVtYV9hdXRob3JpemF0aW9uIiwiYWt2bzpsdW1lbjpsdW1lbjphZG1pbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJ2aWV3LXByb2ZpbGUiXX19LCJuYW1lIjoiU2FuZHJvIFNhbnRpbGxpIiwicHJlZmVycmVkX3VzZXJuYW1lIjoic3RyayIsImdpdmVuX25hbWUiOiJTYW5kcm8iLCJmYW1pbHlfbmFtZSI6IlNhbnRpbGxpIiwiZW1haWwiOiJzdHJrQGtidC5pbyJ9Cg==.c0tpUNGoz6cuC-WFTWUkDJjc8Xu0U2fLXEGOQJs1_3tZYHhsHZ5ojCChQn7jUGiq9f9_mor1ki3LrsYIMyZ4YfRfFmRci52-A8e3HlAQAzoPfhzIjSepuY8eVbdPkqXU6qCP8MbErVr1FkmXjdkhq511Xz0mNmAhjuwJ3BcTR187rV7Hr1n7FpkT0OE2FZsLCoATcZyq3Q55XpkIzStqm5DG_OLB9C1gkxP3DgRQmfypi9YzYhBuO_UqD6HYoOSVwtgoTYYuRIjrWoYVtusenfeOdACBBYgRzOH7NVMJXW6-DWjHSy3JgJ_bWw9ONw4nJym4F-5IvZzZd3wfAP95VQ
I think we're go with this one. Closing the issue
Just for the record, with 97c25d94e8030cd6170bc4882725ad7c5f249aab I had nginx talk to varnish instead of tiler directly
Description
We're implementing Keycloak for single sign-on in our products. Keycloak also has authorization capabilites via Authorization services. Where we can have a fine grained authorization check for a given request.
The initial step is to have our Maps services authenticated with Keycloak (Note: authorization will be a different iteration).
For that Keycloak has some Node.js libraries that we can use for the job (since we're using Express in our sample server).
Deliverables