From 1d24b45745ffb693d2ba4ad90c937a7223170dd5 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Thu, 8 Jun 2023 21:32:29 +0000 Subject: [PATCH 01/10] Fix build break Signed-off-by: Peter Nied --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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}" From da94b822efde63164b40fdc62bb207dd9e7b9484 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Thu, 8 Jun 2023 21:33:43 +0000 Subject: [PATCH 02/10] Make authenticator name more concise Signed-off-by: Peter Nied --- .../opensearch/security/OpenSearchSecurityPlugin.java | 6 +++--- ...Authenticator.java => OnBehalfOfAuthenticator.java} | 6 +++--- ...catorTest.java => OnBehalfOfAuthenticatorTest.java} | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) rename src/main/java/org/opensearch/security/http/{HTTPOnBehalfOfJwtAuthenticator.java => OnBehalfOfAuthenticator.java} (97%) rename src/test/java/org/opensearch/security/http/{HTTPOnBehalfOfJwtAuthenticatorTest.java => OnBehalfOfAuthenticatorTest.java} (94%) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 9bf13957da..0a0726c0a7 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -141,7 +141,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; @@ -839,7 +839,7 @@ public Collection createComponents(Client localClient, ClusterService cl securityRestHandler = new SecurityRestFilter(backendRegistry, auditLog, threadPool, principalExtractor, settings, configPath, compatConfig); - HTTPOnBehalfOfJwtAuthenticator acInstance = new HTTPOnBehalfOfJwtAuthenticator(); + final OnBehalfOfAuthenticator onBehalfOfAuthenticator = new OnBehalfOfAuthenticator(); final DynamicConfigFactory dcf = new DynamicConfigFactory(cr, settings, configPath, localClient, threadPool, cih); dcf.registerDCFListener(backendRegistry); @@ -848,7 +848,7 @@ public Collection createComponents(Client localClient, ClusterService cl dcf.registerDCFListener(xffResolver); dcf.registerDCFListener(evaluator); dcf.registerDCFListener(securityRestHandler); - dcf.registerDCFListener(acInstance); + dcf.registerDCFListener(onBehalfOfAuthenticator); 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/http/HTTPOnBehalfOfJwtAuthenticator.java b/src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java similarity index 97% rename from src/main/java/org/opensearch/security/http/HTTPOnBehalfOfJwtAuthenticator.java rename to src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java index 1fabd0874c..c65c2c591a 100644 --- a/src/main/java/org/opensearch/security/http/HTTPOnBehalfOfJwtAuthenticator.java +++ b/src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java @@ -44,7 +44,7 @@ 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()); @@ -58,13 +58,13 @@ public class HTTPOnBehalfOfJwtAuthenticator implements HTTPAuthenticator { private String signingKey; private String encryptionKey; - public HTTPOnBehalfOfJwtAuthenticator() { + public OnBehalfOfAuthenticator() { super(); init(); } // FOR TESTING - public HTTPOnBehalfOfJwtAuthenticator(String signingKey, String encryptionKey){ + public OnBehalfOfAuthenticator(String signingKey, String encryptionKey){ this.signingKey = signingKey; this.encryptionKey = encryptionKey; init(); diff --git a/src/test/java/org/opensearch/security/http/HTTPOnBehalfOfJwtAuthenticatorTest.java b/src/test/java/org/opensearch/security/http/OnBehalfOfAuthenticatorTest.java similarity index 94% rename from src/test/java/org/opensearch/security/http/HTTPOnBehalfOfJwtAuthenticatorTest.java rename to src/test/java/org/opensearch/security/http/OnBehalfOfAuthenticatorTest.java index 5a6c5ec41c..dabd1e1a54 100644 --- a/src/test/java/org/opensearch/security/http/HTTPOnBehalfOfJwtAuthenticatorTest.java +++ b/src/test/java/org/opensearch/security/http/OnBehalfOfAuthenticatorTest.java @@ -37,7 +37,7 @@ 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; @@ -88,7 +88,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(BaseEncoding.base64().encode(secretKeyBytes),claimsEncryptionKey); Map headers = new HashMap(); AuthCredentials credentials = jwtAuth.extractCredentials(new FakeRestRequest(headers, new HashMap()), null); @@ -101,7 +101,7 @@ public void testInvalid() throws Exception { String jwsToken = "123invalidtoken.."; - HTTPOnBehalfOfJwtAuthenticator jwtAuth = new HTTPOnBehalfOfJwtAuthenticator(BaseEncoding.base64().encode(secretKeyBytes), claimsEncryptionKey); + OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(BaseEncoding.base64().encode(secretKeyBytes), claimsEncryptionKey); Map headers = new HashMap(); headers.put("Authorization", "Bearer "+jwsToken); @@ -114,7 +114,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(BaseEncoding.base64().encode(secretKeyBytes), claimsEncryptionKey); Map headers = new HashMap(); headers.put("Authorization", "Bearer "+jwsToken); @@ -280,7 +280,7 @@ 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(signingKey, encryptionKey); final Map headers = Map.of("Authorization", "Bearer " + jwsToken); return jwtAuth.extractCredentials(new FakeRestRequest(headers, new HashMap<>()), null); } From 5913b57456d29530dd19a68da665ced0be9ce298 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Thu, 8 Jun 2023 21:47:25 +0000 Subject: [PATCH 03/10] Clean up how roles are (de)encrypted --- .../jwt/EncryptionDecryptionUtil.java | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) 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..9fb407128c 100644 --- a/src/main/java/org/opensearch/security/authtoken/jwt/EncryptionDecryptionUtil.java +++ b/src/main/java/org/opensearch/security/authtoken/jwt/EncryptionDecryptionUtil.java @@ -19,38 +19,39 @@ import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; +import org.opensaml.xmlsec.encryption.P; + public class EncryptionDecryptionUtil { public static String encrypt(final String secret, final String data) { - - byte[] decodedKey = Base64.getDecoder().decode(secret); - - 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 Cipher cipher = createCipherFromSecret(secret); + final byte[] cipherText = createCipherText(cipher, data.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(cipherText); } public static String decrypt(final String secret, final String encryptedString) { + final Cipher cipher = createCipherFromSecret(secret); + final byte[] cipherText = createCipherText(cipher, Base64.getDecoder().decode(encryptedString)); + return new String(cipherText, StandardCharsets.UTF_8); + } - byte[] decodedKey = Base64.getDecoder().decode(secret); - + private static Cipher createCipherFromSecret(final String secret) { try { - Cipher cipher = Cipher.getInstance("AES"); - // rebuild key using SecretKeySpec - SecretKey originalKey = new SecretKeySpec(Arrays.copyOf(decodedKey, 16), "AES"); + 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(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; + } catch (final Exception e) { + throw new RuntimeException("Error creating cipher from secret"); + } + } + + private static byte[] createCipherText(final Cipher cipher, final byte[] data) { + try { + return cipher.doFinal(data); + } catch (final Exception e) { + throw new RuntimeException("The cipher was unable to perform pass over data"); } } } From 437d3159d51838256c9f5ce06528b3ecad968420 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Thu, 8 Jun 2023 21:58:46 +0000 Subject: [PATCH 04/10] Single constructor Signed-off-by: Peter Nied --- .../security/authtoken/jwt/JwtVendor.java | 21 +++---------------- .../security/authtoken/jwt/JwtVendorTest.java | 9 ++++---- 2 files changed, 8 insertions(+), 22 deletions(-) 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..99ef47b370 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; @@ -53,24 +54,8 @@ public class JwtVendor { private ConfigModel configModel; private ThreadContext threadContext; - public JwtVendor(Settings settings) { - 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"); - } - timeProvider = System::currentTimeMillis; - } - //For testing the expiration in the future - public JwtVendor(Settings settings, final LongSupplier timeProvider) { + public JwtVendor(Settings settings, final Optional timeProvider) { JoseJwtProducer jwtProducer = new JoseJwtProducer(); try { this.signingKey = createJwkFromSettings(settings); @@ -83,7 +68,7 @@ public JwtVendor(Settings settings, final LongSupplier timeProvider) { } else { this.claimsEncryptionKey = settings.get("encryption_key"); } - this.timeProvider = timeProvider; + this.timeProvider = timeProvider.orElseGet(() -> System::currentTimeMillis); } /* 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..7d4fbb99d4 100644 --- a/src/test/java/org/opensearch/security/authtoken/jwt/JwtVendorTest.java +++ b/src/test/java/org/opensearch/security/authtoken/jwt/JwtVendorTest.java @@ -12,6 +12,7 @@ package org.opensearch.security.authtoken.jwt; import java.util.List; +import java.util.Optional; import java.util.function.LongSupplier; import org.apache.commons.lang3.RandomStringUtils; @@ -56,7 +57,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 +83,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 +97,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,7 +113,7 @@ 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); } From 4eda3dbeb33a7d72357552399e817b99c75fae75 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Thu, 8 Jun 2023 21:59:05 +0000 Subject: [PATCH 05/10] More JwtVendor cleanup - but not sure if this works? Signed-off-by: Peter Nied --- .../security/authtoken/jwt/JwtVendor.java | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) 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 99ef47b370..93efc93b06 100644 --- a/src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java +++ b/src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java @@ -29,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; @@ -51,10 +51,8 @@ public class JwtVendor { 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? - //For testing the expiration in the future public JwtVendor(Settings settings, final Optional timeProvider) { JoseJwtProducer jwtProducer = new JoseJwtProducer(); try { @@ -108,22 +106,7 @@ 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 { + String createJwt(String issuer, String subject, String audience, Integer expirySeconds, List roles) throws Exception { long timeMillis = timeProvider.getAsLong(); Instant now = Instant.ofEpochMilli(timeProvider.getAsLong()); From 64732bd59807f90e2cade1e726a6691a5751942f Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Thu, 8 Jun 2023 22:40:22 +0000 Subject: [PATCH 06/10] Misc cleanup Signed-off-by: Peter Nied --- .../http/OnBehalfOfAuthenticator.java | 124 ++++++------------ .../http/OnBehalfOfAuthenticatorTest.java | 22 +++- 2 files changed, 55 insertions(+), 91 deletions(-) diff --git a/src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java b/src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java index 7cc01251cd..eb5fa434df 100644 --- a/src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.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; @@ -49,75 +50,51 @@ public class OnBehalfOfAuthenticator implements HTTPAuthenticator { 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; - private volatile boolean initialized; - - public OnBehalfOfAuthenticator() { - super(); - init(); - } - - public OnBehalfOfAuthenticator(Settings settings){ - this.signingKey = settings.get("signing_key"); - this.encryptionKey = settings.get("encryption_key"); - init(); - } - - // FOR TESTING - public OnBehalfOfAuthenticator(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")); } - public boolean isInitialized(){ - return initialized; - } - - 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 @@ -170,9 +147,17 @@ 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; + } String[] roles; @@ -193,15 +178,6 @@ private AuthCredentials extractCredentials0(final RestRequest request) { 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()) { @@ -234,26 +210,6 @@ 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); diff --git a/src/test/java/org/opensearch/security/http/OnBehalfOfAuthenticatorTest.java b/src/test/java/org/opensearch/security/http/OnBehalfOfAuthenticatorTest.java index 0a874fceef..f1d5d87222 100644 --- a/src/test/java/org/opensearch/security/http/OnBehalfOfAuthenticatorTest.java +++ b/src/test/java/org/opensearch/security/http/OnBehalfOfAuthenticatorTest.java @@ -31,6 +31,7 @@ import org.junit.Assert; import org.junit.Test; +import org.opensearch.common.settings.Settings; import org.opensearch.security.user.AuthCredentials; import org.opensearch.security.util.FakeRestRequest; @@ -43,9 +44,9 @@ public class OnBehalfOfAuthenticatorTest { new SecureRandom().nextBytes(secretKeyBytes); secretKey = Keys.hmacShaKeyFor(secretKeyBytes); } - final static String signingKey = BaseEncoding.base64().encode(secretKeyBytes); + @Test public void testNoKey() throws Exception { @@ -85,7 +86,7 @@ public void testBadKey() throws Exception { @Test public void testTokenMissing() throws Exception { - OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(signingKey, claimsEncryptionKey); + OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(defaultSettings()); Map headers = new HashMap(); AuthCredentials credentials = jwtAuth.extractCredentials(new FakeRestRequest(headers, new HashMap()), null); @@ -98,7 +99,7 @@ public void testInvalid() throws Exception { String jwsToken = "123invalidtoken.."; - OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(signingKey, claimsEncryptionKey); + OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(defaultSettings()); Map headers = new HashMap(); headers.put("Authorization", "Bearer "+jwsToken); @@ -111,7 +112,7 @@ public void testBearer() throws Exception { String jwsToken = Jwts.builder().setSubject("Leonard McCoy").setAudience("ext_0").signWith(secretKey, SignatureAlgorithm.HS512).compact(); - OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(signingKey, claimsEncryptionKey); + OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(defaultSettings()); Map headers = new HashMap(); headers.put("Authorization", "Bearer "+jwsToken); @@ -127,7 +128,7 @@ public void testBearer() throws Exception { public void testBearerWrongPosition() throws Exception { String jwsToken = Jwts.builder().setSubject("Leonard McCoy").setAudience("ext_0").signWith(secretKey, SignatureAlgorithm.HS512).compact(); - OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(signingKey, claimsEncryptionKey); + OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(defaultSettings()); Map headers = new HashMap(); headers.put("Authorization", jwsToken + "Bearer " + " 123"); @@ -141,7 +142,7 @@ public void testBearerWrongPosition() throws Exception { @Test public void testBasicAuthHeader() throws Exception { String jwsToken = Jwts.builder().setSubject("Leonard McCoy").setAudience("ext_0").signWith(secretKey, SignatureAlgorithm.HS512).compact(); - OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(signingKey, claimsEncryptionKey); + OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(defaultSettings()); Map headers = Collections.singletonMap(HttpHeaders.AUTHORIZATION, "Basic " + jwsToken); @@ -274,8 +275,15 @@ private AuthCredentials extractCredentialsFromJwtHeader( final JwtBuilder jwtBuilder, final Boolean bwcPluginCompatibilityMode) { final String jwsToken = jwtBuilder.signWith(secretKey, SignatureAlgorithm.HS512).compact(); - final OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(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(); + } } From 861b58838ff2e9cfcd5d8fe3dd93dab785d7c505 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Thu, 8 Jun 2023 22:41:29 +0000 Subject: [PATCH 07/10] Remove bad import Signed-off-by: Peter Nied --- .../security/authtoken/jwt/EncryptionDecryptionUtil.java | 2 -- 1 file changed, 2 deletions(-) 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 9fb407128c..b2e2102edd 100644 --- a/src/main/java/org/opensearch/security/authtoken/jwt/EncryptionDecryptionUtil.java +++ b/src/main/java/org/opensearch/security/authtoken/jwt/EncryptionDecryptionUtil.java @@ -19,8 +19,6 @@ import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; -import org.opensaml.xmlsec.encryption.P; - public class EncryptionDecryptionUtil { public static String encrypt(final String secret, final String data) { From 86b4fd984005c584688c09c2443d1dc146e9e9d3 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Fri, 9 Jun 2023 20:52:11 +0000 Subject: [PATCH 08/10] Add endpoint and test that interactions with JwtVendor Signed-off-by: Peter Nied --- .../http/OnBehalfOfJwtAuthenticationTest.java | 1 + .../security/OpenSearchSecurityPlugin.java | 2 + .../onbehalf/CreateOnBehalfOfToken.java | 141 ++++++++++++++++++ .../security/authtoken/jwt/JwtVendor.java | 11 +- .../dlic/rest/api/AccountApiTest.java | 5 +- 5 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfToken.java diff --git a/src/integrationTest/java/org/opensearch/security/http/OnBehalfOfJwtAuthenticationTest.java b/src/integrationTest/java/org/opensearch/security/http/OnBehalfOfJwtAuthenticationTest.java index dcc6323f97..82cb286043 100644 --- a/src/integrationTest/java/org/opensearch/security/http/OnBehalfOfJwtAuthenticationTest.java +++ b/src/integrationTest/java/org/opensearch/security/http/OnBehalfOfJwtAuthenticationTest.java @@ -61,6 +61,7 @@ public class OnBehalfOfJwtAuthenticationTest { @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(); diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index a323e81c77..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; @@ -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, 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..aa1557cd7e --- /dev/null +++ b/src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfToken.java @@ -0,0 +1,141 @@ +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 { + builder.startObject(); + final User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + builder.field("user_name", "nothing"); + builder.field("user", user.toString()); + final String token = vendor.createJwt("me", user.getName(), "self-issued", 60, List.of("1", "2", "3")); + builder.field("field_token", token); + 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/authtoken/jwt/JwtVendor.java b/src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java index 93efc93b06..1587dcab8c 100644 --- a/src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java +++ b/src/main/java/org/opensearch/security/authtoken/jwt/JwtVendor.java @@ -45,15 +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; // This never gets assigned, how does this work at all? - public JwtVendor(Settings settings, final Optional timeProvider) { + public JwtVendor(final Settings settings, final Optional timeProvider) { JoseJwtProducer jwtProducer = new JoseJwtProducer(); try { this.signingKey = createJwkFromSettings(settings); @@ -67,6 +67,7 @@ public JwtVendor(Settings settings, final Optional timeProvider) { this.claimsEncryptionKey = settings.get("encryption_key"); } this.timeProvider = timeProvider.orElseGet(() -> System::currentTimeMillis); + this.configModel = null; } /* @@ -106,7 +107,7 @@ static JsonWebKey createJwkFromSettings(Settings settings) throws Exception { } } - String createJwt(String issuer, String subject, String audience, Integer expirySeconds, List roles) throws Exception { + 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/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/AccountApiTest.java index 9f6bcb65c9..393cebb88e 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", "", encodeBasicHeader(testUser, testPass)); + System.out.println(response.getBody()); } @Test From d15044b79d2c849d3105d81ae45f733b4548b153 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Mon, 12 Jun 2023 16:10:01 +0000 Subject: [PATCH 09/10] AlFix to do both decrypt and encrypt tokens Signed-off-by: Peter Nied --- .../jwt/EncryptionDecryptionUtil.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) 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 b2e2102edd..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,26 +22,26 @@ public class EncryptionDecryptionUtil { public static String encrypt(final String secret, final String data) { - final Cipher cipher = createCipherFromSecret(secret); + final Cipher cipher = createCipherFromSecret(secret, CipherMode.ENCRYPT); final byte[] cipherText = createCipherText(cipher, data.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(cipherText); } public static String decrypt(final String secret, final String encryptedString) { - final Cipher cipher = createCipherFromSecret(secret); + 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) { + private static Cipher createCipherFromSecret(final String secret, final CipherMode mode) { try { 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(Cipher.DECRYPT_MODE, originalKey); + cipher.init(mode.opmode, originalKey); return cipher; } catch (final Exception e) { - throw new RuntimeException("Error creating cipher from secret"); + throw new RuntimeException("Error creating cipher from secret in mode " + mode.name()); } } @@ -52,4 +52,13 @@ private static byte[] createCipherText(final Cipher cipher, final byte[] data) { 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; + } + } } From 7f3e40e31a4175aa2ad4f50372d1347e9c41b758 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Mon, 12 Jun 2023 16:40:37 +0000 Subject: [PATCH 10/10] Add basic parameters and update test to use them Signed-off-by: Peter Nied --- .../onbehalf/CreateOnBehalfOfToken.java | 26 ++++++++++++++----- .../dlic/rest/api/AccountApiTest.java | 2 +- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfToken.java b/src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfToken.java index aa1557cd7e..219594ad98 100644 --- a/src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfToken.java +++ b/src/main/java/org/opensearch/security/action/onbehalf/CreateOnBehalfOfToken.java @@ -113,14 +113,28 @@ private RestChannelConsumer handlePost(RestRequest request, NodeClient client) t public void accept(RestChannel channel) throws Exception { final XContentBuilder builder = channel.newBuilder(); BytesRestResponse response; - try { - builder.startObject(); + 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.field("user_name", "nothing"); - builder.field("user", user.toString()); - final String token = vendor.createJwt("me", user.getName(), "self-issued", 60, List.of("1", "2", "3")); - builder.field("field_token", token); + + 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); 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 393cebb88e..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 @@ -74,7 +74,7 @@ public void testGetAccount() throws Exception { assertNotNull(body.getAsSettings("tenants")); assertNotNull(body.getAsList("roles")); - response = rh.executePostRequest(getEndpointPrefix() + "/api/user/onbehalfof", "", encodeBasicHeader(testUser, testPass)); + response = rh.executePostRequest(getEndpointPrefix() + "/api/user/onbehalfof", "{\"reason\":\"Test generation\"}", encodeBasicHeader(testUser, testPass)); System.out.println(response.getBody()); }