pomerium / pomerium

Pomerium is an identity and context-aware access proxy.
https://www.pomerium.com
Apache License 2.0
4.05k stars 284 forks source link

Struggling with JWT Claims Headers #1588

Closed ajcollett closed 3 years ago

ajcollett commented 4 years ago

Hi,

So I have a similar forward-auth setup as: https://www.pomerium.io/guides/nginx.html With Pom v0.10.6 and NGINX as the Reverse Proxy.

I have set my NGINX config for my site as linked, here's the full thing with extra headers:

server {
  listen       *:443 ssl;

  server_name  <some.server>;

<snip>

  auth_request /authorize;
  client_max_body_size 200m;
  error_page  401 @error401;
  add_header "Set-Cookie" "$saved_set_cookie";
  location / {
    proxy_pass            http://webapp/;
    proxy_read_timeout    90s;
    proxy_connect_timeout 90s;
    proxy_send_timeout    90s;
    proxy_set_header      Host $http_host;
    proxy_set_header      X-Nginx-Proxy true;
    proxy_set_header      X-Real-IP $remote_addr;
    proxy_set_header      X-Forward-For $proxy_add_x_forwarded_for;
    proxy_set_header      X-Forward-Proto $scheme;
    proxy_set_header      X-Pomerium-key <snip>;
    proxy_set_header      X-Pomerium-Claim-User $user;
    proxy_set_header      X-Pomerium-Claim-Email $email;
    proxy_set_header      X-Pomerium-Claim-Name $name;
    auth_request_set $saved_set_cookie $upstream_http_set_cookie;
    auth_request_set $user $upstream_http_x_pomerium_claim_user;
    auth_request_set $email $upstream_http_x_pomerium_claim_email;
    auth_request_set $name $upstream_http_x_pomerium_claim_name;
    proxy_redirect off;
  }

  location @error401 {
    index     index.html index.htm index.php;
    return 302 https://<fwdauth.endpoint>/?uri=$scheme://$http_host$request_uri;
  }

  location /authorize {
    proxy_pass            http://<auth.server>/verify?uri=$scheme://$http_host$request_uri;
    proxy_read_timeout    90s;
    proxy_connect_timeout 90s;
    proxy_send_timeout    90s;
    proxy_set_header      Host <fwdauth.endpoint>;
    proxy_http_version 1.1;
  }
}

I have also enable the claims with an Env Var

JWT_CLAIMS_HEADERS=email,user,name

And in Policy:

  - from: https://mysite.com
    to: https://example.com
    pass_identity_headers: true
    set_request_headers:
      X-Pomerium-key: <some_key>
    tls_skip_verify: true
    allowed_groups:
      - <group>
    allowed_users:
      - <users>

I can reach my final site, but the Name, Email and User X-Pomerium-Claims don't seem to be set. The custom X-Pomerium-key header works. However, if I try pass it from Pomerium instead of NGINX it also doesn't work.

The NGINX config for the auth and fwdauth URLS looks like this:

server {
  listen       *:443 ssl;

  server_name  <snip>;

<snip>

  location / {
    proxy_pass            http://<my_server>/;
    proxy_read_timeout    90s;
    proxy_connect_timeout 90s;
    proxy_send_timeout    90s;
    proxy_set_header      Host $http_host;
    proxy_http_version 1.1;
  }
}

Maybe I've just bungled my NGINX config here, but I cannot for the life of me see why the headers aren't passed through. This used to work when I had Pomerium setup in front of the app, instead of "next to" it with Nginx in front. Are the "set_request_headers" supposed to work with a fwdauth config? Or is that known to not work?

travisgroth commented 3 years ago

Hi @ajcollett,

