diff --git a/build.gradle b/build.gradle index ef187e290f..536744d92e 100644 --- a/build.gradle +++ b/build.gradle @@ -424,7 +424,7 @@ dependencies { runtimeOnly 'com.fasterxml.woodstox:woodstox-core:6.4.0' runtimeOnly 'org.apache.ws.xmlschema:xmlschema-core:2.2.5' runtimeOnly 'org.apache.santuario:xmlsec:2.2.3' - runtimeOnly 'com.github.luben:zstd-jni:1.5.2-1' + runtimeOnly 'com.github.luben:zstd-jni:1.5.5-3' runtimeOnly 'org.checkerframework:checker-qual:3.5.0' runtimeOnly "org.bouncycastle:bcpkix-jdk15on:${versions.bouncycastle}" diff --git a/src/integrationTest/java/org/opensearch/security/http/OnBehalfOfJwtAuthenticationTest.java b/src/integrationTest/java/org/opensearch/security/http/OnBehalfOfJwtAuthenticationTest.java new file mode 100644 index 0000000000..82cb286043 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/http/OnBehalfOfJwtAuthenticationTest.java @@ -0,0 +1,76 @@ +package org.opensearch.security.http; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.List; +import java.util.Map; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.test.framework.OnBehalfOfConfig; +import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; +import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; + + +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class OnBehalfOfJwtAuthenticationTest { + + public static final String POINTER_USERNAME = "/user_name"; + + static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); + + public static final String issuer = "cluster_0"; + public static final String subject = "testUser"; + public static final String audience = "audience_0"; + public static final Integer expirySeconds = 100000; + public static final String headerName = "Authorization"; + public static final List roles = List.of("admin", "HR"); + + private static final String signingKey = Base64.getEncoder().encodeToString("jwt signing key for an on behalf of token authentication backend for testing of extensions".getBytes(StandardCharsets.UTF_8)); + private static final String encryptionKey = Base64.getEncoder().encodeToString("encryptionKey".getBytes(StandardCharsets.UTF_8)); + + private static final OnBehalfOfJwtAuthorizationHeaderFactory tokenFactory = new OnBehalfOfJwtAuthorizationHeaderFactory( + signingKey, + issuer, + subject, + audience, + roles, + expirySeconds, + headerName, + encryptionKey + ); + + @ClassRule + public static final LocalCluster cluster = new LocalCluster.Builder() + .clusterManager(ClusterManager.SINGLENODE).anonymousAuth(false) + .nodeSettings(Map.of("plugins.security.restapi.roles_enabled", List.of("user_" + ADMIN_USER.getName() +"__" + ALL_ACCESS.getName()))) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .onBehalfOf(new OnBehalfOfConfig().signing_key(signingKey).encryption_key(encryptionKey)) + .build(); + + @Test + public void shouldAuthenticateWithJwtToken_positive() { + // TODO: This integration test should use an endpoint to get an OnBehalfOf token, not generate it + try(TestRestClient client = cluster.getRestClient(tokenFactory.generateValidToken())){ + + TestRestClient.HttpResponse response = client.getAuthInfo(); + + response.assertStatusCode(200); + String username = response.getTextFromJsonBody(POINTER_USERNAME); + assertThat("testUser", equalTo(username)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/security/http/OnBehalfOfJwtAuthorizationHeaderFactory.java b/src/integrationTest/java/org/opensearch/security/http/OnBehalfOfJwtAuthorizationHeaderFactory.java new file mode 100644 index 0000000000..108806cd24 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/http/OnBehalfOfJwtAuthorizationHeaderFactory.java @@ -0,0 +1,58 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + */ +package org.opensearch.security.http; + +import java.util.List; +import java.util.function.LongSupplier; + +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.message.BasicHeader; + +import org.opensearch.common.settings.Settings; +import org.opensearch.security.authtoken.jwt.JwtVendor; + +import static java.util.Objects.requireNonNull; + +class OnBehalfOfJwtAuthorizationHeaderFactory { + + private final String issuer; + private final String subject; + private final String audience; + private final List roles; + private final String encryption_key; + private final String signing_key; + private final String headerName; + private final Integer expirySeconds; + + + public OnBehalfOfJwtAuthorizationHeaderFactory(String signing_key, String issuer, String subject, String audience, List roles ,Integer expirySeconds, String headerName, String encryption_key) { + this.signing_key = requireNonNull(signing_key, "Signing key is required"); + this.issuer = requireNonNull(issuer, "Issuer is required"); + this.subject = requireNonNull(subject, "Subject is required"); + this.audience = requireNonNull(audience, "Audience is required."); + this.roles = requireNonNull(roles, "Roles claim is required"); + this.expirySeconds = requireNonNull(expirySeconds, "Expiry is required"); + this.headerName = requireNonNull(headerName, "Header name is required"); + this.encryption_key = encryption_key; + } + + Header generateValidToken() throws Exception { + LongSupplier currentTime = () -> (System.currentTimeMillis() / 1000); + Settings settings = Settings.builder().put("signing_key", signing_key).put("encryption_key", encryption_key).build(); + JwtVendor jwtVendor = new JwtVendor(settings, currentTime); + String encodedJwt = jwtVendor.createJwt(issuer, subject, audience, expirySeconds, roles); + + return toHeader(encodedJwt); + } + + private BasicHeader toHeader(String token) { + return new BasicHeader(headerName, "Bearer " + token); + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/OnBehalfOfConfig.java b/src/integrationTest/java/org/opensearch/test/framework/OnBehalfOfConfig.java new file mode 100644 index 0000000000..cf9be42114 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/test/framework/OnBehalfOfConfig.java @@ -0,0 +1,44 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + */ +package org.opensearch.test.framework; + +import java.io.IOException; + +import org.apache.commons.lang3.StringUtils; + +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; + +public class OnBehalfOfConfig implements ToXContentObject { + private String signing_key; + private String encryption_key; + + public OnBehalfOfConfig signing_key(String signing_key) { + this.signing_key = signing_key; + return this; + } + + public OnBehalfOfConfig encryption_key(String encryption_key) { + this.encryption_key = encryption_key; + return this; + } + + @Override + public XContentBuilder toXContent(XContentBuilder xContentBuilder, ToXContent.Params params) throws IOException { + xContentBuilder.startObject(); + xContentBuilder.field("signing_key", signing_key); + if (StringUtils.isNoneBlank(encryption_key)){ + xContentBuilder.field("encryption_key", encryption_key); + } + xContentBuilder.endObject(); + return xContentBuilder; + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java index 43b98b02ce..9ab1e70e8c 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java +++ b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java @@ -114,6 +114,11 @@ public TestSecurityConfig xff(XffConfig xffConfig) { config.xffConfig(xffConfig); return this; } + + public TestSecurityConfig onBehalfOf(OnBehalfOfConfig onBehalfOfConfig){ + config.onBehalfOfConfig(onBehalfOfConfig); + return this; + } public TestSecurityConfig authc(AuthcDomain authcDomain) { config.authc(authcDomain); @@ -170,6 +175,7 @@ public static class Config implements ToXContentObject { private Boolean doNotFailOnForbidden; private XffConfig xffConfig; + private OnBehalfOfConfig onBehalfOfConfig; private Map authcDomainMap = new LinkedHashMap<>(); private AuthFailureListeners authFailureListeners; @@ -190,6 +196,11 @@ public Config xffConfig(XffConfig xffConfig) { return this; } + public Config onBehalfOfConfig(OnBehalfOfConfig onBehalfOfConfig) { + this.onBehalfOfConfig = onBehalfOfConfig; + return this; + } + public Config authc(AuthcDomain authcDomain) { authcDomainMap.put(authcDomain.id, authcDomain); return this; @@ -210,6 +221,10 @@ public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params xContentBuilder.startObject(); xContentBuilder.startObject("dynamic"); + if (onBehalfOfConfig != null) { + xContentBuilder.field("on_behalf_of", onBehalfOfConfig); + } + if (anonymousAuth || (xffConfig != null)) { xContentBuilder.startObject("http"); xContentBuilder.field("anonymous_auth_enabled", anonymousAuth); diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java index e9bb7b5be5..13465294e5 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java @@ -55,6 +55,7 @@ import org.opensearch.test.framework.AuditConfiguration; import org.opensearch.test.framework.AuthFailureListeners; import org.opensearch.test.framework.AuthzDomain; +import org.opensearch.test.framework.OnBehalfOfConfig; import org.opensearch.test.framework.RolesMapping; import org.opensearch.test.framework.TestIndex; import org.opensearch.test.framework.TestSecurityConfig; @@ -443,6 +444,11 @@ public Builder xff(XffConfig xffConfig){ return this; } + public Builder onBehalfOf(OnBehalfOfConfig onBehalfOfConfig){ + testSecurityConfig.onBehalfOf(onBehalfOfConfig); + return this; + } + public Builder loadConfigurationIntoIndex(boolean loadConfigurationIntoIndex) { this.loadConfigurationIntoIndex = loadConfigurationIntoIndex; return this; diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 9bf13957da..a756ce0b5d 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -117,6 +117,7 @@ import org.opensearch.search.query.QuerySearchResult; import org.opensearch.security.action.configupdate.ConfigUpdateAction; import org.opensearch.security.action.configupdate.TransportConfigUpdateAction; +import org.opensearch.security.action.onbehalf.CreateOnBehalfOfToken; import org.opensearch.security.action.whoami.TransportWhoAmIAction; import org.opensearch.security.action.whoami.WhoAmIAction; import org.opensearch.security.auditlog.AuditLog; @@ -141,7 +142,7 @@ import org.opensearch.security.dlic.rest.validation.PasswordValidator; import org.opensearch.security.filter.SecurityFilter; import org.opensearch.security.filter.SecurityRestFilter; -import org.opensearch.security.http.HTTPOnBehalfOfJwtAuthenticator; +import org.opensearch.security.http.OnBehalfOfAuthenticator; import org.opensearch.security.http.SecurityHttpServerTransport; import org.opensearch.security.http.SecurityNonSslHttpServerTransport; import org.opensearch.security.http.XFFResolver; @@ -477,6 +478,7 @@ public List getRestHandlers(Settings settings, RestController restC Objects.requireNonNull(cs), Objects.requireNonNull(adminDns), Objects.requireNonNull(cr))); handlers.add(new SecurityConfigUpdateAction(settings, restController, Objects.requireNonNull(threadPool), adminDns, configPath, principalExtractor)); handlers.add(new SecurityWhoAmIAction(settings, restController, Objects.requireNonNull(threadPool), adminDns, configPath, principalExtractor)); + handlers.add(new CreateOnBehalfOfToken(settings, threadPool)); handlers.addAll( SecurityRestApiActions.getHandler( settings, @@ -838,9 +840,6 @@ public Collection createComponents(Client localClient, ClusterService cl securityRestHandler = new SecurityRestFilter(backendRegistry, auditLog, threadPool, principalExtractor, settings, configPath, compatConfig); - - HTTPOnBehalfOfJwtAuthenticator acInstance = new HTTPOnBehalfOfJwtAuthenticator(); - final DynamicConfigFactory dcf = new DynamicConfigFactory(cr, settings, configPath, localClient, threadPool, cih); dcf.registerDCFListener(backendRegistry); dcf.registerDCFListener(compatConfig); @@ -848,7 +847,6 @@ public Collection createComponents(Client localClient, ClusterService cl dcf.registerDCFListener(xffResolver); dcf.registerDCFListener(evaluator); dcf.registerDCFListener(securityRestHandler); - dcf.registerDCFListener(acInstance); if (!(auditLog instanceof NullAuditLog)) { // Don't register if advanced modules is disabled in which case auditlog is instance of NullAuditLog dcf.registerDCFListener(auditLog); diff --git a/src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfToken.java b/src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfToken.java new file mode 100644 index 0000000000..219594ad98 --- /dev/null +++ b/src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfToken.java @@ -0,0 +1,155 @@ +package org.opensearch.security.action.onbehalf; + +import java.io.IOException; +import java.util.List; + +import org.apache.cxf.rs.security.jose.jwt.JwtToken; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.action.FailedNodeException; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.nodes.TransportNodesAction; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.inject.Inject; +import org.opensearch.common.inject.Provider; +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.common.settings.Settings; +import org.opensearch.security.auth.BackendRegistry; +import org.opensearch.security.authtoken.jwt.JwtVendor; +import org.opensearch.security.configuration.ConfigurationRepository; +import org.opensearch.security.securityconf.DynamicConfigFactory; +import org.opensearch.security.securityconf.impl.CType; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.TransportRequest; +import org.opensearch.transport.TransportService; +import org.opensearch.rest.BaseRestHandler; + +import java.io.IOException; +import java.nio.file.Path; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.client.Client; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.bytes.BytesReference; +import org.opensearch.common.settings.Settings; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.rest.BytesRestResponse; +import org.opensearch.rest.RestChannel; +import org.opensearch.rest.RestController; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.RestRequest.Method; +import org.opensearch.rest.RestStatus; +import org.opensearch.security.auditlog.AuditLog; +import org.opensearch.security.configuration.AdminDNs; +import org.opensearch.security.configuration.ConfigurationRepository; +import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; +import org.opensearch.security.privileges.PrivilegesEvaluator; +import org.opensearch.security.securityconf.impl.CType; +import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; +import org.opensearch.security.ssl.SecurityKeyStore; +import org.opensearch.security.ssl.transport.PrincipalExtractor; +import org.opensearch.security.ssl.util.SSLConfigConstants; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.client.node.NodeClient; +import org.opensearch.security.user.User; + +import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; + +public class CreateOnBehalfOfToken extends BaseRestHandler { + + private final JwtVendor vendor; + private final ThreadPool threadPool; + + public CreateOnBehalfOfToken(final Settings settings, final ThreadPool threadPool) { + Settings testSettings = Settings.builder() + .put("signing_key", "1234567890123456") + .put("encryption_key", "1234567890123456").build(); + + this.vendor = new JwtVendor(testSettings, Optional.empty()); + this.threadPool = threadPool; + } + + @Override + public String getName() { + return getClass().getSimpleName(); + } + + @Override + public List routes() { + return addRoutesPrefix( + ImmutableList.of( + new Route(Method.POST, "/user/onbehalfof") + ) + ); + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + switch (request.method()) { + case POST: + return handlePost(request, client); + default: + throw new IllegalArgumentException(request.method() + " not supported"); + } + } + + private RestChannelConsumer handlePost(RestRequest request, NodeClient client) throws IOException { + return new RestChannelConsumer() { + @Override + public void accept(RestChannel channel) throws Exception { + final XContentBuilder builder = channel.newBuilder(); + BytesRestResponse response; + try { + final Map requestBody = request.contentOrSourceParamParser().map(); + final String reason = (String)requestBody.getOrDefault("reason", null); + + final Integer tokenDuration = Optional.ofNullable(requestBody.get("duration")) + .map(value -> (String)value) + .map(Integer::parseInt) + .map(value -> Math.min(value, 72)) // Max duration is 72 hours + .orElse(24); // Fallback to default; + + final String source = "self-issued"; + final User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + + builder.startObject(); + builder.field("user", user.getName()); + final String token = vendor.createJwt(/* TODO: Update the issuer to represent the cluster */"OpenSearch", + user.getName(), + source, + tokenDuration, + user.getSecurityRoles().stream().collect(Collectors.toList())); + builder.field("onBehalfOfToken", token); + builder.field("duration", tokenDuration); + builder.endObject(); + + response = new BytesRestResponse(RestStatus.OK, builder); + } catch (final Exception exception) { + System.out.println(exception.toString()); + builder.startObject() + .field("error", exception.toString()) + .endObject(); + + response = new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder); + } + builder.close(); + channel.sendResponse(response); + } + }; + } + +} diff --git a/src/main/java/org/opensearch/security/auth/BackendRegistry.java b/src/main/java/org/opensearch/security/auth/BackendRegistry.java index a36aced5e1..635811a7ae 100644 --- a/src/main/java/org/opensearch/security/auth/BackendRegistry.java +++ b/src/main/java/org/opensearch/security/auth/BackendRegistry.java @@ -224,8 +224,6 @@ public boolean authenticate(final RestRequest request, final RestChannel channel HTTPAuthenticator firstChallengingHttpAuthenticator = null; - //TODO: ADD OUR AUTHC BACKEND IN/BEFORE THIS LIST - //loop over all http/rest auth domains for (final AuthDomain authDomain: restAuthDomains) { if (isDebugEnabled) { diff --git a/src/main/java/org/opensearch/security/authtoken/jwt/EncryptionDecryptionUtil.java b/src/main/java/org/opensearch/security/authtoken/jwt/EncryptionDecryptionUtil.java index 16d1248820..c0e2429948 100644 --- a/src/main/java/org/opensearch/security/authtoken/jwt/EncryptionDecryptionUtil.java +++ b/src/main/java/org/opensearch/security/authtoken/jwt/EncryptionDecryptionUtil.java @@ -22,35 +22,43 @@ public class EncryptionDecryptionUtil { public static String encrypt(final String secret, final String data) { + final Cipher cipher = createCipherFromSecret(secret, CipherMode.ENCRYPT); + final byte[] cipherText = createCipherText(cipher, data.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(cipherText); + } - byte[] decodedKey = Base64.getDecoder().decode(secret); + public static String decrypt(final String secret, final String encryptedString) { + final Cipher cipher = createCipherFromSecret(secret, CipherMode.DECRYPT); + final byte[] cipherText = createCipherText(cipher, Base64.getDecoder().decode(encryptedString)); + return new String(cipherText, StandardCharsets.UTF_8); + } + private static Cipher createCipherFromSecret(final String secret, final CipherMode mode) { try { - Cipher cipher = Cipher.getInstance("AES"); - // rebuild key using SecretKeySpec - SecretKey originalKey = new SecretKeySpec(Arrays.copyOf(decodedKey, 16), "AES"); - cipher.init(Cipher.ENCRYPT_MODE, originalKey); - byte[] cipherText = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)); - return Base64.getEncoder().encodeToString(cipherText); - } catch (Exception e) { - throw new RuntimeException( - "Error occured while encrypting data", e); + final byte[] decodedKey = Base64.getDecoder().decode(secret); + final Cipher cipher = Cipher.getInstance("AES"); + final SecretKey originalKey = new SecretKeySpec(Arrays.copyOf(decodedKey, 16), "AES"); + cipher.init(mode.opmode, originalKey); + return cipher; + } catch (final Exception e) { + throw new RuntimeException("Error creating cipher from secret in mode " + mode.name()); } } - - public static String decrypt(final String secret, final String encryptedString) { - - byte[] decodedKey = Base64.getDecoder().decode(secret); - + + private static byte[] createCipherText(final Cipher cipher, final byte[] data) { try { - Cipher cipher = Cipher.getInstance("AES"); - // rebuild key using SecretKeySpec - SecretKey originalKey = new SecretKeySpec(Arrays.copyOf(decodedKey, 16), "AES"); - cipher.init(Cipher.DECRYPT_MODE, originalKey); - byte[] cipherText = cipher.doFinal(Base64.getDecoder().decode(encryptedString)); - return new String(cipherText, StandardCharsets.UTF_8); - } catch (Exception e) { - throw new RuntimeException("Error occured while decrypting data", e); + return cipher.doFinal(data); + } catch (final Exception e) { + throw new RuntimeException("The cipher was unable to perform pass over data"); + } + } + + private enum CipherMode { + ENCRYPT(Cipher.ENCRYPT_MODE), + DECRYPT(Cipher.DECRYPT_MODE); + private final int opmode; + private CipherMode(final int opmode) { + this.opmode = opmode; } } } diff --git a/src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java b/src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java index 5328453c98..1587dcab8c 100644 --- a/src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java +++ b/src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java @@ -12,6 +12,7 @@ package org.opensearch.security.authtoken.jwt; import java.time.Instant; +import java.util.Optional; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -28,12 +29,12 @@ import org.apache.cxf.rs.security.jose.jwt.JwtClaims; import org.apache.cxf.rs.security.jose.jwt.JwtToken; import org.apache.cxf.rs.security.jose.jwt.JwtUtils; +import org.apache.kafka.common.utils.SystemTime; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.common.settings.Settings; import org.opensearch.common.transport.TransportAddress; -import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.security.securityconf.ConfigModel; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.user.User; @@ -44,16 +45,15 @@ public class JwtVendor { private static JsonMapObjectReaderWriter jsonMapReaderWriter = new JsonMapObjectReaderWriter(); - private String claimsEncryptionKey; - private JsonWebKey signingKey; - private JoseJwtProducer jwtProducer; + private final String claimsEncryptionKey; + private final JsonWebKey signingKey; + private final JoseJwtProducer jwtProducer; private final LongSupplier timeProvider; //TODO: Relocate/Remove them at once we make the descisions about the `roles` - private ConfigModel configModel; - private ThreadContext threadContext; + private ConfigModel configModel; // This never gets assigned, how does this work at all? - public JwtVendor(Settings settings) { + public JwtVendor(final Settings settings, final Optional timeProvider) { JoseJwtProducer jwtProducer = new JoseJwtProducer(); try { this.signingKey = createJwkFromSettings(settings); @@ -66,24 +66,8 @@ public JwtVendor(Settings settings) { } else { this.claimsEncryptionKey = settings.get("encryption_key"); } - timeProvider = System::currentTimeMillis; - } - - //For testing the expiration in the future - public JwtVendor(Settings settings, final LongSupplier timeProvider) { - JoseJwtProducer jwtProducer = new JoseJwtProducer(); - try { - this.signingKey = createJwkFromSettings(settings); - } catch (Exception e) { - throw new RuntimeException(e); - } - this.jwtProducer = jwtProducer; - if (settings.get("encryption_key") == null) { - throw new RuntimeException("encryption_key cannot be null"); - } else { - this.claimsEncryptionKey = settings.get("encryption_key"); - } - this.timeProvider = timeProvider; + this.timeProvider = timeProvider.orElseGet(() -> System::currentTimeMillis); + this.configModel = null; } /* @@ -123,21 +107,6 @@ static JsonWebKey createJwkFromSettings(Settings settings) throws Exception { } } - //TODO:Getting roles from User - public Map prepareClaimsForUser(User user, ThreadPool threadPool) { - Map claims = new HashMap<>(); - this.threadContext = threadPool.getThreadContext(); - final TransportAddress caller = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS); - Set mappedRoles = mapRoles(user, caller); - claims.put("sub", user.getName()); - claims.put("roles", String.join(",", mappedRoles)); - return claims; - } - - public Set mapRoles(final User user, final TransportAddress caller) { - return this.configModel.mapSecurityRoles(user, caller); - } - public String createJwt(String issuer, String subject, String audience, Integer expirySeconds, List roles) throws Exception { long timeMillis = timeProvider.getAsLong(); Instant now = Instant.ofEpochMilli(timeProvider.getAsLong()); diff --git a/src/main/java/org/opensearch/security/http/HTTPOnBehalfOfJwtAuthenticator.java b/src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java similarity index 58% rename from src/main/java/org/opensearch/security/http/HTTPOnBehalfOfJwtAuthenticator.java rename to src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java index 1fabd0874c..eb5fa434df 100644 --- a/src/main/java/org/opensearch/security/http/HTTPOnBehalfOfJwtAuthenticator.java +++ b/src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java @@ -20,6 +20,7 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; +import java.util.Objects; import java.util.Map.Entry; import java.util.regex.Pattern; @@ -28,86 +29,72 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.WeakKeyException; -import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.ObjectUtils; import org.apache.hc.core5.http.HttpHeaders; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.greenrobot.eventbus.Subscribe; import org.opensearch.OpenSearchSecurityException; import org.opensearch.SpecialPermission; +import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.rest.RestChannel; import org.opensearch.rest.RestRequest; import org.opensearch.security.auth.HTTPAuthenticator; import org.opensearch.security.authtoken.jwt.EncryptionDecryptionUtil; -import org.opensearch.security.securityconf.DynamicConfigModel; import org.opensearch.security.user.AuthCredentials; -public class HTTPOnBehalfOfJwtAuthenticator implements HTTPAuthenticator { +public class OnBehalfOfAuthenticator implements HTTPAuthenticator { protected final Logger log = LogManager.getLogger(this.getClass()); private static final Pattern BEARER = Pattern.compile("^\\s*Bearer\\s.*", Pattern.CASE_INSENSITIVE); private static final String BEARER_PREFIX = "bearer "; + private static final String SUBJECT_CLAIM = "sub"; - //TODO: TO SEE IF WE NEED THE FINAL FOR FOLLOWING - private JwtParser jwtParser; - private String subjectKey; + private final JwtParser jwtParser; + private final String encryptionKey; - private String signingKey; - private String encryptionKey; - - public HTTPOnBehalfOfJwtAuthenticator() { - super(); - init(); - } - - // FOR TESTING - public HTTPOnBehalfOfJwtAuthenticator(String signingKey, String encryptionKey){ - this.signingKey = signingKey; - this.encryptionKey = encryptionKey; - init(); + public OnBehalfOfAuthenticator(Settings settings) { + encryptionKey = settings.get("encryption_key"); + jwtParser = initParser(settings.get("signing_key")); } - private void init() { + private JwtParser initParser(final String signingKey) { + if (signingKey == null || signingKey.length() == 0) { + throw new RuntimeException("Unable to find on behalf of authenticator signing key"); + } try { - if(signingKey == null || signingKey.length() == 0) { - log.error("signingKey must not be null or empty. JWT authentication will not work"); - } else { + final String minmalKeyFormat = signingKey + .replace("-----BEGIN PUBLIC KEY-----\n", "") + .replace("-----END PUBLIC KEY-----", ""); - signingKey = signingKey.replace("-----BEGIN PUBLIC KEY-----\n", ""); - signingKey = signingKey.replace("-----END PUBLIC KEY-----", ""); + final byte[] decoded = Decoders.BASE64.decode(minmalKeyFormat); + Key key = null; - byte[] decoded = Decoders.BASE64.decode(signingKey); - Key key = null; - - try { - key = getPublicKey(decoded, "RSA"); - } catch (Exception e) { - log.debug("No public RSA key, try other algos ({})", e.toString()); - } - - try { - key = getPublicKey(decoded, "EC"); - } catch (Exception e) { - log.debug("No public ECDSA key, try other algos ({})", e.toString()); - } + try { + key = getPublicKey(decoded, "RSA"); + } catch (Exception e) { + log.debug("No public RSA key, try other algos ({})", e.toString()); + } - if(key != null) { - jwtParser = Jwts.parser().setSigningKey(key); - } else { - jwtParser = Jwts.parser().setSigningKey(decoded); - } + try { + key = getPublicKey(decoded, "EC"); + } catch (Exception e) { + log.debug("No public ECDSA key, try other algos ({})", e.toString()); + } + if (Objects.nonNull(key)) { + return Jwts.parser().setSigningKey(key); } + // Fallback to the decoded signing key + // TODO: Should we ever do this, I think no?? + return Jwts.parser().setSigningKey(decoded); } catch (Throwable e) { log.error("Error while creating JWT authenticator", e); throw new RuntimeException(e); } - - subjectKey = "sub"; } @Override @@ -149,7 +136,7 @@ private AuthCredentials extractCredentials0(final RestRequest request) { } final int index; - if((index = jwtToken.toLowerCase().indexOf(BEARER_PREFIX)) > -1) { //detect Bearer + if(jwtToken != null && (index = jwtToken.toLowerCase().indexOf(BEARER_PREFIX)) > -1) { //detect Bearer jwtToken = jwtToken.substring(index+BEARER_PREFIX.length()); } else { if(log.isDebugEnabled()) { @@ -160,25 +147,21 @@ private AuthCredentials extractCredentials0(final RestRequest request) { try { final Claims claims = jwtParser.parseClaimsJws(jwtToken).getBody(); - final String subject = extractSubject(claims, request); + final String subject = claims.getSubject(); + if (Objects.isNull(subject)) { + log.error("Valid jwt on behalf of token with no subject"); + return null; + } final String audience = claims.getAudience(); + if (Objects.isNull(subject)) { + log.error("Valid jwt on behalf of token with no audience"); + return null; + } - //TODO: GET ROLESCLAIM DEPENDING ON THE STATUS OF BWC MODE. ON: er / OFF: dr - Object rolesObject = null; String[] roles; - try { - rolesObject = claims.get("er"); - } catch (Throwable e) { - log.debug("No encrypted role founded in the claim, continue searching for decrypted roles."); - } - - try { - rolesObject = claims.get("dr"); - } catch (Throwable e) { - log.debug("No decrypted role founded in the claim."); - } + Object rolesObject = ObjectUtils.firstNonNull(claims.get("er"), claims.get("dr")); if (rolesObject == null) { log.warn( @@ -190,21 +173,11 @@ private AuthCredentials extractCredentials0(final RestRequest request) { // Extracting roles based on the compatbility mode String decryptedRoles = rolesClaim; if (rolesObject == claims.get("er")) { - //TODO: WHERE TO GET THE ENCRYTION KEY decryptedRoles = EncryptionDecryptionUtil.decrypt(encryptionKey, rolesClaim); } roles = Arrays.stream(decryptedRoles.split(",")).map(String::trim).toArray(String[]::new); } - if (subject == null) { - log.error("No subject found in JWT token"); - return null; - } - - if (audience == null) { - log.error("No audience found in JWT token"); - } - final AuthCredentials ac = new AuthCredentials(subject, roles).markComplete(); for(Entry claim: claims.entrySet()) { @@ -214,9 +187,12 @@ private AuthCredentials extractCredentials0(final RestRequest request) { return ac; } catch (WeakKeyException e) { + System.out.println("Error MSG1!" + e.getMessage()); log.error("Cannot authenticate user with JWT because of ", e); return null; } catch (Exception e) { + System.out.println("Error MSG2!" + e.getMessage()); + e.printStackTrace(); if(log.isDebugEnabled()) { log.debug("Invalid or expired JWT token.", e); } @@ -234,39 +210,10 @@ public String getType() { return "onbehalfof_jwt"; } - //TODO: Extract the audience (ext_id) and inject it into thread context - - protected String extractSubject(final Claims claims, final RestRequest request) { - String subject = claims.getSubject(); - if(subjectKey != null) { - // try to get roles from claims, first as Object to avoid having to catch the ExpectedTypeException - Object subjectObject = claims.get(subjectKey, Object.class); - if(subjectObject == null) { - log.warn("Failed to get subject from JWT claims, check if subject_key '{}' is correct.", subjectKey); - return null; - } - // We expect a String. If we find something else, convert to String but issue a warning - if(!(subjectObject instanceof String)) { - log.warn("Expected type String in the JWT for subject_key {}, but value was '{}' ({}). Will convert this value to String.", subjectKey, subjectObject, subjectObject.getClass()); - } - subject = String.valueOf(subjectObject); - } - return subject; - } - private static PublicKey getPublicKey(final byte[] keyBytes, final String algo) throws NoSuchAlgorithmException, InvalidKeySpecException { X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); KeyFactory kf = KeyFactory.getInstance(algo); return kf.generatePublic(spec); } - @Subscribe - public void onDynamicConfigModelChanged(DynamicConfigModel dcm) { - - //TODO: #2615 FOR CONFIGURATION - //For Testing - signingKey = "abcd1234"; - encryptionKey = RandomStringUtils.randomAlphanumeric(16); - } - } diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java index c3e3792c5c..254b82510d 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java @@ -54,6 +54,8 @@ import org.opensearch.security.auth.HTTPAuthenticator; import org.opensearch.security.auth.blocking.ClientBlockRegistry; import org.opensearch.security.auth.internal.InternalAuthenticationBackend; +import org.opensearch.security.auth.internal.NoOpAuthenticationBackend; +import org.opensearch.security.http.OnBehalfOfAuthenticator; import org.opensearch.security.securityconf.impl.v7.ConfigV7; import org.opensearch.security.securityconf.impl.v7.ConfigV7.Authc; import org.opensearch.security.securityconf.impl.v7.ConfigV7.AuthcDomain; @@ -312,6 +314,12 @@ private void buildAAA() { } } + Settings oboSettings = getDynamicOnBehalfOfSettings(); + if (oboSettings.get("signing_key") != null && oboSettings.get("encryption_key") != null) { + final AuthDomain _ad = new AuthDomain(new NoOpAuthenticationBackend(Settings.EMPTY, null), new OnBehalfOfAuthenticator(getDynamicOnBehalfOfSettings()), false, -1); + restAuthDomains0.add(_ad); + } + List originalDestroyableComponents = destroyableComponents; restAuthDomains = Collections.unmodifiableSortedSet(restAuthDomains0); diff --git a/src/test/java/org/opensearch/security/authtoken/jwt/JwtVendorTest.java b/src/test/java/org/opensearch/security/authtoken/jwt/JwtVendorTest.java index 3330477721..2ea22ffbec 100644 --- a/src/test/java/org/opensearch/security/authtoken/jwt/JwtVendorTest.java +++ b/src/test/java/org/opensearch/security/authtoken/jwt/JwtVendorTest.java @@ -11,7 +11,10 @@ package org.opensearch.security.authtoken.jwt; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.List; +import java.util.Optional; import java.util.function.LongSupplier; import org.apache.commons.lang3.RandomStringUtils; @@ -23,6 +26,8 @@ import org.opensearch.common.settings.Settings; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class JwtVendorTest { @Test @@ -56,7 +61,7 @@ public void testCreateJwtWithRoles() throws Exception { Settings settings = Settings.builder().put("signing_key", "abc123").put("encryption_key", claimsEncryptionKey).build(); Long expectedExp = currentTime.getAsLong() + (expirySeconds * 1000); - JwtVendor jwtVendor = new JwtVendor(settings, currentTime); + JwtVendor jwtVendor = new JwtVendor(settings, Optional.of(currentTime)); String encodedJwt = jwtVendor.createJwt(issuer, subject, audience, expirySeconds, roles); JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer(encodedJwt); @@ -82,7 +87,7 @@ public void testCreateJwtWithBadExpiry() throws Exception { String claimsEncryptionKey = RandomStringUtils.randomAlphanumeric(16); Settings settings = Settings.builder().put("signing_key", "abc123").put("encryption_key", claimsEncryptionKey).build(); - JwtVendor jwtVendor = new JwtVendor(settings); + JwtVendor jwtVendor = new JwtVendor(settings, Optional.empty()); jwtVendor.createJwt(issuer, subject, audience, expirySeconds, roles); } @@ -96,7 +101,7 @@ public void testCreateJwtWithBadEncryptionKey() throws Exception { Integer expirySeconds = 300; Settings settings = Settings.builder().put("signing_key", "abc123").build(); - JwtVendor jwtVendor = new JwtVendor(settings); + JwtVendor jwtVendor = new JwtVendor(settings, Optional.empty()); jwtVendor.createJwt(issuer, subject, audience, expirySeconds, roles); } @@ -112,8 +117,34 @@ public void testCreateJwtWithBadRoles() throws Exception { Settings settings = Settings.builder().put("signing_key", "abc123").put("encryption_key", claimsEncryptionKey).build(); - JwtVendor jwtVendor = new JwtVendor(settings); + JwtVendor jwtVendor = new JwtVendor(settings, Optional.empty()); jwtVendor.createJwt(issuer, subject, audience, expirySecond, roles); } + + //For Manual Testing + @Test + public void testCreateJwtWithRolesAndEncyptionKey() throws Exception { + String issuer = "cluster_0"; + String subject = "craig"; + String audience = "audience_0"; + List roles = List.of("admin", "HR"); + Integer expirySeconds = 10000; + LongSupplier currentTime = () -> (System.currentTimeMillis() / 1000); + String claimsEncryptionKey = RandomStringUtils.randomAlphanumeric(16); + String signingKey = Base64.getEncoder().encodeToString("jwt signing key for an on behalf of token authentication backend for testing of extensions".getBytes(StandardCharsets.UTF_8)); + String encryptionKey = Base64.getEncoder().encodeToString("encryptionKey".getBytes(StandardCharsets.UTF_8)); + System.out.println("The encryptionkey is:" + encryptionKey); + Settings settings = Settings.builder().put("signing_key", signingKey).put("encryption_key", encryptionKey).build(); + + JwtVendor jwtVendor = new JwtVendor(settings, Optional.of(currentTime)); + String encodedJwt = jwtVendor.createJwt(issuer, subject, audience, expirySeconds, roles); + + JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer(encodedJwt); + JwtToken jwt = jwtConsumer.getJwtToken(); + + System.out.println("JWT: " + encodedJwt); + + assertTrue(true); + } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java index 9f6bcb65c9..c323b15e6c 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java @@ -39,7 +39,7 @@ public AccountApiTest(){ BASE_ENDPOINT = getEndpointPrefix() + "/api/"; ENDPOINT = getEndpointPrefix() + "/api/account"; } - + @Test public void testGetAccount() throws Exception { // arrange @@ -73,6 +73,9 @@ public void testGetAccount() throws Exception { assertNotNull(body.getAsList("custom_attribute_names").size()); assertNotNull(body.getAsSettings("tenants")); assertNotNull(body.getAsList("roles")); + + response = rh.executePostRequest(getEndpointPrefix() + "/api/user/onbehalfof", "{\"reason\":\"Test generation\"}", encodeBasicHeader(testUser, testPass)); + System.out.println(response.getBody()); } @Test diff --git a/src/test/java/org/opensearch/security/http/HTTPOnBehalfOfJwtAuthenticatorTest.java b/src/test/java/org/opensearch/security/http/OnBehalfOfAuthenticatorTest.java similarity index 87% rename from src/test/java/org/opensearch/security/http/HTTPOnBehalfOfJwtAuthenticatorTest.java rename to src/test/java/org/opensearch/security/http/OnBehalfOfAuthenticatorTest.java index 5a6c5ec41c..f1d5d87222 100644 --- a/src/test/java/org/opensearch/security/http/HTTPOnBehalfOfJwtAuthenticatorTest.java +++ b/src/test/java/org/opensearch/security/http/OnBehalfOfAuthenticatorTest.java @@ -31,13 +31,11 @@ import org.junit.Assert; import org.junit.Test; -import com.amazon.dlic.auth.http.jwt.HTTPJwtAuthenticator; - import org.opensearch.common.settings.Settings; import org.opensearch.security.user.AuthCredentials; import org.opensearch.security.util.FakeRestRequest; -public class HTTPOnBehalfOfJwtAuthenticatorTest { +public class OnBehalfOfAuthenticatorTest { final static byte[] secretKeyBytes = new byte[1024]; final static String claimsEncryptionKey = RandomStringUtils.randomAlphanumeric(16); final static SecretKey secretKey; @@ -46,9 +44,9 @@ public class HTTPOnBehalfOfJwtAuthenticatorTest { new SecureRandom().nextBytes(secretKeyBytes); secretKey = Keys.hmacShaKeyFor(secretKeyBytes); } - final static String signingKey = BaseEncoding.base64().encode(secretKeyBytes); + @Test public void testNoKey() throws Exception { @@ -88,7 +86,7 @@ public void testBadKey() throws Exception { @Test public void testTokenMissing() throws Exception { - HTTPOnBehalfOfJwtAuthenticator jwtAuth = new HTTPOnBehalfOfJwtAuthenticator(BaseEncoding.base64().encode(secretKeyBytes),claimsEncryptionKey); + OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(defaultSettings()); Map headers = new HashMap(); AuthCredentials credentials = jwtAuth.extractCredentials(new FakeRestRequest(headers, new HashMap()), null); @@ -101,7 +99,7 @@ public void testInvalid() throws Exception { String jwsToken = "123invalidtoken.."; - HTTPOnBehalfOfJwtAuthenticator jwtAuth = new HTTPOnBehalfOfJwtAuthenticator(BaseEncoding.base64().encode(secretKeyBytes), claimsEncryptionKey); + OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(defaultSettings()); Map headers = new HashMap(); headers.put("Authorization", "Bearer "+jwsToken); @@ -114,7 +112,7 @@ public void testBearer() throws Exception { String jwsToken = Jwts.builder().setSubject("Leonard McCoy").setAudience("ext_0").signWith(secretKey, SignatureAlgorithm.HS512).compact(); - HTTPOnBehalfOfJwtAuthenticator jwtAuth = new HTTPOnBehalfOfJwtAuthenticator(BaseEncoding.base64().encode(secretKeyBytes), claimsEncryptionKey); + OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(defaultSettings()); Map headers = new HashMap(); headers.put("Authorization", "Bearer "+jwsToken); @@ -129,11 +127,9 @@ public void testBearer() throws Exception { @Test public void testBearerWrongPosition() throws Exception { - Settings settings = Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).build(); - - String jwsToken = Jwts.builder().setSubject("Leonard McCoy").signWith(secretKey, SignatureAlgorithm.HS512).compact(); + String jwsToken = Jwts.builder().setSubject("Leonard McCoy").setAudience("ext_0").signWith(secretKey, SignatureAlgorithm.HS512).compact(); + OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(defaultSettings()); - HTTPJwtAuthenticator jwtAuth = new HTTPJwtAuthenticator(settings, null); Map headers = new HashMap(); headers.put("Authorization", jwsToken + "Bearer " + " 123"); @@ -145,11 +141,10 @@ public void testBearerWrongPosition() throws Exception { @Test public void testBasicAuthHeader() throws Exception { - Settings settings = Settings.builder().put("signing_key", BaseEncoding.base64().encode(secretKeyBytes)).build(); - HTTPJwtAuthenticator jwtAuth = new HTTPJwtAuthenticator(settings, null); + String jwsToken = Jwts.builder().setSubject("Leonard McCoy").setAudience("ext_0").signWith(secretKey, SignatureAlgorithm.HS512).compact(); + OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(defaultSettings()); - String basicAuth = BaseEncoding.base64().encode("user:password".getBytes(StandardCharsets.UTF_8)); - Map headers = Collections.singletonMap(HttpHeaders.AUTHORIZATION, "Basic " + basicAuth); + Map headers = Collections.singletonMap(HttpHeaders.AUTHORIZATION, "Basic " + jwsToken); AuthCredentials credentials = jwtAuth.extractCredentials(new FakeRestRequest(headers, Collections.emptyMap()), null); Assert.assertNull(credentials); @@ -280,8 +275,15 @@ private AuthCredentials extractCredentialsFromJwtHeader( final JwtBuilder jwtBuilder, final Boolean bwcPluginCompatibilityMode) { final String jwsToken = jwtBuilder.signWith(secretKey, SignatureAlgorithm.HS512).compact(); - final HTTPOnBehalfOfJwtAuthenticator jwtAuth = new HTTPOnBehalfOfJwtAuthenticator(signingKey, encryptionKey); + final OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(defaultSettings()); final Map headers = Map.of("Authorization", "Bearer " + jwsToken); return jwtAuth.extractCredentials(new FakeRestRequest(headers, new HashMap<>()), null); } + + private Settings defaultSettings() { + return Settings.builder() + .put("signing_key", signingKey) + .put("encryption_key", claimsEncryptionKey) + .build(); + } }