Skip to main content

ES内核开发:数据权限实现原理

· 7 min read
Ethan
引言

本文将介绍纳速云扩展Elasticsearch数据权限的实现原理,适合具备高级 Elasticsearch 开发技能的开发者。阅读者需要熟悉 Elasticsearch 的核心概念,具备相关的编程和调试能力。

在Elasticsearch中添加权限控制可以起到以下作用:

  1. 数据保护:对于敏感的数据,例如用户信息、支付记录等。通过实施权限控制,可以确保只有经过授权的用户能够访问这些数据,从而保护数据的安全性和隐私。
  2. 避免误操作:在一个团队或组织中,不同的人可能具有不同的职责和权限需求。通过权限控制,可以限制用户只能执行其职责范围内的操作,避免误操作导致的数据丢失或损坏。
  3. 合规性要求:许多行业和法规对数据访问和处理提出了严格的要求。通过实施权限控制,可以确保组织符合相关的合规性标准,避免可能的法律责任和罚款。
  4. 防止未经授权的访问:没有权限控制的系统容易受到未经授权的访问和攻击。通过实施权限控制,可以有效地防止未经授权的用户访问系统,并提高系统的安全性。
  5. 数据完整性:通过权限控制,可以限制用户对数据的修改和删除操作,从而保护数据的完整性,防止数据被恶意篡改或损坏。

综上所述,添加权限控制是保护数据安全、确保系统运行稳定、满足合规性要求的关键措施,对于任何需要保护数据安全和确保系统稳定的环境都是必不可少的。

实现思路

info

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 CloudPlugin 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()) {
case INDEX:
return version == 1 ? policy.canCreate() : policy.canUpdate();
case DELETE:
return policy.canDelete();
}
return true;
}

/**
* DDL检查 是否允许删除索引
*
* @param index index
* @return allowed
*/
public boolean canDeleteIndex(String index) {
boolean matched = Regex.simpleMatch(pattern, index) || Regex.simpleMatch(index, pattern);
return matched ? policy.canDelete() : true;
}

@Override
public String toString() {
return policy == null ? null : policy + ":" + pattern;
}

}

通过以上步骤,你可以成功地实现一个定制化的Elasticsearch引擎插件,以满足特定的业务需求或功能需求。总的来说,ES Engine Plugin提供了一种灵活和强大的方式,允许开发人员定制和扩展Elasticsearch的核心引擎功能,从而实现各种高级功能和定制化需求。