SOLR-15194: relax requirements and allow http urls. (#2430)

Relax the need for https urls for JWT IDP's if you pass in solr.auth.jwt.allowOutboundHttp=true system property.
This commit is contained in:
Eric Pugh 2021-02-27 09:13:51 -05:00 committed by GitHub
parent 6348c284fd
commit d4fb023756
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 83 additions and 19 deletions

View File

@ -217,7 +217,7 @@ Bug Fixes
* SOLR-14546: Fix for a relatively hard to hit issue in OverseerTaskProcessor that could lead to out of order execution
of Collection API tasks competing for a lock (Ilan Ginzburg).
* SOLR-15162: Allow readOnly parameter to be used with v2 modify collection command (Eric Pugh)
* SOLR-15162: Allow readOnly parameter to be used with v2 modify collection command (Eric Pugh)
================== 8.9.0 ==================
@ -250,6 +250,8 @@ Improvements
* SOLR-15038: Add elevateOnlyDocsMatchingQuery and collectElevatedDocsWhenCollapsing parameters to query elevation.
(Dennis Berger, Tobias Kässmann via Bruno Roustant)
* SOLR-15194: Allow Solr to make outbound non SSL calls to a JWT IDP via -Dsolr.auth.jwt.allowOutboundHttp=true property. (Eric Pugh)
Optimizations
---------------------
* SOLR-15079: Block Collapse - Faster collapse code when groups are co-located via Block Join style nested doc indexing.

View File

@ -68,6 +68,9 @@ public class JWTIssuerConfig {
private WellKnownDiscoveryConfig wellKnownDiscoveryConfig;
private String clientId;
private String authorizationEndpoint;
public static boolean ALLOW_OUTBOUND_HTTP = Boolean.parseBoolean(System.getProperty("solr.auth.jwt.allowOutboundHttp", "false"));
public static final String ALLOW_OUTBOUND_HTTP_ERR_MSG = "HTTPS required for IDP communication. Please use SSL or start your nodes with -Dsolr.auth.jwt.allowOutboundHttp=true to allow HTTP for test purposes.";
/**
* Create config for further configuration with setters, builder style.
@ -349,13 +352,16 @@ public class JWTIssuerConfig {
this.jwkCacheDuration = jwkCacheDuration;
this.refreshReprieveThreshold = refreshReprieveThreshold;
}
/**
* While the class name is HttpsJwks, it actually works with plain http formatted url as well.
* @param url the Url to connect to for JWK details.
*/
private HttpsJwks create(String url) {
try {
URL jwksUrl = new URL(url);
if (!"https".equalsIgnoreCase(jwksUrl.getProtocol())) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, PARAM_JWKS_URL + " must use HTTPS");
}
checkAllowOutboundHttpConnections(PARAM_JWKS_URL, jwksUrl);
} catch (MalformedURLException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Url " + url + " configured in " + PARAM_JWKS_URL + " is not a valid URL");
}
@ -384,9 +390,11 @@ public class JWTIssuerConfig {
public static WellKnownDiscoveryConfig parse(String urlString) {
try {
URL url = new URL(urlString);
if (!Arrays.asList("https", "file").contains(url.getProtocol())) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Well-known config URL must be HTTPS or file");
if (!Arrays.asList("https", "file", "http").contains(url.getProtocol())) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Well-known config URL must be one of HTTPS or HTTP or file");
}
checkAllowOutboundHttpConnections(PARAM_WELL_KNOWN_URL, url);
return parse(url.openStream());
} catch (MalformedURLException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Well-known config URL " + urlString + " is malformed", e);
@ -435,4 +443,13 @@ public class JWTIssuerConfig {
return (List<String>) securityConf.get("response_types_supported");
}
}
public static void checkAllowOutboundHttpConnections(String parameterName, URL url) {
if ("http".equalsIgnoreCase(url.getProtocol())) {
if (!ALLOW_OUTBOUND_HTTP) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, parameterName + " is using http protocol. " + ALLOW_OUTBOUND_HTTP_ERR_MSG);
}
}
}
}

View File

