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

Hierarchic scope strategy not working for OAuth2 Introspection authn #377

Closed
tleef opened this issue Mar 10, 2020 · 7 comments · Fixed by #379
Closed

Hierarchic scope strategy not working for OAuth2 Introspection authn #377

tleef opened this issue Mar 10, 2020 · 7 comments · Fixed by #379

Comments

@tleef
Copy link
Contributor

tleef commented Mar 10, 2020

Describe the bug

I'm running into an issue authenticating a request with oathkeeper using the hierarchic scope strategy.
I'm trying to lock down my hydra introspection endpoint.

I have added the following to the oathkeeper config to enable the oauth2 introspection authenticator

authenticators:
  oauth2_introspection:
    enabled: true
    config:
      introspection_url: http://localhost:4445/oauth2/introspect
      scope_strategy: hierarchic
      introspection_request_headers:
        x-forwarded-proto: https

I have added the following rule to protect my hydra introspection endpoint

- id: admin:introspect-access-token
  upstream:
    url: http://localhost:4445
    preserve_host: true
    strip_path: /v1/op
  match:
    url: http://<[a-zA-Z0-9-_:\.]+>/v1/op/oauth2/introspect
    methods:
      - POST
  authenticators:
    - handler: oauth2_introspection
      config:
        required_scope:
          - admin.introspect
  authorizer:
    handler: allow
  mutators:
    - handler: noop

I have created the following client for administrative purposes

{
    "client_id": "090e30ed-bc4b-478d-bb82-975a1667334b",
    "client_name": "Admin",
    "redirect_uris": [],
    "grant_types": [
        "client_credentials"
    ],
    "response_types": [],
    "scope": "admin",
    "audience": [],
    "owner": "",
    "policy_uri": "",
    "allowed_cors_origins": [],
    "tos_uri": "",
    "client_uri": "",
    "logo_uri": "",
    "contacts": [],
    "client_secret_expires_at": 0,
    "subject_type": "public",
    "token_endpoint_auth_method": "client_secret_basic",
    "userinfo_signed_response_alg": "none",
    "created_at": "2020-03-10T13:43:55Z",
    "updated_at": "2020-03-10T13:43:55Z",
    "metadata": null
}

Using that client I have obtained the following token.

VRS1H90d3XeP5xX18EeiIG31RHvU-1ewUzcGRLHwT1U.DBqrTNGO0d5h4s8Jn6-3sBGsYvQsL87hjy6PvZDD3Co

I then attempted to introspect that token with the following request (Format from insomnia timeline)

> POST /v1/op/oauth2/introspect HTTP/2
> Host: myapp.com
> User-Agent: insomnia/7.1.1
> Content-Type: application/x-www-form-urlencoded
> Authorization: Bearer VRS1H90d3XeP5xX18EeiIG31RHvU-1ewUzcGRLHwT1U.DBqrTNGO0d5h4s8Jn6-3sBGsYvQsL87hjy6PvZDD3Co
> Accept: */*
> Content-Length: 93

| token=VRS1H90d3XeP5xX18EeiIG31RHvU-1ewUzcGRLHwT1U.DBqrTNGO0d5h4s8Jn6-3sBGsYvQsL87hjy6PvZDD3Co

oathkeeper, which is protecting the introspection endpoint, responded with the following

{
  "error": {
    "code": 401,
    "status": "Unauthorized",
    "request": "bec3fc5f-2569-92ed-8ae7-3925cc3677a0",
    "reason": "Access token i says token is not active",
    "message": "Access credentials are invalid"
  }
}

I then inspected the same token again but this time using a different Authorization token which has the scope of admin.introspect.

> POST /v1/op/oauth2/introspect HTTP/2
> Host: myapp.com
> User-Agent: insomnia/7.1.1
> Content-Type: application/x-www-form-urlencoded
> Authorization: Bearer XPx_x65ReH10Di-HALA62l9TYAQJPZth4t7e7apYMSc.jHVDuDLOqyt9sd6gHQ5Gw2WYlCq7oAk0CBLkbaTgX9M
> Accept: */*
> Content-Length: 93

| token=VRS1H90d3XeP5xX18EeiIG31RHvU-1ewUzcGRLHwT1U.DBqrTNGO0d5h4s8Jn6-3sBGsYvQsL87hjy6PvZDD3Co

This time the request to was successful and I received the following response.

{
  "active": true,
  "scope": "admin",
  "client_id": "090e30ed-bc4b-478d-bb82-975a1667334b",
  "sub": "090e30ed-bc4b-478d-bb82-975a1667334b",
  "exp": 1583860409,
  "iat": 1583856809,
  "iss": "https://myapp.com/v1/op/",
  "token_type": "access_token"
}

As you can see the introspected token is active and has the admin scope. The admin scope should match admin.introspect using the hierarchic scope strategy.

I investigated a little further and found that oathkeeper is sending the required_scope to hydra when inspecting the token.
https://github.com/ory/oathkeeper/blob/master/pipeline/authn/authenticator_oauth2_introspection.go#L88

Since admin.introspect, the required scope, doesn't equal admin, the granted scope, hydra responds with the following when introspecting the token

{
  "active": false
}

Oathkeeper sees this response and rejects the request.

It seems the me that the solution is for oathkeeper to not include the scope when introspecting the token and instead validate the scope itself (which it already does further down)
https://github.com/ory/oathkeeper/blob/master/pipeline/authn/authenticator_oauth2_introspection.go#L132-L138

Environment

  • Version:
  • Oathkeeper: oryd/oathkeeper:v0.33.0-beta.1
  • Hydra: oryd/hydra:v1.1.0-alpine
  • Environment: Docker
@aeneasr
Copy link
Member

aeneasr commented Mar 13, 2020

Thank you for the detailed report! Have you enabled the hierarchic strategy in hydra as well?

@tleef
Copy link
Contributor Author

tleef commented Mar 14, 2020

Are you referencing this setting in the Hydra config?

strategies:
  scope: DEPRECATED_HIERARCHICAL_SCOPE_STRATEGY

I haven't enabled that. Basically because it says it's deprecated.

I'm a little confused about what that setting does. I assumed it allowed tokens to be granted scopes that are hierarchically below the allowed scopes of the client. For example, a client with the admin scope could request a token with a scope of admin.introspect. Is that wrong?

After thinking about it however, I feel like fixing this on the Hydra side shouldn't be the solution since there are many clients of Hydra and they may use different strategies for validating scopes.

Its Oathkeepr's job to do scope validation as a part of authentication. Making it also Hydra's job when introspecting a token seems like it blurs the boundaries a bit.

What do you think?

@aeneasr
Copy link
Member

aeneasr commented Mar 15, 2020

Yeah that's what I meant. We should probably update the DEPRECATED flag.

The problem is that ORY Oathkeeper sends the scope as part of the introspection request. Hydra processes this scope parameter (which is actually not defined in the OAuth2 Introspection RFC) according to the scope strategy defined in Hydra (hence my question). The scope strategy defined in ORY Oathkeeper serves as a fallback for when OAuth2 servers do not implement this feature.

Therefore, these two settings must be kept in sync�. The same problem would arise if you configure your client to be allowed to request scope foo and your OAuth2 request requests foo.bar.

We'll probably change the naming of DEPRECATED_HIERARCHICAL_SCOPE_STRATEGY as there are no plans to deprecate it.

@aeneasr
Copy link
Member

aeneasr commented Mar 15, 2020

This is now referenced here: ory/hydra#1760

I think that if you set the right strategy in Hydra, this will all work as you expect it to. To make it less confusing, we'll update the name in Hydra. If you agree with these findings, we can close this issue as all upstream issues have been created.

@tleef
Copy link
Contributor Author

tleef commented Mar 16, 2020

Thanks for getting back to me over the weekend.

Renaming the DEPRECATED_HIERARCHICAL_SCOPE_STRATEGY in Hydra sounds great.

The problem is that Hydra cannot satisfy the scope strategy for all clients. What if one client wants to introspect a token and validate it using the HIERARCHIC strategy while a different client want's to introspect a token and validate it using the EXACT strategy? These strategies are incompatible so there is no way to configure Hydra to satisfy both clients.

Personally, as it is not part of the RFC, I'd prefer that Hydra didn't do any scope validation on the introspection endpoint. Is it possible to configure Hydra to use the NONE strategy to achieve this effect?

If Hydra can't simply be configured to use the NONE strategy and you don't want to add that, I think the only other workable solution is to just modify Oathkeeper to not send the scope to Hydra. In this way Oathkeeper can opt out of Hydra's scope validation in favor of its own.

Does that sound reasonable to you?

@aeneasr
Copy link
Member

aeneasr commented Mar 16, 2020

I think the only other workable solution is to just modify Oathkeeper to not send the scope to Hydra. In this way Oathkeeper can opt out of Hydra's scope validation in favor of its own.

Yeah, if Oathkeeper enforces a scope strategy it should probably not send the scope in the request as well. All other options are more intrusive / breaking existing behavior :)

Would you be up for a PR?

@tleef
Copy link
Contributor Author

tleef commented Mar 16, 2020

I'll get a PR in for this soon. If not today, in the next couple of days.

aeneasr pushed a commit that referenced this issue Mar 17, 2020
This patch removes the `scope` key from the OAuth2 Introspection request body when a scope strategy other than `none` is set for the OAuth2 Introspection handler. If the scope strategy is `none`, the `scope` key is included in the body.

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

Successfully merging a pull request may close this issue.

2 participants