
io.fabric8.elasticsearch.plugin.acl.ACLDocumentManager Maven / Gradle / Ivy
/**
* Copyright (C) 2015 Red Hat, Inc.
*
* Licensed 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 io.fabric8.elasticsearch.plugin.acl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.action.DocWriteRequest.OpType;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
import org.elasticsearch.action.update.UpdateRequestBuilder;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.util.concurrent.ThreadContext.StoredContext;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.threadpool.ThreadPool;
import com.floragunn.searchguard.action.configupdate.ConfigUpdateAction;
import com.floragunn.searchguard.action.configupdate.ConfigUpdateRequest;
import com.floragunn.searchguard.action.configupdate.ConfigUpdateResponse;
import com.floragunn.searchguard.support.ConfigConstants;
import io.fabric8.elasticsearch.plugin.ConfigurationSettings;
import io.fabric8.elasticsearch.plugin.OpenshiftRequestContextFactory.OpenshiftRequestContext;
import io.fabric8.elasticsearch.plugin.PluginClient;
import io.fabric8.elasticsearch.plugin.PluginSettings;
import io.fabric8.elasticsearch.plugin.acl.SearchGuardRoles.Roles;
import io.fabric8.elasticsearch.plugin.acl.SearchGuardRolesMapping.RolesMapping;
/**
* Manages process of loading and updating the ACL Documents
* for a user request
*
*/
public class ACLDocumentManager implements ConfigurationSettings {
private static final String [] CONFIG_DOCS = {SEARCHGUARD_ROLE_TYPE, SEARCHGUARD_MAPPING_TYPE};
private static final Logger LOGGER = Loggers.getLogger(ACLDocumentManager.class);
private final ReentrantLock lock = new ReentrantLock();
private final String searchGuardIndex;
private final PluginClient client;
private final SearchGuardSyncStrategyFactory documentFactory;
private final ConfigurationLoader configLoader;
private final ThreadContext threadContext;
public ACLDocumentManager(final PluginClient client, final PluginSettings settings, final SearchGuardSyncStrategyFactory documentFactory, ThreadPool threadPool) {
this.searchGuardIndex = settings.getSearchGuardIndex();
this.client = client;
this.documentFactory = documentFactory;
this.threadContext = threadPool.getThreadContext();
this.configLoader = new ConfigurationLoader(client.getClient(), threadPool, settings.getSettings());
}
@SuppressWarnings("rawtypes")
interface ACLDocumentOperation{
void execute(Collection docs);
BulkRequest buildRequest(Client client, BulkRequestBuilder builder, Collection docs) throws IOException;
}
private void logContent(final String message, final String type, final ToXContent content) throws IOException{
if(LOGGER.isDebugEnabled()) {
LOGGER.debug(message, type, XContentHelper.toString(content));
}
}
private void logDebug(final String message, final Object obj) {
if(LOGGER.isDebugEnabled()) {
LOGGER.debug(message, obj);
}
}
@SuppressWarnings("rawtypes")
class SyncFromContextOperation implements ACLDocumentOperation {
private OpenshiftRequestContext context;
public SyncFromContextOperation(OpenshiftRequestContext context) {
this.context = context;
}
@Override
public void execute(Collection docs) {
LOGGER.debug("Syncing from context to ACL...");
for (SearchGuardACLDocument doc : docs) {
if(ConfigurationSettings.SEARCHGUARD_MAPPING_TYPE.equals(doc.getType())){
RolesMappingSyncStrategy rolesMappingSync = documentFactory.createRolesMappingSyncStrategy((SearchGuardRolesMapping) doc);
rolesMappingSync.syncFrom(context);
} else if(ConfigurationSettings.SEARCHGUARD_ROLE_TYPE.equals(doc.getType())) {
RolesSyncStrategy rolesSync = documentFactory.createRolesSyncStrategy((SearchGuardRoles) doc);
rolesSync.syncFrom(context);
}
}
}
@Override
public BulkRequest buildRequest(Client client, BulkRequestBuilder builder, Collection docs) throws IOException{
for (SearchGuardACLDocument doc : docs) {
logContent("Updating {} to: {}", doc.getType(), doc);
Map content = new HashMap<>();
content.put(doc.getType(), new BytesArray(XContentHelper.toString(doc)));
UpdateRequestBuilder update = client
.prepareUpdate(searchGuardIndex, doc.getType(), SEARCHGUARD_CONFIG_ID)
.setDoc(content);
if(doc.getVersion() != null) {
update.setVersion(doc.getVersion());
}
builder.add(update.request());
}
return builder.request();
}
}
@SuppressWarnings("rawtypes")
class ExpireOperation implements ACLDocumentOperation {
private long now;
public ExpireOperation(long currentTimeMillis) {
this.now = currentTimeMillis;
}
@Override
public void execute(Collection docs) {
LOGGER.debug("Expiring ACLs older then {}", now);
for (SearchGuardACLDocument doc : docs) {
if(ConfigurationSettings.SEARCHGUARD_MAPPING_TYPE.equals(doc.getType())){
SearchGuardRolesMapping mappings = (SearchGuardRolesMapping) doc;
for (RolesMapping mapping : mappings) {
//assume if the value is there its intentional
String expire = mapping.getExpire();
if(expire != null && NumberUtils.isNumber(expire) && Long.parseLong(expire) < now) {
logDebug("Expiring rolesMapping: {}", mapping);
mappings.removeRolesMapping(mapping);
}
}
} else if(ConfigurationSettings.SEARCHGUARD_ROLE_TYPE.equals(doc.getType())) {
SearchGuardRoles roles = (SearchGuardRoles) doc;
for (Roles role : roles) {
//assume if the value is there its intentional
String expire = role.getExpire();
if(expire != null && Long.parseLong(expire) < now) {
logDebug("Expiring role: {}", role);
roles.removeRole(role);
}
}
}
}
}
@Override
public BulkRequest buildRequest(Client client, BulkRequestBuilder builder, Collection docs) throws IOException{
for (SearchGuardACLDocument doc : docs) {
logContent("Expired doc {} to be: {}", doc.getType(), doc);
Map content = new HashMap<>();
content.put(doc.getType(), new BytesArray(XContentHelper.toString(doc)));
IndexRequestBuilder indexBuilder = client
.prepareIndex(searchGuardIndex, doc.getType(), SEARCHGUARD_CONFIG_ID)
.setOpType(OpType.INDEX)
.setSource(content);
builder.add(indexBuilder.request());
}
return builder.request();
}
}
public void expire() {
syncAcl(new ExpireOperation(System.currentTimeMillis()));
}
public void syncAcl(OpenshiftRequestContext context) {
if(!syncAcl(new SyncFromContextOperation(context))){
LOGGER.warn("Unable to sync ACLs for request from user: {}", context.getUser());
}
}
private boolean syncAcl(ACLDocumentOperation operation) {
//try up to 30 seconds and then continue
for (int n : new int [] {1 , 1 , 2 , 3 , 5 , 8}) {
if(trySyncAcl(operation)) {
return true;
}
try {
if(LOGGER.isTraceEnabled()) {
LOGGER.trace("Sleeping for {}(s)", n);
}
Thread.sleep(n * 1000);
} catch (InterruptedException e) {
LOGGER.error("There was an error while trying the sleep the syncACL operation", e);
}
}
return false;
}
public boolean trySyncAcl(ACLDocumentOperation operation) {
LOGGER.debug("Syncing the ACL to ElasticSearch");
try (StoredContext ctx = threadContext.stashContext()) {
threadContext.putHeader(ConfigConstants.SG_CONF_REQUEST_HEADER, "true");
lock.lock();
@SuppressWarnings("rawtypes")
Collection docs = loadAcls();
if(docs.size() < 2) {
return false;
}
operation.execute(docs);
return isSuccessfulWrite(writeAcl(operation, docs));
} catch (Exception e) {
LOGGER.error("Exception while syncing ACL to Elasticsearch", e);
} finally {
lock.unlock();
}
return false;
}
@SuppressWarnings("rawtypes")
private Collection loadAcls() throws Exception {
LOGGER.debug("Loading SearchGuard ACL...waiting up to 30s");
Map> loadedDocs = configLoader.load(CONFIG_DOCS, 30, TimeUnit.SECONDS);
Collection docs = new ArrayList<>(loadedDocs.size());
for (Entry> item : loadedDocs.entrySet()) {
Settings settings = item.getValue().v1();
Long version = item.getValue().v2();
Map original = settings.getAsStructuredMap();
if(LOGGER.isDebugEnabled()){
logContent("Read in {}: {}", item.getKey(), settings);
}
switch (item.getKey()) {
case SEARCHGUARD_ROLE_TYPE:
docs.add(new SearchGuardRoles(version).load(original));
break;
case SEARCHGUARD_MAPPING_TYPE:
docs.add(new SearchGuardRolesMapping(version).load(original));
break;
}
}
return docs;
}
@SuppressWarnings("rawtypes")
private BulkResponse writeAcl(ACLDocumentOperation operation, Collection docs) throws Exception {
BulkRequestBuilder builder = client.getClient().prepareBulk().setRefreshPolicy(RefreshPolicy.WAIT_UNTIL);
BulkRequest request = operation.buildRequest(this.client.getClient(), builder, docs);
client.addCommonHeaders();
return this.client.getClient().bulk(request).actionGet();
}
private boolean isSuccessfulWrite(BulkResponse response) {
if(!response.hasFailures()) {
ConfigUpdateRequest confRequest = new ConfigUpdateRequest(SEARCHGUARD_INITIAL_CONFIGS);
client.addCommonHeaders();
try {
ConfigUpdateResponse cur = this.client.getClient().execute(ConfigUpdateAction.INSTANCE, confRequest).actionGet();
final int totNodes = cur.getNodes().size();
if (totNodes > 0) {
LOGGER.debug("Successfully reloaded config with '{}' nodes", totNodes);
}else {
LOGGER.warn("Failed to reloaded configs", totNodes);
}
}catch(Exception e) {
LOGGER.error("Unable to notify of an ACL config update", e);
}
return true;
} else {
LOGGER.debug("Unable to write ACL {}", response.buildFailureMessage());
}
return false;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy