|
| 1 | +# `OpenIDConnectPolicy` - a metapolicy |
| 2 | + |
| 3 | +- Feature Name: oidc-policy |
| 4 | +- Start Date: 2025-02-05 |
| 5 | +- RFC PR: [Kuadrant/architecture#0114](https://github.com/Kuadrant/architecture/pull/114) |
| 6 | +- Issue tracking: [Kuadrant/architecture#0000](https://github.com/Kuadrant/architecture/issues/0000) |
| 7 | + |
| 8 | +# Summary |
| 9 | +[summary]: #summary |
| 10 | + |
| 11 | +The `OpenIDConnectPolicy` aims at helping users to easily & quickly get started with the Kuadrant stack to setup an |
| 12 | +[OpenID Connect flow](https://openid.net/developers/how-connect-works/) without needing to initially understand how to |
| 13 | +use Kuadrant's `AuthPolicy` to achieve their goals. |
| 14 | + |
| 15 | +# Motivation |
| 16 | +[motivation]: #motivation |
| 17 | + |
| 18 | +There are a few goals this implementation hopes to achieve and set a precedent for introducing more such APIs in |
| 19 | +Kuadrant: |
| 20 | + |
| 21 | + - users looking at an example policy would be able to make mostly sense of it without needing to lookup documentation; |
| 22 | + - the policy would apply sensible defaults in most cases and only require the very minimal input from the user; |
| 23 | + - the idea is to implement it as _metapolicy_, which would only be consumed by a controller to produce a `AuthPolicy` |
| 24 | + that would then enforce the flow; |
| 25 | + - it is _not_ the goal to expose all the power of the `AuthPolicy` to the user through the new policy, but rather to let |
| 26 | + users achieve most of their goals by slowly exploring defaults of the policy and eventually dig down the stack, into |
| 27 | + the `AuthPolicy` to expand their use cases when they need it; |
| 28 | + - explore how to, overtime, grow the initial minimal API of the policy into a larger one to cover more use cases than |
| 29 | + the one currently set as the goal for this RFC |
| 30 | + |
| 31 | +# Guide-level explanation |
| 32 | +[guide-level-explanation]: #guide-level-explanation |
| 33 | + |
| 34 | +The `OpenIDConnectPolicy` is a direct policy attachment that attaches to one or multiple |
| 35 | +[`HttpRoute`](https://gateway-api.sigs.k8s.io/api-types/httproute/) objects. Targeted routes will require requests to |
| 36 | +carry an access token to be authorized through. Should the token be missing or be invalid, the requesting entity gets |
| 37 | +redirected to the `provider`'s `authorizationEndpoint` specified in the policy for them to identify. |
| 38 | + |
| 39 | +It is important to note here that the access token is to be negotiated stored by the client. While this may change in |
| 40 | +the future, currently the token is to either be provided by the appropriate HTTP header (`Authorization: Bearer`, which |
| 41 | +is the default source), or by a Cookie. |
| 42 | + |
| 43 | +## Minimal example |
| 44 | + |
| 45 | +Using a very minimal example, we'll use |
| 46 | +[gitlab.com](https://docs.gitlab.com/ee/integration/openid_connect_provider.html) as our provider for an OpenID Connect |
| 47 | +flow to protect all requests routed to our application by the `HttpRoute`. |
| 48 | + |
| 49 | +```yaml |
| 50 | +apiVersion: kuadrant.io/v1alpha1 |
| 51 | +kind: OpenIDConnectPolicy |
| 52 | +metadata: |
| 53 | + name: gitlab |
| 54 | +spec: |
| 55 | + targetRef: |
| 56 | + name: toystore |
| 57 | + group: gateway.networking.k8s.io |
| 58 | + kind: HttpRoute |
| 59 | + provider: |
| 60 | + host: gitlab.com |
| 61 | + credentials: |
| 62 | + clientID: 35001bfef37042bf2fb125e9e8f99f0c719231632ab62a18cbf5220c3d1f8f10 |
| 63 | +``` |
| 64 | +
|
| 65 | +First we need to provide the `host` of our `provider`, in this case `gitlab.com`. Kuadrant will default to using |
| 66 | +`https` to [query the provider for the additional metadata](https://datatracker.ietf.org/doc/html/rfc8414) it requires |
| 67 | +from [gitlab's endpoing](https://gitlab.com/.well-known/openid-configuration), initially the `authorizationEndpoint` to which |
| 68 | +to redirect unauthorized requests to. |
| 69 | + |
| 70 | +If the request for the additional metadata fails, all access to the protected resources will be `Unauthorized`. All the |
| 71 | +information from the endpoint can be manually specified under `provider` to mitigate these failures. These values would |
| 72 | +also override any values successfully obtained from querying the provider's endpoint. |
| 73 | + |
| 74 | +Next, we'll have to identify our request with the provider using the `clientID` that were |
| 75 | +provided to us by, in this example, gitlab. You'd probably rather store these in a `credentialsRef` instead as we did |
| 76 | +here. A `credentialsRef` would point to a Kubernetes' |
| 77 | +[`Secret`](https://kubernetes.io/docs/concepts/configuration/secret/) with the required information. And that's all you |
| 78 | +should need to get started with to use an OpenID Connect provider to authorize access to your application. |
| 79 | + |
| 80 | +## The complete `OpenIDConnectPolicy` |
| 81 | + |
| 82 | +Here is an example of the complete proposal for configuring an authorization flow using OpenID Connect: |
| 83 | + |
| 84 | +```yaml |
| 85 | +apiVersion: kuadrant.io/v1alpha1 |
| 86 | +kind: OpenIDConnectPolicy |
| 87 | +metadata: |
| 88 | + name: gitlab |
| 89 | +spec: |
| 90 | + targetRef: |
| 91 | + name: toystore |
| 92 | + group: gateway.networking.k8s.io |
| 93 | + kind: HttpRoute |
| 94 | + tokenSource: authorizationHeader |
| 95 | + provider: |
| 96 | + host: gitlab.com |
| 97 | + discoveryEndpoint: https://gitlab.com/.well-known/openid-configuration |
| 98 | + authorizationEndpoint: https://gitlab.com/oauth/authorize |
| 99 | + authorizationEndpointPayload: |
| 100 | + query: |
| 101 | + client_id: provider.credentials.clientID |
| 102 | + redirect_uri: >- route.gateway.listeners[0].hostname + "/oauth/callback" |
| 103 | + scope: >- "openid" |
| 104 | + response_type: >- "code" |
| 105 | + tokenEndpoint: https://gitlab.com/oauth/token |
| 106 | + introspectionEndpoint: https://gitlab.com/oauth/introspect |
| 107 | + jwksUri: https://gitlab.com/oauth/discovery/keys |
| 108 | + credentials: |
| 109 | + clientID: 35001bfef37042bf2fb125e9e8f99f0c719231632ab62a18cbf5220c3d1f8f10 |
| 110 | + clientSecret: gloas-35001bfef37042bf2fb125e9e8f99f0c719231632ab62a18cbf5220c3d1f8f10 |
| 111 | + redirectUri: |
| 112 | + host: example.org |
| 113 | + path: /oauth/callback |
| 114 | +``` |
| 115 | + |
| 116 | +Some of these were inferred in the simple example above, while others are implied and some not even necessary for the |
| 117 | +basic case. `tokenEndpoint` and `introspectionEndpoint` are both unnecessary for this flow to succeed. That's because we |
| 118 | +use the `authorizationHeader` as the source for our access token. In this flow, we let the client deal with getting the |
| 119 | +token from the identity provider, i.e. gitlab, as well as storing it and sending it along each request to our |
| 120 | +application. |
| 121 | + |
| 122 | +While we do need the `authorizationEndpoint` to redirect unauthorized requests to the identity provider, as well as the |
| 123 | +`jwksUri` to validate the token when one is present, these can be inferred from the `host` by querying the |
| 124 | +`discoveryEndpoint` from the provider, which itself is inferred from the `host`. |
| 125 | + |
| 126 | +Kuadrant will also automatically append a query string to the `authorizationEndpoint` when forwarding a user. That will |
| 127 | +contain data inferred from the configuration itself, as well as some defaults: |
| 128 | + |
| 129 | + - `client_id`: from the `provider`'s `credential` section |
| 130 | + - `redirect_uri`: composed of `host`, inferred if there is a single listener for the `Gateway` the targeted `HttpRoute` |
| 131 | + is pointed to; and the `path`, which defaults to `/oauth/callback` |
| 132 | + - `scope`: defaults to `openid` |
| 133 | + - `response_type`: defaulting to `code` |
| 134 | + |
| 135 | + All of which can be overridden by explicitly configuring the `authorizationEndpointPayload` explicitly. Any entry can |
| 136 | + be added, the value needs to be a valid CEL expression and will be encoded depending on the how the payload is to be |
| 137 | + sent. Here `query`, each key/value pair will be URL encoded and appended to the `authorizationEndpoint` when |
| 138 | + redirecting. |
| 139 | + |
| 140 | +# Reference-level explanation |
| 141 | +[reference-level-explanation]: #reference-level-explanation |
| 142 | + |
| 143 | +### WIP |
| 144 | + |
| 145 | +Some of this I think I have sorted out in a proof of concept... But I'm not completely done yet. |
| 146 | +I'm trying to see if I can get away with: |
| 147 | + - Having the client completely deal with `401 Unauthorized` and have it initiate the flow; |
| 148 | + - Having the client intercept the redirect when being redirected from the Identity Provider, rather than needing to |
| 149 | + have the `AuthConfig` deal with it so that the flow is completely handled by the client. |
| 150 | + |
| 151 | +I'm doing this by using the information provided here and using it inside the `AuthPolicy` directly for now. All that |
| 152 | +then remains is writing the mapper controller for "translating" the `OpenIDConnectPolicy` into an `AuthPolicy`. |
| 153 | + |
| 154 | +# Drawbacks |
| 155 | +[drawbacks]: #drawbacks |
| 156 | + |
| 157 | + |
| 158 | +# Rationale and alternatives |
| 159 | +[rationale-and-alternatives]: #rationale-and-alternatives |
| 160 | + |
| 161 | + - This iteration is fairly low risk. It tries to make this all work with multiple OIDC providers, starting with Gitlab. |
| 162 | + - Today, no changes are required to `AuthorizationPolicy` or `Authorino` nor any other component in Kuadrant, this only |
| 163 | + does some to the heavy lifting for the users. |
| 164 | + - The API leaves some space to grow it more complex use cases as we evolve this to support possibly more advanced |
| 165 | + use cases. |
| 166 | + |
| 167 | +# Prior art |
| 168 | +[prior-art]: #prior-art |
| 169 | + |
| 170 | + - I'm looking into some OIDC client & server side libraries to see how to best shape the API. |
| 171 | + |
| 172 | +# Unresolved questions |
| 173 | +[unresolved-questions]: #unresolved-questions |
| 174 | + |
| 175 | + |
| 176 | +# Future possibilities |
| 177 | +[future-possibilities]: #future-possibilities |
| 178 | + |
| 179 | + - Add support for letting the upstream deal with the token exchange? |
| 180 | + - Add support for letting Kuadrant (i.e. Authorino?) exchange the token with the Identity Provider and store it? |
| 181 | + |
0 commit comments