% curl -b "${COOKIE}" --insecure -i 'https://forwardauth.localhost.pomerium.io/verify?uri=https://httpbin.localhost.pomerium.io'
HTTP/1.1 200 OK
accept: */*
content-length: 0
content-type: text/plain; charset=utf-8
cookie: XXXXXXXX
user-agent: curl/7.70.0
x-content-type-options: nosniff
x-envoy-expected-rq-timeout-ms: 15000
x-envoy-internal: true
x-forwarded-for: 192.168.0.154
x-forwarded-proto: https
x-pomerium-claim-email: XXXXXXXX
x-pomerium-claim-user: XXXXXXXX
x-pomerium-jwt-assertion: XXXXXXXX
x-request-id: e67011fc-90d8-44d1-b7df-d3838d65a47a
date: Tue, 24 Nov 2020 20:48:23 GMT
x-envoy-upstream-service-time: 0
server: envoy

Adding this block to the location / section of https://github.com/pomerium/pomerium/tree/master/examples/nginx does appear to set the X-Pomerium-Claim-Email header correctly correctly.

    proxy_set_header      X-Pomerium-Claim-Email $email;
    auth_request_set $email $upstream_http_x_pomerium_claim_email;

For the purposes of debugging, you may want to configure a route to https://httpbin.org directly through pomerium and check that you see the claims on the request when not in forward auth. Example:

policy:
  - from: https://httpbin.localhost.pomerium.io
    to: https://httpbin
    pass_identity_headers: true
    allowed_domains:
      - gmail.com
ajcollett commented 3 years ago

Hey, sorry for the late reply. Let me look into this and let you know.

ajcollett commented 3 years ago

Hello,

Okay, so it's been a while, but I have come back to this.

I eventually took the fwauth part out, and am using Pomerium directly.

I now have this in my config:

JWT_CLAIMS_HEADERS=email,user,preferred_username,oid,tid,unique_name,name,sub

And yet, I only get:

X-Pomerium-Claim-Email: myemail@fred.flint
X-Pomerium-Claim-User: azure/<oid>
X-Pomerium-Claim-Sub: azure/<oid>

I don't get "Name" or any of the other claims I request.

Could this be something in the Azure config for the IDP client? What are the default claims I should be able to access? I see these in the MS docs:

https://docs.microsoft.com/en-us/azure/active-directory/develop/id-tokens

So I am somewhat confused. Though I am starting to think there is some restriction on the azure side.

Thoughts?

ajcollett commented 3 years ago

So, I see this high up in the process when I try login:

8:41AM ERR directory: failed to refresh user data error="rpc error: code = Unknown desc = azure: error querying api (https://graph.microsoft.com/v1.0/users/<snip>): 403 Forbidden"  

But I can't tell if this happens before or after I login.

ajcollett commented 3 years ago

I have requested my IT dept to look into the issue too, maybe there is something on the Azure side.

Can you advise what I should be expecting back from Azure, or rather, what Pomerium requests?

ajcollett commented 3 years ago

I think my issues come from the optional claims that MS introduced. I am going to debug that then revert back here.

ajcollett commented 3 years ago

So I sat with my IT personal and we enabled a whole bunch of optional claims.

No change, at all. I cannot fathom what is wrong.

I am using the latest. I will try a few different versions now. Maybe something will make a difference.

calebdoxsey commented 3 years ago

@ajcollett I'm not sure, but looking at the code I think we only attach a predefined set of JWT claims. There was a major refactor of this code to move it to rego:

jwt_claims := [
    ["iss", jwt_payload_iss],
    ["aud", jwt_payload_aud],
    ["jti", jwt_payload_jti],
    ["exp", jwt_payload_exp],
    ["iat", jwt_payload_iat],
    ["sub", jwt_payload_sub],
    ["user", jwt_payload_user],
    ["email", jwt_payload_email],
    ["groups", jwt_payload_groups],
]

But I can't remember if this is how it was always intended to work, or if we accidentally removed a feature. Regardless we should be able to update the code to support arbitrary claims by updating the code.

ajcollett commented 3 years ago

Hello @calebdoxsey

Ah that makes a lot more sense. I decoded the JWT assertion and only got those on the list you posted.

I used to get "name" as well for the JWT headers, but I am not sure what that was pulled form, since azure doesn't have a "name" claim. But that changed at some point.

This is the list of optional JWT claims I wanted to make: JWT_CLAIMS_HEADERS=email,user,family_name,given_name,upn,preferred_username,email,xms_pl,xms_tpl,groups,in_corp

I think if Pomerium wants to support arbitrary claims, you might want to add an additional Config option. Not sure.

I don't know if this is at all related? https://github.com/pomerium/pomerium/issues/1855

ajcollett commented 3 years ago

@calebdoxsey okay now I am properly confused. I just had a student login and they got a Name attribute. However, after I decoded the JWT assertion, it wasn't there. . . how is the name retrieved for the user? Why would it be missing for some people?

EDIT

Sorry for the noise. The Student had faked some headers. So no, there is no name header.

calebdoxsey commented 3 years ago

@ajcollett no problem. We'll be discussing this issue today and we'll get back to you.

calebdoxsey commented 3 years ago

@ajcollett I've re-added arbitrary JWT claim support and merged to master. If you have a chance could you test it to see if it works for you? Thanks.

ajcollett commented 3 years ago

Very nice work. From my limited testing, it works like a bomb. I deployed the master docker image.

I am getting all, well most, of the claims. But all the ones I am not getting are likely due to them being empty (language related, and "in corporate net" for example).

That really helps me. You mention that this was re-added. What was the reason it was removed? Or was it just how things landed up?

calebdoxsey commented 3 years ago

@ajcollett The removal was accidental. We did a major refactor and it was a feature I overlooked. Thanks again for your patience.

ajcollett commented 3 years ago

Ah okay. No worries!