mirror of https://github.com/apache/lucene.git
SOLR-12791: Add Metrics reporting for AuthenticationPlugin
This commit is contained in:
parent
280f67927e
commit
ef2f0cd88c
|
@ -79,6 +79,8 @@ New Features
|
|||
|
||||
* SOLR-12593: The default configSet now includes an "ignored_*" dynamic field. (David Smiley)
|
||||
|
||||
* SOLR-12791: Add Metrics reporting for AuthenticationPlugin (janhoy)
|
||||
|
||||
Bug Fixes
|
||||
----------------------
|
||||
|
||||
|
|
|
@ -804,6 +804,12 @@ public class CoreContainer {
|
|||
SecurityConfHandler.SecurityConfig securityConfig = securityConfHandler.getSecurityConfig(false);
|
||||
initializeAuthorizationPlugin((Map<String, Object>) securityConfig.getData().get("authorization"));
|
||||
initializeAuthenticationPlugin((Map<String, Object>) securityConfig.getData().get("authentication"));
|
||||
if (authenticationPlugin != null) {
|
||||
authenticationPlugin.plugin.initializeMetrics(metricManager, SolrInfoBean.Group.node.toString(), metricTag, "/authentication");
|
||||
}
|
||||
if (pkiAuthenticationPlugin != null && pkiAuthenticationPlugin.getMetricRegistry() == null) {
|
||||
pkiAuthenticationPlugin.initializeMetrics(metricManager, SolrInfoBean.Group.node.toString(), metricTag, "/authentication/pki");
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkForDuplicateCoreNames(List<CoreDescriptor> cds) {
|
||||
|
|
|
@ -34,7 +34,7 @@ public interface SolrInfoBean {
|
|||
* Category of Solr component.
|
||||
*/
|
||||
enum Category { CONTAINER, ADMIN, CORE, QUERY, UPDATE, CACHE, HIGHLIGHTER, QUERYPARSER, SPELLCHECKER,
|
||||
SEARCHER, REPLICATION, TLOG, INDEX, DIRECTORY, HTTP, OTHER }
|
||||
SEARCHER, REPLICATION, TLOG, INDEX, DIRECTORY, HTTP, SECURITY, OTHER }
|
||||
|
||||
/**
|
||||
* Top-level group of beans or metrics for a subsystem.
|
||||
|
|
|
@ -20,16 +20,42 @@ import javax.servlet.FilterChain;
|
|||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import java.io.Closeable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.codahale.metrics.Counter;
|
||||
import com.codahale.metrics.Meter;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.codahale.metrics.Timer;
|
||||
import org.apache.solr.core.SolrInfoBean;
|
||||
import org.apache.solr.metrics.SolrMetricManager;
|
||||
import org.apache.solr.metrics.SolrMetricProducer;
|
||||
|
||||
/**
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
public abstract class AuthenticationPlugin implements Closeable {
|
||||
public abstract class AuthenticationPlugin implements Closeable, SolrInfoBean, SolrMetricProducer {
|
||||
|
||||
final public static String AUTHENTICATION_PLUGIN_PROP = "authenticationPlugin";
|
||||
|
||||
// Metrics
|
||||
private Set<String> metricNames = ConcurrentHashMap.newKeySet();
|
||||
private MetricRegistry registry;
|
||||
|
||||
protected String registryName;
|
||||
protected SolrMetricManager metricManager;
|
||||
protected Meter numErrors = new Meter();
|
||||
protected Counter requests = new Counter();
|
||||
protected Timer requestTimes = new Timer();
|
||||
protected Counter totalTime = new Counter();
|
||||
protected Counter numAuthenticated = new Counter();
|
||||
protected Counter numPassThrough = new Counter();
|
||||
protected Counter numWrongCredentials = new Counter();
|
||||
protected Counter numMissingCredentials = new Counter();
|
||||
|
||||
/**
|
||||
* This is called upon loading up of a plugin, used for setting it up.
|
||||
* @param pluginConfig Config parameters, possibly from a ZK source
|
||||
|
@ -52,6 +78,23 @@ public abstract class AuthenticationPlugin implements Closeable {
|
|||
public abstract boolean doAuthenticate(ServletRequest request, ServletResponse response,
|
||||
FilterChain filterChain) throws Exception;
|
||||
|
||||
/**
|
||||
* This method is called by SolrDispatchFilter in order to initiate authentication.
|
||||
* It does some standard metrics counting.
|
||||
*/
|
||||
public final boolean authenticate(ServletRequest request, ServletResponse response, FilterChain filterChain) throws Exception {
|
||||
Timer.Context timer = requestTimes.time();
|
||||
requests.inc();
|
||||
try {
|
||||
return doAuthenticate(request, response, filterChain);
|
||||
} catch(Exception e) {
|
||||
numErrors.mark();
|
||||
throw e;
|
||||
} finally {
|
||||
long elapsed = timer.stop();
|
||||
totalTime.inc(elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup any per request data
|
||||
|
@ -59,4 +102,47 @@ public abstract class AuthenticationPlugin implements Closeable {
|
|||
public void closeRequest() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeMetrics(SolrMetricManager manager, String registryName, String tag, final String scope) {
|
||||
this.metricManager = manager;
|
||||
this.registryName = registryName;
|
||||
// Metrics
|
||||
registry = manager.registry(registryName);
|
||||
numErrors = manager.meter(this, registryName, "errors", getCategory().toString(), scope);
|
||||
requests = manager.counter(this, registryName, "requests", getCategory().toString(), scope);
|
||||
numAuthenticated = manager.counter(this, registryName, "authenticated", getCategory().toString(), scope);
|
||||
numPassThrough = manager.counter(this, registryName, "passThrough", getCategory().toString(), scope);
|
||||
numWrongCredentials = manager.counter(this, registryName, "failWrongCredentials", getCategory().toString(), scope);
|
||||
numMissingCredentials = manager.counter(this, registryName, "failMissingCredentials", getCategory().toString(), scope);
|
||||
requestTimes = manager.timer(this, registryName, "requestTimes", getCategory().toString(), scope);
|
||||
totalTime = manager.counter(this, registryName, "totalTime", getCategory().toString(), scope);
|
||||
metricNames.addAll(Arrays.asList("errors", "requests", "authenticated", "passThrough",
|
||||
"failWrongCredentials", "failMissingCredentials", "requestTimes", "totalTime"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.getClass().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Authentication Plugin " + this.getClass().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Category getCategory() {
|
||||
return Category.SECURITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getMetricNames() {
|
||||
return metricNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetricRegistry getMetricRegistry() {
|
||||
return registry;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -127,6 +127,7 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
|
|||
if (st.hasMoreTokens()) {
|
||||
String basic = st.nextToken();
|
||||
if (basic.equalsIgnoreCase("Basic")) {
|
||||
if (st.hasMoreTokens()) {
|
||||
try {
|
||||
String credentials = new String(Base64.decodeBase64(st.nextToken()), "UTF-8");
|
||||
int p = credentials.indexOf(":");
|
||||
|
@ -134,8 +135,10 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
|
|||
final String username = credentials.substring(0, p).trim();
|
||||
String pwd = credentials.substring(p + 1).trim();
|
||||
if (!authenticate(username, pwd)) {
|
||||
numWrongCredentials.inc();
|
||||
log.debug("Bad auth credentials supplied in Authorization header");
|
||||
authenticationFailure(response, isAjaxRequest, "Bad credentials");
|
||||
return false;
|
||||
} else {
|
||||
HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(request) {
|
||||
@Override
|
||||
|
@ -143,29 +146,39 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
|
|||
return new BasicUserPrincipal(username);
|
||||
}
|
||||
};
|
||||
numAuthenticated.inc();
|
||||
filterChain.doFilter(wrapper, response);
|
||||
return true;
|
||||
}
|
||||
|
||||
} else {
|
||||
numErrors.mark();
|
||||
authenticationFailure(response, isAjaxRequest, "Invalid authentication token");
|
||||
return false;
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new Error("Couldn't retrieve authentication", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
numErrors.mark();
|
||||
authenticationFailure(response, isAjaxRequest, "Malformed Basic Auth header");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No auth header OR header empty OR Authorization header not of type Basic, i.e. "unknown" user
|
||||
if (blockUnknown) {
|
||||
numMissingCredentials.inc();
|
||||
authenticationFailure(response, isAjaxRequest, "require authentication");
|
||||
return false;
|
||||
} else {
|
||||
numPassThrough.inc();
|
||||
request.setAttribute(AuthenticationPlugin.class.getName(), authenticationProvider.getPromptHeaders());
|
||||
filterChain.doFilter(request, response);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
|
|
|
@ -244,6 +244,24 @@ public class HadoopAuthPlugin extends AuthenticationPlugin {
|
|||
};
|
||||
authFilter.doFilter(request, rspCloseShield, filterChain);
|
||||
|
||||
switch (frsp.getStatus()) {
|
||||
case HttpServletResponse.SC_UNAUTHORIZED:
|
||||
// Cannot tell whether the 401 is due to wrong or missing credentials
|
||||
numWrongCredentials.inc();
|
||||
break;
|
||||
|
||||
case HttpServletResponse.SC_FORBIDDEN:
|
||||
// Are there other status codes which should also translate to error?
|
||||
numErrors.mark();
|
||||
break;
|
||||
default:
|
||||
if (frsp.getStatus() >= 200 && frsp.getStatus() <= 299) {
|
||||
numAuthenticated.inc();
|
||||
} else {
|
||||
numErrors.mark();
|
||||
}
|
||||
}
|
||||
|
||||
if (TRACE_HTTP) {
|
||||
log.info("----------HTTP Response---------");
|
||||
log.info("Status : {}", frsp.getStatus());
|
||||
|
|
|
@ -90,6 +90,7 @@ public class PKIAuthenticationPlugin extends AuthenticationPlugin implements Htt
|
|||
|
||||
String requestURI = ((HttpServletRequest) request).getRequestURI();
|
||||
if (requestURI.endsWith(PublicKeyHandler.PATH)) {
|
||||
numPassThrough.inc();
|
||||
filterChain.doFilter(request, response);
|
||||
return true;
|
||||
}
|
||||
|
@ -98,6 +99,7 @@ public class PKIAuthenticationPlugin extends AuthenticationPlugin implements Htt
|
|||
if (header == null) {
|
||||
//this must not happen
|
||||
log.error("No SolrAuth header present");
|
||||
numMissingCredentials.inc();
|
||||
filterChain.doFilter(request, response);
|
||||
return true;
|
||||
}
|
||||
|
@ -105,6 +107,7 @@ public class PKIAuthenticationPlugin extends AuthenticationPlugin implements Htt
|
|||
List<String> authInfo = StrUtils.splitWS(header, false);
|
||||
if (authInfo.size() < 2) {
|
||||
log.error("Invalid SolrAuth Header {}", header);
|
||||
numErrors.mark();
|
||||
filterChain.doFilter(request, response);
|
||||
return true;
|
||||
}
|
||||
|
@ -115,11 +118,13 @@ public class PKIAuthenticationPlugin extends AuthenticationPlugin implements Htt
|
|||
PKIHeaderData decipher = decipherHeader(nodeName, cipher);
|
||||
if (decipher == null) {
|
||||
log.error("Could not decipher a header {} . No principal set", header);
|
||||
numMissingCredentials.inc();
|
||||
filterChain.doFilter(request, response);
|
||||
return true;
|
||||
}
|
||||
if ((receivedTime - decipher.timestamp) > MAX_VALIDITY) {
|
||||
log.error("Invalid key request timestamp: {} , received timestamp: {} , TTL: {}", decipher.timestamp, receivedTime, MAX_VALIDITY);
|
||||
numErrors.mark();
|
||||
filterChain.doFilter(request, response);
|
||||
return true;
|
||||
}
|
||||
|
@ -128,6 +133,7 @@ public class PKIAuthenticationPlugin extends AuthenticationPlugin implements Htt
|
|||
SU :
|
||||
new BasicUserPrincipal(decipher.userName);
|
||||
|
||||
numAuthenticated.inc();
|
||||
filterChain.doFilter(getWrapper((HttpServletRequest) request, principal), response);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -478,7 +478,7 @@ public class SolrDispatchFilter extends BaseSolrFilter {
|
|||
try {
|
||||
log.debug("Request to authenticate: {}, domain: {}, port: {}", request, request.getLocalName(), request.getLocalPort());
|
||||
// upon successful authentication, this should call the chain's next filter.
|
||||
requestContinues = authenticationPlugin.doAuthenticate(request, response, (req, rsp) -> {
|
||||
requestContinues = authenticationPlugin.authenticate(request, response, (req, rsp) -> {
|
||||
isAuthenticated.set(true);
|
||||
wrappedRequest.set((HttpServletRequest) req);
|
||||
});
|
||||
|
|
|
@ -30,6 +30,7 @@ import java.util.Objects;
|
|||
import java.util.Random;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
|
@ -47,13 +48,15 @@ import org.apache.solr.client.solrj.request.GenericSolrRequest;
|
|||
import org.apache.solr.client.solrj.request.RequestWriter.StringPayloadContentWriter;
|
||||
import org.apache.solr.client.solrj.request.UpdateRequest;
|
||||
import org.apache.solr.client.solrj.request.V2Request;
|
||||
import org.apache.solr.cloud.SolrCloudTestCase;
|
||||
import org.apache.solr.cloud.SolrCloudAuthTestCase;
|
||||
import org.apache.solr.common.SolrInputDocument;
|
||||
import org.apache.solr.common.cloud.DocCollection;
|
||||
import org.apache.solr.common.cloud.Replica;
|
||||
import org.apache.solr.common.cloud.Slice;
|
||||
import org.apache.solr.common.params.CommonParams;
|
||||
import org.apache.solr.common.params.MapSolrParams;
|
||||
import org.apache.solr.common.params.ModifiableSolrParams;
|
||||
import org.apache.solr.common.params.SolrParams;
|
||||
import org.apache.solr.common.util.Base64;
|
||||
import org.apache.solr.common.util.NamedList;
|
||||
import org.apache.solr.common.util.StrUtils;
|
||||
|
@ -67,7 +70,7 @@ import org.slf4j.LoggerFactory;
|
|||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.Collections.singletonMap;
|
||||
|
||||
public class BasicAuthIntegrationTest extends SolrCloudTestCase {
|
||||
public class BasicAuthIntegrationTest extends SolrCloudAuthTestCase {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
|
@ -116,6 +119,9 @@ public class BasicAuthIntegrationTest extends SolrCloudTestCase {
|
|||
|
||||
baseUrl = randomJetty.getBaseUrl().toString();
|
||||
verifySecurityStatus(cl, baseUrl + authcPrefix, "authentication/class", "solr.BasicAuthPlugin", 20);
|
||||
assertNumberOfMetrics(16); // Basic auth metrics available
|
||||
assertAuthMetricsMinimums(1, 0, 1, 0, 0, 0);
|
||||
assertPkiAuthMetricsMinimums(0, 0, 0, 0, 0, 0);
|
||||
|
||||
String command = "{\n" +
|
||||
"'set-user': {'harry':'HarryIsCool'}\n" +
|
||||
|
@ -134,6 +140,8 @@ public class BasicAuthIntegrationTest extends SolrCloudTestCase {
|
|||
cluster.getSolrClient().request(genericReq);
|
||||
});
|
||||
assertEquals(401, exp.code());
|
||||
assertAuthMetricsMinimums(2, 0, 2, 0, 0, 0);
|
||||
assertPkiAuthMetricsMinimums(0, 0, 0, 0, 0, 0);
|
||||
|
||||
command = "{\n" +
|
||||
"'set-user': {'harry':'HarryIsUberCool'}\n" +
|
||||
|
@ -148,6 +156,8 @@ public class BasicAuthIntegrationTest extends SolrCloudTestCase {
|
|||
int statusCode = r.getStatusLine().getStatusCode();
|
||||
Utils.consumeFully(r.getEntity());
|
||||
assertEquals("proper_cred sent, but access denied", 200, statusCode);
|
||||
assertPkiAuthMetricsMinimums(0, 0, 0, 0, 0, 0);
|
||||
assertAuthMetricsMinimums(4, 1, 3, 0, 0, 0);
|
||||
|
||||
baseUrl = cluster.getRandomJetty(random()).getBaseUrl().toString();
|
||||
|
||||
|
@ -157,6 +167,7 @@ public class BasicAuthIntegrationTest extends SolrCloudTestCase {
|
|||
"}";
|
||||
|
||||
executeCommand(baseUrl + authzPrefix, cl,command, "solr", "SolrRocks");
|
||||
assertAuthMetricsMinimums(6, 2, 4, 0, 0, 0);
|
||||
|
||||
baseUrl = cluster.getRandomJetty(random()).getBaseUrl().toString();
|
||||
verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/user-role/harry", NOT_NULL_PREDICATE, 20);
|
||||
|
@ -167,10 +178,12 @@ public class BasicAuthIntegrationTest extends SolrCloudTestCase {
|
|||
"role", "dev"))), "harry", "HarryIsUberCool" );
|
||||
|
||||
verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/permissions[1]/collection", "x", 20);
|
||||
assertAuthMetricsMinimums(9, 3, 6, 0, 0, 0);
|
||||
|
||||
executeCommand(baseUrl + authzPrefix, cl,Utils.toJSONString(singletonMap("set-permission", Utils.makeMap
|
||||
("name", "collection-admin-edit", "role", "admin"))), "harry", "HarryIsUberCool" );
|
||||
verifySecurityStatus(cl, baseUrl + authzPrefix, "authorization/permissions[2]/name", "collection-admin-edit", 20);
|
||||
assertAuthMetricsMinimums(11, 4, 7, 0, 0, 0);
|
||||
|
||||
CollectionAdminRequest.Reload reload = CollectionAdminRequest.reloadCollection(COLLECTION);
|
||||
|
||||
|
@ -197,7 +210,7 @@ public class BasicAuthIntegrationTest extends SolrCloudTestCase {
|
|||
.setBasicAuthCredentials("harry", "Cool12345"));
|
||||
fail("This should not succeed");
|
||||
} catch (HttpSolrClient.RemoteSolrException e) {
|
||||
|
||||
assertAuthMetricsMinimums(15, 5, 9, 1, 0, 0);
|
||||
}
|
||||
|
||||
executeCommand(baseUrl + authzPrefix, cl,"{set-permission : { name : update , role : admin}}", "harry", "HarryIsUberCool");
|
||||
|
@ -214,6 +227,7 @@ public class BasicAuthIntegrationTest extends SolrCloudTestCase {
|
|||
executeCommand(baseUrl + authcPrefix, cl, "{set-property : { blockUnknown: true}}", "harry", "HarryIsUberCool");
|
||||
verifySecurityStatus(cl, baseUrl + authcPrefix, "authentication/blockUnknown", "true", 20, "harry", "HarryIsUberCool");
|
||||
verifySecurityStatus(cl, baseUrl + "/admin/info/key", "key", NOT_NULL_PREDICATE, 20);
|
||||
assertAuthMetricsMinimums(18, 8, 9, 1, 0, 0);
|
||||
|
||||
String[] toolArgs = new String[]{
|
||||
"status", "-solr", baseUrl};
|
||||
|
@ -232,6 +246,24 @@ public class BasicAuthIntegrationTest extends SolrCloudTestCase {
|
|||
log.error("RunExampleTool failed due to: " + e +
|
||||
"; stdout from tool prior to failure: " + baos.toString(StandardCharsets.UTF_8.name()));
|
||||
}
|
||||
|
||||
SolrParams params = new MapSolrParams(Collections.singletonMap("q", "*:*"));
|
||||
// Query that fails due to missing credentials
|
||||
exp = expectThrows(HttpSolrClient.RemoteSolrException.class, () -> {
|
||||
cluster.getSolrClient().query(COLLECTION, params);
|
||||
});
|
||||
assertEquals(401, exp.code());
|
||||
assertAuthMetricsMinimums(20, 8, 9, 1, 2, 0);
|
||||
assertPkiAuthMetricsMinimums(4, 4, 0, 0, 0, 0);
|
||||
|
||||
// Query that succeeds
|
||||
GenericSolrRequest req = new GenericSolrRequest(SolrRequest.METHOD.GET, "/select", params);
|
||||
req.setBasicAuthCredentials("harry", "HarryIsUberCool");
|
||||
cluster.getSolrClient().request(req, COLLECTION);
|
||||
|
||||
assertAuthMetricsMinimums(21, 9, 9, 1, 2, 0);
|
||||
assertPkiAuthMetricsMinimums(7, 7, 0, 0, 0, 0);
|
||||
|
||||
executeCommand(baseUrl + authcPrefix, cl, "{set-property : { blockUnknown: false}}", "harry", "HarryIsUberCool");
|
||||
} finally {
|
||||
if (cl != null) {
|
||||
|
@ -240,6 +272,13 @@ public class BasicAuthIntegrationTest extends SolrCloudTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
private void assertNumberOfMetrics(int num) {
|
||||
MetricRegistry registry0 = cluster.getJettySolrRunner(0).getCoreContainer().getMetricManager().registry("solr.node");
|
||||
assertNotNull(registry0);
|
||||
|
||||
assertEquals(num, registry0.getMetrics().entrySet().stream().filter(e -> e.getKey().startsWith("SECURITY")).count());
|
||||
}
|
||||
|
||||
public static void executeCommand(String url, HttpClient cl, String payload, String user, String pwd)
|
||||
throws IOException {
|
||||
HttpPost httpPost;
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.apache.http.client.methods.HttpPost;
|
|||
import org.apache.http.entity.ByteArrayEntity;
|
||||
import org.apache.http.message.AbstractHttpMessage;
|
||||
import org.apache.http.message.BasicHeader;
|
||||
import org.apache.solr.SolrTestCaseJ4;
|
||||
import org.apache.solr.client.solrj.SolrRequest;
|
||||
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
|
||||
import org.apache.solr.client.solrj.impl.HttpClientUtil;
|
||||
|
@ -41,8 +42,6 @@ import org.apache.solr.common.util.Base64;
|
|||
import org.apache.solr.common.util.Utils;
|
||||
import org.apache.solr.handler.admin.SecurityConfHandler;
|
||||
import org.apache.solr.handler.admin.SecurityConfHandlerLocalForTesting;
|
||||
import org.apache.solr.SolrTestCaseJ4;
|
||||
import org.apache.solr.util.LogLevel;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
@ -84,7 +83,6 @@ public class BasicAuthStandaloneTest extends SolrTestCaseJ4 {
|
|||
}
|
||||
|
||||
@Test
|
||||
@LogLevel("org.apache.solr=DEBUG")
|
||||
public void testBasicAuth() throws Exception {
|
||||
|
||||
String authcPrefix = "/admin/authentication";
|
||||
|
|
|
@ -25,7 +25,7 @@ import org.apache.http.client.HttpClient;
|
|||
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
|
||||
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
|
||||
import org.apache.solr.client.solrj.request.QueryRequest;
|
||||
import org.apache.solr.cloud.SolrCloudTestCase;
|
||||
import org.apache.solr.cloud.SolrCloudAuthTestCase;
|
||||
import org.apache.solr.common.cloud.ZkStateReader;
|
||||
import org.apache.solr.common.params.ModifiableSolrParams;
|
||||
import org.apache.solr.common.util.Utils;
|
||||
|
@ -39,7 +39,7 @@ import static java.util.Collections.singletonMap;
|
|||
import static org.apache.solr.common.util.Utils.makeMap;
|
||||
import static org.apache.solr.security.TestAuthorizationFramework.verifySecurityStatus;
|
||||
|
||||
public class PKIAuthenticationIntegrationTest extends SolrCloudTestCase {
|
||||
public class PKIAuthenticationIntegrationTest extends SolrCloudAuthTestCase {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
|
@ -96,7 +96,7 @@ public class PKIAuthenticationIntegrationTest extends SolrCloudTestCase {
|
|||
QueryRequest query = new QueryRequest(params);
|
||||
query.process(cluster.getSolrClient(), "collection");
|
||||
assertTrue("all nodes must get the user solr , no:of nodes got solr : " + count.get(), count.get() > 2);
|
||||
|
||||
assertPkiAuthMetricsMinimums(2, 2, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
@After
|
||||
|
|
|
@ -99,7 +99,7 @@ public class TestPKIAuthenticationPlugin extends SolrTestCaseJ4 {
|
|||
final AtomicReference<ServletRequest> wrappedRequestByFilter = new AtomicReference<>();
|
||||
HttpServletRequest mockReq = createMockRequest(header);
|
||||
FilterChain filterChain = (servletRequest, servletResponse) -> wrappedRequestByFilter.set(servletRequest);
|
||||
mock.doAuthenticate(mockReq, null, filterChain);
|
||||
mock.authenticate(mockReq, null, filterChain);
|
||||
|
||||
assertNotNull(((HttpServletRequest) wrappedRequestByFilter.get()).getUserPrincipal());
|
||||
assertNotNull(wrappedRequestByFilter.get());
|
||||
|
@ -112,7 +112,7 @@ public class TestPKIAuthenticationPlugin extends SolrTestCaseJ4 {
|
|||
request = new BasicHttpRequest("GET", "http://localhost:56565");
|
||||
mock.setHeader(request);
|
||||
assertNull(request.getFirstHeader(PKIAuthenticationPlugin.HEADER));
|
||||
mock.doAuthenticate(mockReq, null, filterChain);
|
||||
mock.authenticate(mockReq, null, filterChain);
|
||||
assertNotNull(wrappedRequestByFilter.get());
|
||||
assertNull(((HttpServletRequest) wrappedRequestByFilter.get()).getUserPrincipal());
|
||||
|
||||
|
@ -129,7 +129,7 @@ public class TestPKIAuthenticationPlugin extends SolrTestCaseJ4 {
|
|||
assertNotNull(header.get());
|
||||
assertTrue(header.get().getValue().startsWith(nodeName));
|
||||
|
||||
mock.doAuthenticate(mockReq, null, filterChain);
|
||||
mock.authenticate(mockReq, null, filterChain);
|
||||
assertNotNull(wrappedRequestByFilter.get());
|
||||
assertEquals("$", ((HttpServletRequest) wrappedRequestByFilter.get()).getUserPrincipal().getName());
|
||||
|
||||
|
@ -146,7 +146,7 @@ public class TestPKIAuthenticationPlugin extends SolrTestCaseJ4 {
|
|||
}
|
||||
};
|
||||
|
||||
mock1.doAuthenticate(mockReq, null,filterChain );
|
||||
mock1.authenticate(mockReq, null,filterChain );
|
||||
assertNotNull(wrappedRequestByFilter.get());
|
||||
assertEquals("$", ((HttpServletRequest) wrappedRequestByFilter.get()).getUserPrincipal().getName());
|
||||
mock1.close();
|
||||
|
|
|
@ -27,14 +27,14 @@ import org.apache.solr.client.solrj.request.CollectionAdminRequest;
|
|||
import org.apache.solr.client.solrj.response.QueryResponse;
|
||||
import org.apache.solr.cloud.AbstractDistribZkTestBase;
|
||||
import org.apache.solr.cloud.KerberosTestServices;
|
||||
import org.apache.solr.cloud.SolrCloudTestCase;
|
||||
import org.apache.solr.cloud.SolrCloudAuthTestCase;
|
||||
import org.apache.solr.common.SolrInputDocument;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
// commented 20-July-2018 @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 12-Jun-2018
|
||||
public class TestSolrCloudWithHadoopAuthPlugin extends SolrCloudTestCase {
|
||||
public class TestSolrCloudWithHadoopAuthPlugin extends SolrCloudAuthTestCase {
|
||||
protected static final int NUM_SERVERS = 1;
|
||||
protected static final int NUM_SHARDS = 1;
|
||||
protected static final int REPLICATION_FACTOR = 1;
|
||||
|
@ -119,11 +119,14 @@ public class TestSolrCloudWithHadoopAuthPlugin extends SolrCloudTestCase {
|
|||
CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(collectionName, "conf1",
|
||||
NUM_SHARDS, REPLICATION_FACTOR);
|
||||
create.process(solrClient);
|
||||
// The metrics counter for wrong credentials here really just means
|
||||
assertAuthMetricsMinimums(6, 3, 0, 3, 0, 0);
|
||||
|
||||
SolrInputDocument doc = new SolrInputDocument();
|
||||
doc.setField("id", "1");
|
||||
solrClient.add(collectionName, doc);
|
||||
solrClient.commit(collectionName);
|
||||
assertAuthMetricsMinimums(10, 5, 0, 5, 0, 0);
|
||||
|
||||
SolrQuery query = new SolrQuery();
|
||||
query.setQuery("*:*");
|
||||
|
@ -134,6 +137,7 @@ public class TestSolrCloudWithHadoopAuthPlugin extends SolrCloudTestCase {
|
|||
deleteReq.process(solrClient);
|
||||
AbstractDistribZkTestBase.waitForCollectionToDisappear(collectionName,
|
||||
solrClient.getZkStateReader(), true, true, 330);
|
||||
assertAuthMetricsMinimums(16, 8, 0, 8, 0, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.solr.cloud;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.codahale.metrics.Counter;
|
||||
import com.codahale.metrics.Meter;
|
||||
import com.codahale.metrics.Metric;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.codahale.metrics.Timer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Base test class for cloud tests wanting to track authentication metrics.
|
||||
* The assertions provided by this base class require a *minimum* count, not exact count from metrics.
|
||||
* Warning: Make sure that your test case does not break when beasting.
|
||||
*/
|
||||
public class SolrCloudAuthTestCase extends SolrCloudTestCase {
|
||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
private static final List<String> AUTH_METRICS_KEYS = Arrays.asList("errors", "requests", "authenticated",
|
||||
"passThrough", "failWrongCredentials", "failMissingCredentials", "requestTimes", "totalTime");
|
||||
private static final List<String> AUTH_METRICS_METER_KEYS = Arrays.asList("errors");
|
||||
private static final List<String> AUTH_METRICS_TIMER_KEYS = Collections.singletonList("requestTimes");
|
||||
private static final String METRICS_PREFIX_PKI = "SECURITY./authentication/pki.";
|
||||
private static final String METRICS_PREFIX = "SECURITY./authentication.";
|
||||
|
||||
/**
|
||||
* Used to check metric counts for PKI auth
|
||||
*/
|
||||
protected void assertPkiAuthMetricsMinimums(int requests, int authenticated, int passThrough, int failWrongCredentials, int failMissingCredentials, int errors) {
|
||||
assertAuthMetricsMinimums(METRICS_PREFIX_PKI, requests, authenticated, passThrough, failWrongCredentials, failMissingCredentials, errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to check metric counts for the AuthPlugin in use (except PKI)
|
||||
*/
|
||||
protected void assertAuthMetricsMinimums(int requests, int authenticated, int passThrough, int failWrongCredentials, int failMissingCredentials, int errors) {
|
||||
assertAuthMetricsMinimums(METRICS_PREFIX, requests, authenticated, passThrough, failWrongCredentials, failMissingCredentials, errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common test method to be able to check security from any authentication plugin
|
||||
* @param prefix the metrics key prefix, currently "SECURITY./authentication." for basic auth and "SECURITY./authentication/pki." for PKI
|
||||
*/
|
||||
private void assertAuthMetricsMinimums(String prefix, int requests, int authenticated, int passThrough, int failWrongCredentials, int failMissingCredentials, int errors) {
|
||||
List<Map<String, Metric>> metrics = new ArrayList<>();
|
||||
cluster.getJettySolrRunners().forEach(r -> {
|
||||
MetricRegistry registry = r.getCoreContainer().getMetricManager().registry("solr.node");
|
||||
assertNotNull(registry);
|
||||
metrics.add(registry.getMetrics());
|
||||
});
|
||||
|
||||
Map<String,Long> counts = new HashMap<>();
|
||||
AUTH_METRICS_KEYS.forEach(k -> {
|
||||
counts.put(k, sumCount(prefix, k, metrics));
|
||||
});
|
||||
|
||||
// check each counter
|
||||
assertExpectedMetrics(requests, "requests", counts);
|
||||
assertExpectedMetrics(authenticated, "authenticated", counts);
|
||||
assertExpectedMetrics(passThrough, "passThrough", counts);
|
||||
assertExpectedMetrics(failWrongCredentials, "failWrongCredentials", counts);
|
||||
assertExpectedMetrics(failMissingCredentials, "failMissingCredentials", counts);
|
||||
assertExpectedMetrics(errors, "errors", counts);
|
||||
if (counts.get("requests") > 0) {
|
||||
assertTrue("requestTimes count not > 1", counts.get("requestTimes") > 1);
|
||||
assertTrue("totalTime not > 0", counts.get("totalTime") > 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the actual metric is equal to or greater than the expected value, never less
|
||||
private void assertExpectedMetrics(int expected, String key, Map<String, Long> counts) {
|
||||
long cnt = counts.get(key);
|
||||
log.debug("Asserting that auth metrics count ({}) > expected ({})", cnt, expected);
|
||||
assertTrue("Expected " + key + " metric count to be " + expected + " or higher, but got " + cnt,
|
||||
cnt >= expected);
|
||||
}
|
||||
|
||||
// Have to sum the metrics from all three shards/nodes
|
||||
private long sumCount(String prefix, String key, List<Map<String, Metric>> metrics) {
|
||||
assertTrue("Metric " + prefix + key + " does not exist", metrics.get(0).containsKey(prefix + key));
|
||||
if (AUTH_METRICS_METER_KEYS.contains(key))
|
||||
return metrics.stream().mapToLong(l -> ((Meter)l.get(prefix + key)).getCount()).sum();
|
||||
else if (AUTH_METRICS_TIMER_KEYS.contains(key))
|
||||
return (long) ((long) 1000 * metrics.stream().mapToDouble(l -> ((Timer)l.get(prefix + key)).getMeanRate()).average().orElse(0.0d));
|
||||
else
|
||||
return metrics.stream().mapToLong(l -> ((Counter)l.get(prefix + key)).getCount()).sum();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue