[
https://issues.apache.org/jira/browse/KNOX-3424?focusedWorklogId=1038584&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-1038584
]
ASF GitHub Bot logged work on KNOX-3424:
----------------------------------------
Author: ASF GitHub Bot
Created on: 28/Aug/26 22:53
Start Date: 28/Aug/26 22:53
Worklog Time Spent: 10m
Work Description: hsheinblatt commented on code in PR #1356:
URL: https://github.com/apache/knox/pull/1356#discussion_r3883353647
##########
gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java:
##########
@@ -125,6 +125,8 @@ public class TokenResource {
protected static final String TOKEN_TTL_PARAM = TOKEN_PARAM_PREFIX + "ttl";
public static final String TOKEN_TYPE_PARAM = TOKEN_PARAM_PREFIX + "type";
private static final String TOKEN_AUDIENCES_PARAM = TOKEN_PARAM_PREFIX +
"audiences";
+ static final String AUDIENCE_QUERY_PARAM = "audience";
Review Comment:
For RFC 8693 token exchange, the audience requested is in the post body. See
section 2.1:
```
The client makes a token exchange request to the token endpoint with an
extension grant type using the HTTP POST method. The following parameters are
included in the HTTP request entity-body using the
application/x-www-form-urlencoded format with a character encoding of UTF-8 as
described in [Appendix B](https://www.rfc-editor.org/rfc/rfc6749#appendix-B) of
[[RFC6749](https://datatracker.ietf.org/doc/html/rfc6749)].
```
then
```
audience
OPTIONAL. The logical name of the target service where the client intends to
use the requested security token. This serves a purpose similar to the resource
parameter but with the client providing a logical name for the target service.
Interpretation of the name requires that the value be something that both the
client and the authorization server understand. An OAuth client identifier, a
SAML entity identifier
[[OASIS.saml-core-2.0-os](http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf)],
and an OpenID Connect Issuer Identifier
[[OpenID.Core](https://openid.net/specs/openid-connect-core-1_0.html)] are
examples of things that might be used as audience parameter values. However,
audience values used with a given authorization server must be unique within
that server to ensure that they are properly interpreted as the intended type
of value. Multiple audience parameters may be used to indicate that the issued
token is intended to be used at the multiple audiences listed. The audience and
resource parameters may be used together to indicate multiple target services
with a mix of logical names and resource URIs.
```
My presumption was that we'd parse out the requested audience from the body
at the same time we parse out the subject_token and optionally the actor_token
in the filter flow. We'd then have to stash that somewhere that the knoxtoken
service could access for inclusion in the minted token, presuming the request
passed filter authz. The commonly used spot seems to be a request header.
So, one possibility is that for RFC 8693 token exchange we require the
audience to be specified in the body and reject requests with it in a header or
query parameter rather than ignore it. We add it to the request headers if
authz passes in the filter. Then for knoxtoken, we can read it from a header
rather than a querystring.
If we want to support post body/header in one path and querystring in
another, we probably want different feature flags for it. But i'm not aware of
any reason to allow the querystring.
##########
gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java:
##########
@@ -198,6 +199,11 @@ private void configureCommonExpectations(Map<String,
String> contextExpectations
EasyMock.expect(request.getParameter(TokenResource.QUERY_PARAMETER_DOAS)).andReturn(contextExpectations.get(TokenResource.QUERY_PARAMETER_DOAS)).anyTimes();
}
EasyMock.expect(request.getParameterNames()).andReturn(Collections.emptyEnumeration()).anyTimes();
+ final Map<String, String[]> parameterMap = new HashMap<>();
Review Comment:
Possibly in the filter for the rfc token exchange we should check there is
no audience querystring param and fail if there is, just to be clear. Also, if
we're stashing a value in the header, we should fail if there already is one
value there. If we switch knoxtoken to also use a header as suggested, then the
former we'd maybe just want to do in general, but the latter we'd leave off if
not rfc token exchange.
##########
gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/WhitelistAudienceValidatorTest.java:
##########
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway.service.knoxtoken;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Test;
+
+public class WhitelistAudienceValidatorTest {
+
+ private final WhitelistAudienceValidator validator = new
WhitelistAudienceValidator();
+
+ @Test
+ public void noRequestedAudienceReturnsConfigured() throws Exception {
Review Comment:
If the backwards compatible case is 'static' and the 'whitelist' is new,
then why not have this return an empty set? That is, the way to get the current
behavior is to use the 'static' validator, it just returns what's configured,
ignores whatever is requested. If you switch to a whitelist, it means you want
to request audiences. It seems like a security hole that if you don't request
an audience you get all of them.
I could see it for an incremental adoption use case: some services start
requesting audiences, but not all are updated yet. The ones that haven't
updated get the same value as the static validator, so remain backwards
compatible until they switch.
It's just that after migration is done, anyone that wants to backslide, or
new services, can start off without requesting any audiences and get them all,
so their token will just work everywhere. So the end state is less secure.
Just an observation, but I'd make sure to document this behavior: you have
to check your audit logs for exchanges that don't request audiences after a
migration to the 'whitelist' validator is done to ensure folk are requesting
appropriate audiences. Or, put in some controls to restrict the number of
audiences allowed in a token for validation.
##########
knox-site/docs/config_knox_token.md:
##########
@@ -99,6 +99,45 @@ This feature is enabled by default. If you want to disable
it, add the following
<value>false</value>
</param>
+#### Requesting a token audience dynamically
+
+By default the `aud` claim of an issued token is fixed to the value(s)
configured in `knox.token.audiences`, and the per-request `audience` query
parameter is ignored. To let a caller request the audience(s) per request
instead, select an *audience validator* that honors the parameter. Multiple
audiences may be supplied either comma-separated in a single parameter or as
repeated parameters; surrounding whitespace is trimmed.
+
+ curl -u admin:admin-password -k
"https://localhost:8443/gateway/homepage/knoxtoken/api/v1/token?audience=service-a"
+
+Which requested audiences are allowed is decided by a pluggable *audience
validator*, selected with `knox.token.audience.validator`:
+
+* `static` (the default) preserves the historical behavior: the `audience`
query parameter is ignored and the statically configured `knox.token.audiences`
are always used. No configuration beyond `knox.token.audiences` is needed and
nothing new is exposed to callers.
+* `whitelist` validates requested audiences against `knox.token.audiences`,
treating it as a whitelist.
+* `passthrough` accepts whatever audience(s) the caller requests without any
local whitelist. It requires no configured `knox.token.audiences`; when the
request contains no `audience` parameter the token is issued with no `aud`
claim (it does not fall back to `knox.token.audiences`).
+
+Selecting the `whitelist` validator enables per-request audiences:
+
+ <param>
+ <name>knox.token.audience.validator</name>
+ <value>whitelist</value>
+ </param>
+
+With the `whitelist` validator its behavior is:
+
+* the request does not contain an `audience` parameter -> the statically
configured `knox.token.audiences` are used, exactly as before (unchanged
default behavior)
+* the request contains an `audience` parameter and every requested audience
is present in `knox.token.audiences` -> only the requested audience(s) are
placed in the token's `aud` claim
+* the request contains an `audience` parameter and any requested audience is
not present in `knox.token.audiences` -> the request is rejected with `400 Bad
Request`
+
+Only exact matches against the whitelist are honored.
+
+The `passthrough` validator instead stamps the requested audience(s) into the
token's `aud` claim verbatim, without any whitelist check, and rejects nothing.
If the request contains no `audience` parameter the token is issued with no
`aud` claim. Because it performs no local authorization, `passthrough` relies
on a downstream JWTProvider to reject tokens whose `aud` does not match the
consumer topology's expected audiences, deferring enforcement to the point of
consumption. Use it only when such consumption-time validation is in place.
Review Comment:
I didn't follow this. Isn't the provider upstream from the knoxtoken? That
is, if configured in the topology, the filter would run before all this code,
and if it failed the audience validation there, no token would be issued, you'd
get a failure response. There's no special requirement on consumption
Issue Time Tracking
-------------------
Worklog Id: (was: 1038584)
Time Spent: 1h (was: 50m)
> Dynamic audience handling in the KNOXTOKEN service
> --------------------------------------------------
>
> Key: KNOX-3424
> URL: https://issues.apache.org/jira/browse/KNOX-3424
> Project: Apache Knox
> Issue Type: Improvement
> Components: Server
> Affects Versions: 3.1.0
> Reporter: Tamás Hanicz
> Assignee: Tamás Hanicz
> Priority: Major
> Time Spent: 1h
> Remaining Estimate: 0h
>
--
This message was sent by Atlassian Jira
(v8.20.10#820010)