Elasticsearch 内核研发 - 如何给引擎加上权限保护

53 阅读4分钟

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

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

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

本文将介绍如何通过扩展 Elasticsearch 插件机制扩展内核引擎添加ACL权限控制。

实现思路

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()) {
            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的核心引擎功能,从而实现各种高级功能和定制化需求。