实现思路
ACL权限策略表 : 通过扩展Elasticsearch 元数据(cluster_state metadata) 实现ACL规则的注册与管理维护机制。
引擎权限检测钩子: 实现自定义引擎,通过钩子从ACL表获取当前索引权限,检测引擎操作是否被允许。
Engine Plugin
Elasticsearch的Engine Plugin是一种插件,它允许开发人员通过自定义索引内核引擎来扩展Elasticsearch的核心功能。Engine Plugin允许你以更底层的方式介入Elasticsearch的索引和搜索过程。
public interface EnginePlugin {
Optional<EngineFactory> getEngineFactory(IndexSettings indexSettings);
}
实现自己的Elasticsearch 引擎插件
public class MyEnginePlugin extends Plugin implements EnginePlugin {
public Optional<EngineFactory> getEngineFactory(final IndexSettings indexSettings) {
return Optional.of(config -> new PermissionInternalEngine(config);
}
}
自定义引擎,放置钩子,添加权限检测
通过覆盖索引策略和删除策略,增加Checker拦截机制
public class PermissionInternalEngine extends InternalEngine {
/**
* 返回 EngineOperation 是否被允许
*/
Function<EngineOperation, Boolean> checker;
public PermissionInternalEngine(EngineConfig engineConfig, Function<EngineOperation, Boolean> checker) {
super(engineConfig);
this.checker = checker;
}
/**
* 索引策略
*/
@Override
protected IndexingStrategy indexingStrategyForOperation(final Index index) throws IOException {
boolean allowed = checker.apply(new EngineOperation(shardId.getIndexName(), index.version(), index));
if (allowed == false) {
final PermissionEngineException error = new PermissionEngineException(shardId, index.seqNo(), lookupPrimaryTerm(index.seqNo()));
return IndexingStrategy.skipDueToVersionConflict(error, false, index.version());
} else {
return super.indexingStrategyForOperation(index);
}
}
/**
* 删除策略
*/
protected InternalEngine.DeletionStrategy deletionStrategyForOperation(final Delete delete) throws IOException {
boolean allowed = checker.apply(new EngineOperation(shardId.getIndexName(), Versions.MATCH_DELETED, delete));
if (allowed == false) {
final PermissionEngineException error = new PermissionEngineException(shardId, delete.seqNo(), lookupPrimaryTerm(delete.seqNo()));
return DeletionStrategy.skipDueToVersionConflict(error, delete.version(), false);
} else {
return super.deletionStrategyForOperation(delete);
}
}
...
}
ACL 权限策略设计
这里设计一种枚举类型,代表是否能创建、更新、删除文档,然后通过组合构建不同的权限策略
public enum PermissionPolicy {
/*
* 不允许新增、修改、删除
*/
STRICT(false, false, false),
/*
* 仅允许新增,不允许更新、删除
*/
ALLOW_APPEND_ONLY(true, false, false),
/*
* 允许新增、更新,不允许删除
*/
ALLOW_CREATE_UPDATE(true, true, false),
/*
* 仅允许更新,不允许新增、删除
*/
ALLOW_UPDATE_ONLY(false, true, false),
/**
* 仅允许删除,不允许新增、更新
*/
ALLOW_DELETE_ONLY(false, false, true);
private final boolean create;
private final boolean update;
private final boolean delete;
PermissionPolicy(boolean create, boolean update, boolean delete) {
this.create = create;
this.update = update;
this.delete = delete;
}
public boolean canCreate() {
return create;
}
public boolean canUpdate() {
return update;
}
public boolean canDelete() {
return delete;
}
public static PermissionPolicy fromString(String str) {
if ("STRICT".equalsIgnoreCase(str)) {
return STRICT;
} else if ("ALLOW_APPEND_ONLY".equalsIgnoreCase(str)) {
return ALLOW_APPEND_ONLY;
} else if ("ALLOW_UPDATE_ONLY".equalsIgnoreCase(str)) {
return ALLOW_UPDATE_ONLY;
} else if ("ALLOW_CREATE_UPDATE".equalsIgnoreCase(str)) {
return ALLOW_CREATE_UPDATE;
} else if ("ALLOW_DELETE_ONLY".equalsIgnoreCase(str)) {
return ALLOW_DELETE_ONLY;
}
throw new IllegalArgumentException("unkonw operation policy : " + str);
}
}
索引权限策略
通过定义索引名或索引模式挂接到策略
/**
* 索引权限策略
*/
public class IndexPermissionPolicy {
private final PermissionPolicy policy;
private final String pattern;
public IndexPermissionPolicy(PermissionPolicy policy, String pattern) {
this.policy = policy;
this.pattern = pattern;
}
public boolean isEmpty() {
return policy == null && pattern == null;
}
public static IndexPermissionPolicy EMPTY = new IndexPermissionPolicy(null, null);
/**
* 引擎层权限检查
*
* @param index index
* @param version version
* @param operation operation
* @return allowed
*/
public boolean canEngineOperation(String index, long version, Engine.Operation operation) {
if (Regex.simpleMatch(pattern, index) == false) {
return true;
}
switch (operation.operationType()) {