@ -17,6 +17,9 @@
package org.apache.solr.security;
import static org.apache.solr.SolrTestCaseJ4.TEST_PATH;
import static org.apache.solr.security.JWTAuthPluginTest.testJwk;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@ -28,24 +31,22 @@ import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.solr.SolrTestCase;
import org.apache.solr.common.SolrException;
import org.jose4j.jwk.JsonWebKeySet;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.noggit.JSONUtil;
import static org.apache.solr.SolrTestCaseJ4.TEST_PATH;
import static org.apache.solr.security.JWTAuthPluginTest.testJwk;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class JWTIssuerConfigTest {
public class JWTIssuerConfigTest extends SolrTestCase {
private JWTIssuerConfig testIssuer;
private Map<String, Object> testIssuerConfigMap;
private String testIssuerJson;
@Before
public void setUp() throws Exception {
super.setUp();
testIssuer = new JWTIssuerConfig("name")
.setJwksUrl("https://issuer/path")
.setIss("issuer")
@ -65,6 +66,12 @@ public class JWTIssuerConfigTest {
" \"iss\":\"issuer\",\n" +
" \"authorizationEndpoint\":\"https://issuer/authz\"}";
}
@After
public void tearDown() throws Exception {
super.tearDown();
JWTIssuerConfig.ALLOW_OUTBOUND_HTTP = false;
}
@Test
public void parseConfigMap() {
@ -125,6 +132,26 @@ public class JWTIssuerConfigTest {
assertEquals("https://host/jwk", issuerConfig.getJwksUrls().get(0));
}
@Test
public void jwksUrlwithHttpBehaviors() {
HashMap<String, Object> issuerConfigMap = new HashMap<>();
issuerConfigMap.put("name", "myName");
issuerConfigMap.put("iss", "myIss");
issuerConfigMap.put("jwksUrl", "http://host/jwk");
JWTIssuerConfig issuerConfig = new JWTIssuerConfig(issuerConfigMap);
SolrException e = expectThrows(SolrException.class, () -> issuerConfig.getHttpsJwks());
assertEquals(400, e.code());
assertEquals("jwksUrl is using http protocol. HTTPS required for IDP communication. Please use SSL or start your nodes with -Dsolr.auth.jwt.allowOutboundHttp=true to allow HTTP for test purposes.", e.getMessage());
JWTIssuerConfig.ALLOW_OUTBOUND_HTTP = true;
assertEquals(1, issuerConfig.getHttpsJwks().size());
assertEquals("http://host/jwk", issuerConfig.getHttpsJwks().get(0).getLocation());
}
@Test
public void wellKnownConfigFromInputstream() throws IOException {
Path configJson = TEST_PATH().resolve("security").resolve("jwt_well-known-config.json");
@ -144,13 +171,27 @@ public class JWTIssuerConfigTest {
assertEquals(Arrays.asList("code", "code id_token", "code token", "code id_token token", "token", "id_token", "id_token token"), config.getResponseTypesSupported());
}
@Test(expected = SolrException.class)
public void wellKnownConfigNotHttps() {
JWTIssuerConfig.WellKnownDiscoveryConfig.parse("http://127.0.0.1:45678/.well-known/config");
}
@Test
public void wellKnownConfigWithHttpBehaviors() {
SolrException e = expectThrows(SolrException.class, () -> JWTIssuerConfig.WellKnownDiscoveryConfig.parse("http://127.0.0.1:45678/.well-known/config"));
assertEquals(400, e.code());
assertEquals("wellKnownUrl is using http protocol. HTTPS required for IDP communication. Please use SSL or start your nodes with -Dsolr.auth.jwt.allowOutboundHttp=true to allow HTTP for test purposes.", e.getMessage());
JWTIssuerConfig.ALLOW_OUTBOUND_HTTP = true;
e = expectThrows(SolrException.class, () -> JWTIssuerConfig.WellKnownDiscoveryConfig.parse("http://127.0.0.1:45678/.well-known/config"));
assertEquals(500, e.code());
// We open a connection in the code path to a server that doesn't exist, which causes this. Should really be mocked.
assertEquals("Well-known config could not be read from url http://127.0.0.1:45678/.well-known/config", e.getMessage());
@Test(expected = SolrException.class)
}
@Test
public void wellKnownConfigNotReachable() {
JWTIssuerConfig.WellKnownDiscoveryConfig.parse("https://127.0.0.1:45678/.well-known/config");
SolrException e = expectThrows(SolrException.class, () -> JWTIssuerConfig.WellKnownDiscoveryConfig.parse("https://127.0.0.1:45678/.well-known/config"));
assertEquals(500, e.code());
assertEquals("Well-known config could not be read from url https://127.0.0.1:45678/.well-known/config", e.getMessage());
}
}

View File

@ -161,6 +161,10 @@ Let's comment on this config:
<12> Configure the audience claim. A token's 'aud' claim must match 'aud' for one of the configured issuers.
<13> This issuer is auto configured through discovery, so 'iss' and JWK settings are not required
=== Using non SSL URLs
In production environments you should always use SSL protected HTTPS connections, otherwise you open yourself up to attacks.
However, in development, it may be useful to use regular http urls, and bypass the
security check that Solr performs. To support this you can set the environment variable `solr.auth.jwt.allowOutboundHttp=true`.
== Editing JWT Authentication Plugin Configuration