notes on locking down solr - krickert/search-api GitHub Wiki
Here's a detailed implementation outline and complete example of how to secure Solr using custom security plugins and a PostFilter, integrated with an external ACL (Access Control List) service via a cookie token passed from your API.
- Solr Security Plugin: Authenticates requests based on cookie headers and integrates ACL filtering.
- ACL Micronaut Service: Provides ACL lists for authenticated users from sources (GitLab, Confluence, SharePoint).
- Custom Update Handler: Validates ACL fields on documents during indexing.
- Custom Query Parser or PostFilter: Filters results based on ACL.
Define a generic ACL service interface first.
public interface ACLService {
/**
* Resolve ACLs for user token.
* @param token the session token from cookie.
* @return Set of ACL strings.
*/
Set<String> getUserACLs(String token);
}
Example JSON REST Response:
{
"acls": ["gitlab/project1", "confluence/space2", "sharepoint/site3"]
}
Create a plugin extending AuthenticationPlugin
to handle authentication via cookies and set the user principal.
import org.apache.solr.security.AuthenticationPlugin;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
public class CookieTokenAuthenticationPlugin extends AuthenticationPlugin {
private ACLService aclService;
public CookieTokenAuthenticationPlugin(ACLService aclService) {
this.aclService = aclService;
}
@Override
public void init(Map<String, Object> pluginConfig) {}
@Override
public boolean doAuthenticate(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws Exception {
String cookieToken = extractTokenFromCookie(request);
if (cookieToken == null || aclService.getUserACLs(cookieToken).isEmpty()) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
return false;
}
request.setAttribute(AuthenticationPlugin.USER, cookieToken);
filterChain.doFilter(request, response);
return true;
}
private String extractTokenFromCookie(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("auth_token".equals(cookie.getName())) {
return cookie.getValue();
}
}
}
return null;
}
@Override
public void close() {}
}
Enforce ACL validation on every document.
import org.apache.solr.update.processor.UpdateRequestProcessor;
import org.apache.solr.update.processor.UpdateRequestProcessorFactory;
import org.apache.solr.common.SolrInputDocument;
public class ACLValidationProcessorFactory extends UpdateRequestProcessorFactory {
@Override
public UpdateRequestProcessor getInstance(SolrQueryRequest req, SolrQueryResponse rsp, UpdateRequestProcessor next) {
return new UpdateRequestProcessor(next) {
@Override
public void processAdd(AddUpdateCommand cmd) throws IOException {
SolrInputDocument doc = cmd.getSolrInputDocument();
if (doc.getFieldValue("acl") == null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "ACL field is required.");
}
super.processAdd(cmd);
}
};
}
}
Add to solrconfig.xml
:
<updateRequestProcessorChain name="aclValidationChain">
<processor class="com.example.ACLValidationProcessorFactory" />
<processor class="solr.RunUpdateProcessorFactory" />
</updateRequestProcessorChain>
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.*;
import org.apache.solr.search.*;
import java.io.IOException;
import java.util.Set;
public class ACLPostFilter extends ExtendedQueryBase implements PostFilter {
private final Set<String> userACLs;
public ACLPostFilter(Set<String> userACLs) {
this.userACLs = userACLs;
}
@Override
public DelegatingCollector getFilterCollector(IndexSearcher searcher) {
return new DelegatingCollector() {
private SortedDocValues aclValues;
@Override
protected void doSetNextReader(LeafReaderContext context) throws IOException {
aclValues = DocValues.getSorted(context.reader(), "acl");
super.doSetNextReader(context);
}
@Override
public void collect(int doc) throws IOException {
if (aclValues.advanceExact(doc)) {
String docAcl = aclValues.lookupOrd(aclValues.ordValue()).utf8ToString();
if (userACLs.contains(docAcl)) {
super.collect(doc);
}
}
}
};
}
@Override
public boolean getCache() {
return false;
}
@Override
public boolean getCacheSep() {
return false;
}
@Override
public int hashCode() {
return userACLs.hashCode();
}
@Override
public boolean equals(Object other) {
return other instanceof ACLPostFilter && userACLs.equals(((ACLPostFilter) other).userACLs);
}
}
For easy client-side integration, create a custom parser.
import org.apache.solr.search.QParser;
import org.apache.solr.search.QParserPlugin;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.request.SolrQueryRequest;
public class ACLQueryParserPlugin extends QParserPlugin {
private ACLService aclService;
public ACLQueryParserPlugin(ACLService aclService) {
this.aclService = aclService;
}
@Override
public QParser createParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
String token = (String) req.getContext().get("user");
Set<String> userACLs = aclService.getUserACLs(token);
return new QParser(qstr, localParams, params, req) {
@Override
public Query parse() throws SyntaxError {
Query userQuery = subQuery(qstr, null).getQuery();
ACLPostFilter aclFilter = new ACLPostFilter(userACLs);
return new BooleanQuery.Builder()
.add(userQuery, BooleanClause.Occur.MUST)
.add(aclFilter, BooleanClause.Occur.FILTER)
.build();
}
};
}
}
Register in solrconfig.xml
:
<queryParser name="aclParser" class="com.example.ACLQueryParserPlugin"/>
Client sends queries with token in the cookie:
GET /solr/collection/select?q={!aclParser}search+terms HTTP/1.1
Cookie: auth_token=abc123xyz
- Package the ACL service as a Micronaut microservice.
- Deploy the Solr plugins into Solr's
lib
directory. - Update
solrconfig.xml
to activate plugins and parsers. - Ensure ACL data is consistently updated and cached for performance.
- Implement caching (Redis, Caffeine) in the ACL service to optimize lookups.
- Extend this example for real-world ACL sources (GitLab API, Confluence REST, SharePoint Graph API).
- Enhance logging and monitoring for security events (unauthorized attempts, ACL lookup failures).
- Add unit and integration tests for robust coverage.
This detailed example should give you a comprehensive foundation to implement a secure Solr environment leveraging your existing expertise and infrastructure.