Skip to content

Commit

Permalink
[Feature/Extension] Add configuration of disable OBO (opensearch-proj…
Browse files Browse the repository at this point in the history
…ect#3047)


Signed-off-by: Ryan Liang <[email protected]>
  • Loading branch information
RyanL1997 authored Aug 2, 2023
1 parent df3dba3 commit d634d60
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public class OnBehalfOfJwtAuthenticationTest {

static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS);

private static Boolean oboEnabled = true;
private static final String signingKey = Base64.getEncoder()
.encodeToString(
"jwt signing key for an on behalf of token authentication backend for testing of OBO authentication".getBytes(
Expand All @@ -70,7 +71,7 @@ public class OnBehalfOfJwtAuthenticationTest {
)
)
.authc(AUTHC_HTTPBASIC_INTERNAL)
.onBehalfOf(new OnBehalfOfConfig().signing_key(signingKey).encryption_key(encryptionKey))
.onBehalfOf(new OnBehalfOfConfig().oboEnabled(oboEnabled).signing_key(signingKey).encryption_key(encryptionKey))
.build();

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@
import org.opensearch.core.xcontent.XContentBuilder;

public class OnBehalfOfConfig implements ToXContentObject {
private Boolean oboEnabled;
private String signing_key;
private String encryption_key;

public OnBehalfOfConfig oboEnabled(Boolean oboEnabled) {
this.oboEnabled = oboEnabled;
return this;
}

public OnBehalfOfConfig signing_key(String signing_key) {
this.signing_key = signing_key;
return this;
Expand All @@ -34,6 +40,9 @@ public OnBehalfOfConfig encryption_key(String encryption_key) {
@Override
public XContentBuilder toXContent(XContentBuilder xContentBuilder, ToXContent.Params params) throws IOException {
xContentBuilder.startObject();
if (oboEnabled || oboEnabled == null) {
xContentBuilder.field("enabled", oboEnabled);
}
xContentBuilder.field("signing_key", signing_key);
if (StringUtils.isNoneBlank(encryption_key)) {
xContentBuilder.field("encryption_key", encryption_key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import org.opensearch.rest.RestChannel;
import org.opensearch.rest.RestRequest;
import org.opensearch.rest.RestRequest.Method;
import org.opensearch.rest.RestStatus;
import org.opensearch.core.rest.RestStatus;
import org.opensearch.security.authtoken.jwt.JwtVendor;
import org.opensearch.security.securityconf.ConfigModel;
import org.opensearch.security.securityconf.DynamicConfigModel;
Expand Down Expand Up @@ -59,9 +59,18 @@ public void onConfigModelChanged(ConfigModel configModel) {
@Subscribe
public void onDynamicConfigModelChanged(DynamicConfigModel dcm) {
this.dcm = dcm;
if (dcm.getDynamicOnBehalfOfSettings().get("signing_key") != null
&& dcm.getDynamicOnBehalfOfSettings().get("encryption_key") != null) {
this.vendor = new JwtVendor(dcm.getDynamicOnBehalfOfSettings(), Optional.empty());

Settings settings = dcm.getDynamicOnBehalfOfSettings();
if (settings != null) {
Boolean enabled = Boolean.parseBoolean(settings.get("enabled"));
String signingKey = settings.get("signing_key");
String encryptionKey = settings.get("encryption_key");

if (!Boolean.FALSE.equals(enabled) && signingKey != null && encryptionKey != null) {
this.vendor = new JwtVendor(settings, Optional.empty());
} else {
this.vendor = null;
}
} else {
this.vendor = null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,23 @@ public class OnBehalfOfAuthenticator implements HTTPAuthenticator {

private final JwtParser jwtParser;
private final String encryptionKey;
private final Boolean oboEnabled;

public OnBehalfOfAuthenticator(Settings settings) {
String oboEnabledSetting = settings.get("enabled");
oboEnabled = oboEnabledSetting == null ? Boolean.TRUE : Boolean.valueOf(oboEnabledSetting);
encryptionKey = settings.get("encryption_key");
jwtParser = initParser(settings.get("signing_key"));
}

private JwtParser initParser(final String signingKey) {
JwtParser _jwtParser = keyUtil.keyAlgorithmCheck(signingKey, log);
if (_jwtParser != null) {
return _jwtParser;
} else {

if (_jwtParser == null) {
throw new RuntimeException("Unable to find on behalf of authenticator signing key");
}

return _jwtParser;
}

private List<String> extractSecurityRolesFromClaims(Claims claims) {
Expand Down Expand Up @@ -128,6 +132,11 @@ public AuthCredentials run() {
}

private AuthCredentials extractCredentials0(final RestRequest request) {
if (!oboEnabled) {
log.error("On-behalf-of authentication has been disabled");
return null;
}

if (jwtParser == null) {
log.error("Missing Signing Key. JWT authentication will not work");
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,8 @@ public String toString() {
}

public static class OnBehalfOf {
@JsonProperty("enabled")
private Boolean oboEnabled;
@JsonProperty("signing_key")
private String signingKey;
@JsonProperty("encryption_key")
Expand All @@ -495,6 +497,14 @@ public String configAsJson() {
}
}

public Boolean getOboEnabled() {
return oboEnabled == null ? Boolean.TRUE : oboEnabled;
}

public void setOboEnabled(Boolean oboEnabled) {
this.oboEnabled = oboEnabled;
}

public String getSigningKey() {
return signingKey;
}
Expand All @@ -513,7 +523,7 @@ public void setEncryptionKey(String encryptionKey) {

@Override
public String toString() {
return "OnBehalfOf [signing_key=" + signingKey + ", encryption_key=" + encryptionKey + "]";
return "OnBehalfOf [ enabled=" + oboEnabled + ", signing_key=" + signingKey + ", encryption_key=" + encryptionKey + "]";
}
}

Expand Down
78 changes: 48 additions & 30 deletions src/main/java/org/opensearch/security/util/keyUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import org.apache.logging.log4j.Logger;
import org.opensearch.SpecialPermission;

import java.security.AccessController;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedAction;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
Expand All @@ -27,40 +30,55 @@
public class keyUtil {

public static JwtParser keyAlgorithmCheck(final String signingKey, final Logger log) {
if (signingKey == null || signingKey.length() == 0) {
log.error("Unable to find signing key");
return null;
} else {
try {
Key key = null;

final String minimalKeyFormat = signingKey.replace("-----BEGIN PUBLIC KEY-----\n", "")
.replace("-----END PUBLIC KEY-----", "");

final byte[] decoded = Base64.getDecoder().decode(minimalKeyFormat);

try {
key = getPublicKey(decoded, "RSA");
} catch (Exception e) {
log.debug("No public RSA key, try other algos ({})", e.toString());
}
final SecurityManager sm = System.getSecurityManager();

try {
key = getPublicKey(decoded, "EC");
} catch (final Exception e) {
log.debug("No public ECDSA key, try other algos ({})", e.toString());
}
JwtParser jwtParser = null;

if (Objects.nonNull(key)) {
return Jwts.parser().setSigningKey(key);
}
if (sm != null) {
sm.checkPermission(new SpecialPermission());
}

jwtParser = AccessController.doPrivileged(new PrivilegedAction<JwtParser>() {
@Override
public JwtParser run() {
if (signingKey == null || signingKey.length() == 0) {
log.error("Unable to find signing key");
return null;
} else {
try {
Key key = null;

final String minimalKeyFormat = signingKey.replace("-----BEGIN PUBLIC KEY-----\n", "")
.replace("-----END PUBLIC KEY-----", "");

final byte[] decoded = Base64.getDecoder().decode(minimalKeyFormat);

return Jwts.parser().setSigningKey(decoded);
} catch (Throwable e) {
log.error("Error while creating JWT authenticator", e);
throw new RuntimeException(e);
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 (final Exception e) {
log.debug("No public ECDSA key, try other algos ({})", e.toString());
}

if (Objects.nonNull(key)) {
return Jwts.parser().setSigningKey(key);
}

return Jwts.parser().setSigningKey(decoded);
} catch (Throwable e) {
log.error("Error while creating JWT authenticator", e);
throw new RuntimeException(e);
}
}
}
}
});

return jwtParser;
}

private static PublicKey getPublicKey(final byte[] keyBytes, final String algo) throws NoSuchAlgorithmException,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
import org.opensearch.security.util.FakeRestRequest;

public class OnBehalfOfAuthenticatorTest {
final static String enableOBO = "true";
final static String disableOBO = "false";
final static String claimsEncryptionKey = RandomStringUtils.randomAlphanumeric(16);

final static String signingKey =
Expand Down Expand Up @@ -115,6 +117,38 @@ public void testInvalid() throws Exception {
Assert.assertNull(credentials);
}

@Test
public void testDisabled() throws Exception {
String jwsToken = Jwts.builder()
.setSubject("Leonard McCoy")
.setAudience("ext_0")
.signWith(Keys.hmacShaKeyFor(Base64.getDecoder().decode(signingKeyB64Encoded)), SignatureAlgorithm.HS512)
.compact();

OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(disableOBOSettings());
Map<String, String> headers = new HashMap<String, String>();
headers.put("Authorization", "Bearer " + jwsToken);

AuthCredentials credentials = jwtAuth.extractCredentials(new FakeRestRequest(headers, new HashMap<String, String>()), null);
Assert.assertNull(credentials);
}

@Test
public void testNonSpecifyOBOSetting() throws Exception {
String jwsToken = Jwts.builder()
.setSubject("Leonard McCoy")
.setAudience("ext_0")
.signWith(Keys.hmacShaKeyFor(Base64.getDecoder().decode(signingKeyB64Encoded)), SignatureAlgorithm.HS512)
.compact();

OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(nonSpecifyOBOSetting());
Map<String, String> headers = new HashMap<String, String>();
headers.put("Authorization", "Bearer " + jwsToken);

AuthCredentials credentials = jwtAuth.extractCredentials(new FakeRestRequest(headers, new HashMap<String, String>()), null);
Assert.assertNotNull(credentials);
}

@Test
public void testBearer() throws Exception {

Expand Down Expand Up @@ -297,7 +331,11 @@ private AuthCredentials extractCredentialsFromJwtHeader(
final Boolean bwcPluginCompatibilityMode
) {
final OnBehalfOfAuthenticator jwtAuth = new OnBehalfOfAuthenticator(
Settings.builder().put("signing_key", signingKeyB64Encoded).put("encryption_key", encryptionKey).build()
Settings.builder()
.put("enabled", enableOBO)
.put("signing_key", signingKeyB64Encoded)
.put("encryption_key", encryptionKey)
.build()
);

final String jwsToken = jwtBuilder.signWith(
Expand All @@ -309,6 +347,22 @@ private AuthCredentials extractCredentialsFromJwtHeader(
}

private Settings defaultSettings() {
return Settings.builder()
.put("enabled", enableOBO)
.put("signing_key", signingKeyB64Encoded)
.put("encryption_key", claimsEncryptionKey)
.build();
}

private Settings disableOBOSettings() {
return Settings.builder()
.put("enabled", disableOBO)
.put("signing_key", signingKeyB64Encoded)
.put("encryption_key", claimsEncryptionKey)
.build();
}

private Settings nonSpecifyOBOSetting() {
return Settings.builder().put("signing_key", signingKeyB64Encoded).put("encryption_key", claimsEncryptionKey).build();
}
}
1 change: 1 addition & 0 deletions src/test/resources/restapi/securityconfig_nondefault.json
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@
"hosts_resolver_mode" : "ip-only",
"do_not_fail_on_forbidden_empty" : false,
"on_behalf_of": {
"enabled": true,
"signing_key": "VGhpcyBpcyB0aGUgand0IHNpZ25pbmcga2V5IGZvciBhbiBvbiBiZWhhbGYgb2YgdG9rZW4gYXV0aGVudGljYXRpb24gYmFja2VuZCBmb3IgdGVzdGluZyBvZiBleHRlbnNpb25z",
"encryption_key": "ZW5jcnlwdGlvbktleQ=="
}
Expand Down

0 comments on commit d634d60

Please sign in to comment.