[ https://issues.apache.org/jira/browse/KNOX-3028?focusedWorklogId=914513&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-914513 ]
ASF GitHub Bot logged work on KNOX-3028: ---------------------------------------- Author: ASF GitHub Bot Created on: 13/Apr/24 20:40 Start Date: 13/Apr/24 20:40 Worklog Time Spent: 10m Work Description: lmccay commented on code in PR #900: URL: https://github.com/apache/knox/pull/900#discussion_r1564244621 ########## gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java: ########## @@ -853,105 +917,148 @@ private Response getAuthenticationToken() { if (userTokens.size() >= tokenLimitPerUser) { log.tokenLimitExceeded(userName); if (UserLimitExceededAction.RETURN_ERROR == userLimitExceededAction) { - return Response.status(Response.Status.FORBIDDEN).entity("{ \"Unable to get token - token limit exceeded.\" }").build(); + response = Response.status(Response.Status.FORBIDDEN).entity("{ \"Unable to get token - token limit exceeded.\" }").build(); } else { // userTokens is an ordered collection (by issue time) -> the first element is the oldest one final String oldestTokenId = userTokens.iterator().next().getTokenId(); log.generalInfoMessage(String.format(Locale.getDefault(), "Revoking %s's oldest token %s ...", userName, Tokens.getTokenIDDisplayText(oldestTokenId))); final Response revocationResponse = revoke(oldestTokenId); if (Response.Status.OK.getStatusCode() != revocationResponse.getStatus()) { - return Response.status(Response.Status.fromStatusCode(revocationResponse.getStatus())) + response = Response.status(Response.Status.fromStatusCode(revocationResponse.getStatus())) .entity("{\n \"error\": \"An error occurred during the oldest token revocation of " + userName + " \"\n}\n").build(); } } } } } + return response; + } - try { - final boolean managedToken = tokenStateService != null; - JWT token; - JWTokenAttributes jwtAttributes; - final JWTokenAttributesBuilder jwtAttributesBuilder = new JWTokenAttributesBuilder(); - jwtAttributesBuilder - .setIssuer(tokenIssuer) - .setUserName(userName) - .setAlgorithm(signatureAlgorithm) - .setExpires(expires) - .setManaged(managedToken) - .setJku(jku) - .setType(tokenType); - if (!targetAudiences.isEmpty()) { - jwtAttributesBuilder.setAudiences(targetAudiences); + protected void setupPublicCertPEM() { + GatewayServices services = getGatewayServices(); + if (endpointPublicCert == null) { + // acquire PEM for gateway identity of this gateway instance + KeystoreService ks = services.getService(ServiceType.KEYSTORE_SERVICE); + if (ks != null) { + try { + Certificate cert = ks.getCertificateForGateway(); + byte[] bytes = cert.getEncoded(); + endpointPublicCert = Base64.encodeBase64String(bytes); + } catch (KeyStoreException | KeystoreServiceException | CertificateEncodingException e) { + // assuming that certs will be properly provisioned across all clients + log.unableToAcquireCertForEndpointClients(e); + } } - if (shouldIncludeGroups()) { - if (includeGroupsInTokenAllowed) { - jwtAttributesBuilder.setGroups(groups()); - } else { - return Response - .status(Response.Status.BAD_REQUEST) - .entity("{\n \"error\": \"Including group information in tokens is disabled\"\n}\n") - .build(); + } + } + + protected Response enforceClientCertIfRequired() { + Response response = null; + if (clientCertRequired) { + X509Certificate cert = extractCertificate(request); + if (cert != null) { + if (!allowedDNs.contains(cert.getSubjectDN().getName().replaceAll("\\s+", ""))) { + response = Response.status(Response.Status.FORBIDDEN) + .entity("{ \"Unable to get token - untrusted client cert.\" }") + .build(); } + } else { + response = Response.status(Response.Status.FORBIDDEN) + .entity("{ \"Unable to get token - client cert required.\" }") + .build(); } + } + return response; + } - jwtAttributes = jwtAttributesBuilder.build(); - token = ts.issueToken(jwtAttributes); + protected void persistTokenDetails(ResponseMap result, long expires, String userName, String createdBy) { + // Optional token store service persistence + if (tokenStateService != null) { + final long issueTime = System.currentTimeMillis(); + tokenStateService.addToken(result.tokenId, + issueTime, + expires, + maxTokenLifetime.orElse(tokenStateService.getDefaultMaxLifetimeDuration())); + final String comment = request.getParameter(COMMENT); + final TokenMetadata tokenMetadata = new TokenMetadata(userName, StringUtils.isBlank(comment) ? null : comment); + tokenMetadata.setPasscode(tokenMAC.hash(result.tokenId, issueTime, userName, result.passcode)); + addArbitraryTokenMetadata(tokenMetadata); + if (createdBy != null) { + tokenMetadata.setCreatedBy(createdBy); + } + tokenStateService.addMetadata(result.tokenId, tokenMetadata); + log.storedToken(getTopologyName(), Tokens.getTokenDisplayText(result.accessToken), Tokens.getTokenIDDisplayText(result.tokenId)); + } + } - if (token != null) { - String accessToken = token.toString(); - String tokenId = TokenUtils.getTokenId(token); - log.issuedToken(getTopologyName(), Tokens.getTokenDisplayText(accessToken), Tokens.getTokenIDDisplayText(tokenId)); - - final HashMap<String, Object> map = new HashMap<>(); - map.put(ACCESS_TOKEN, accessToken); - map.put(TOKEN_ID, tokenId); - map.put(MANAGED_TOKEN, String.valueOf(managedToken)); - map.put(TOKEN_TYPE, BEARER); - map.put(EXPIRES_IN, expires); - if (tokenTargetUrl != null) { - map.put(TARGET_URL, tokenTargetUrl); - } - if (tokenClientDataMap != null) { - map.putAll(tokenClientDataMap); - } - if (endpointPublicCert != null) { - map.put(ENDPOINT_PUBLIC_CERT, endpointPublicCert); - } + protected ResponseMap buildResponseMap(JWT token, long expires) { + String accessToken = token.toString(); + String tokenId = TokenUtils.getTokenId(token); + final boolean managedToken = tokenStateService != null; + + log.issuedToken(getTopologyName(), Tokens.getTokenDisplayText(accessToken), Tokens.getTokenIDDisplayText(tokenId)); + + final HashMap<String, Object> map = new HashMap<>(); Review Comment: Missed that one. The intellij refactoring seems to have done that in a few places. PMD caught two or three others which I already changed. I'll do this one too. Issue Time Tracking ------------------- Worklog Id: (was: 914513) Time Spent: 1h 40m (was: 1.5h) > KnoxToken extension for OAuth Token Flows > ----------------------------------------- > > Key: KNOX-3028 > URL: https://issues.apache.org/jira/browse/KNOX-3028 > Project: Apache Knox > Issue Type: Bug > Components: JWT > Reporter: Larry McCay > Assignee: Larry McCay > Priority: Major > Fix For: 2.1.0 > > Time Spent: 1h 40m > Remaining Estimate: 0h > > This change will extend the existing TokenResource for KNOXTOKEN service to > include OAuth specifics such as expected URL, error messages and flows to > support Token Exchange Flow and Token Refresh. > This is being driven by a specific need to proxy access to the Iceberg REST > Catalog API. In this specific usecase, we need to intercept the use of the > following endpoint URLs and serve the token exchange flow for the > authenticating user. > {code} > /v1/oauth/tokens > {code} > Details for these requirements can be found in the openapi description for > the catalog API [1]. > In addition to this usecase, we should add generic support for the token > exchange flow with more generic URL that better aligns with what others use. > {code} > /oauth/v1/token > {code} > We will support the use of the "oauth" service name within the existing > KNOXTOKEN service with an extension of the TokenResource which adapts the > existing KNOXTOKEN behavior to the expectations of clients on OAuth responses. > In order to support both URLs, the deployment contributor will need to > register a url pattern for each usecase and the resource path within the > jersey service will need to accommodate the dynamic nature of the Iceberg > REST Catalog API which will add the catalog API service name as well. > {code} > /icecli/v1/oauth/tokens/ > {code} > Where "icecli" may be some configurable service name and need to match to the > incoming URL. > We will wildcard that by making it a regex matched path param. > We will also need to accommodate a first-class Knox pattern and service name > of "oauth" and only allow "token" or "oauth" after the v1 with the remaining > path fragment being optional for the iceberg specific "tokens". > Not pretty but it will work. > 1. > https://github.com/apache/iceberg/blob/main/open-api/rest-catalog-open-api.yaml -- This message was sent by Atlassian Jira (v8.20.10#820010)