如何实现ignite的安全插件(security plugin)

Ignite可以实现自定义的安全插件,以下实现在2.13.0测试可行。

1.如何实现自定义的安全插件

第一步:需要定义一个提供插件的类,它会被IgniteKernal在启动时调用,主要实现的是createComponent和plugin方法

public class NodeAuthenticationPluginProvider implements PluginConfiguration, PluginProvider<IgniteSecurityPluginProvider> {
 
    ...
 
    @Override
    public IgnitePlugin plugin() {
        return new NodeAuthenticationProcessor();
    }
 
    ...
 
    @Override
    public Object createComponent(PluginContext ctx, Class cls) {
        if (cls.isAssignableFrom(GridSecurityProcessor.class)) {
            return new NodeAuthenticationProcessor();
        }
        else {
            return null;
        }
    }
}
第二步:注册这个提供插件的类NodeAuthenticationPluginProvider到ignite

可以通过以下两种方法:

1)IgniteConfiguration.setPluginProviders()

2)Service loader

早期版本的官网推荐的是方式二,目前最新版本的官网推荐的是方式一(参考:https://ignite.apache.org/docs/latest/plugins)

鉴于方式一可以在官网查询,所以介绍下方式二如何注册:

配置META-INF/services/org.apache.ignite.plugin.PluginProvider

yourpackage.NodeAuthenticationPluginProvider

 第三步:实现具体的安全插件类 NodeAuthenticationProcessor, 具体的插件类需要实现GridSecurityProcessorIgnitePlugin 接口,它主要有几个需要override的函数

函数名 作用
authenticateNode 当任何ignite server还是clinet node需要加入ignite topology,这个函数会被调用:
如果node允许加入需要返回一个SecurityContextImpl对象
authenticate 当client需要连接到ignite server或者jetty server,那么这个函数会被调用;
这个和之前的client node的区别是这个client并不会加入到ignite topology里,只是连接;
典型的场景:运行自带的 control.sh / Rest API
enabled 必须返回true!
只有这个返回true,这个自定义的plugin才会被ignite使用,否则ignite会new一个NoOpIgniteSecurityProcessor对象
validateNode 当任何ignite server还是clinet node需要加入ignite topology,这个函数会被调用:
如果node允许加入需要返回一个null,如果不同意加入,那么返回一个IgniteNodeValidationResult
isGlobalNodeAuthentication 如果是false,只有coordinator 运行authentication;
如果是true,那么所有node都可以运行authentication;
securityContext 如果是true,那么所有node都可以运行authentication;当一个thin client连接到ignite的时候,这个函数就会被调用,如果允许thin client连接,那么返回一个SecurityContextImpl对象
以下的mock代码,比较简单,主要会对加入到ignite topology的server或者client node做一个校验,而且真实的校验动作发生在validateNode这个函数里

(为啥这样,是因为我们公司在ignite 2.7.5的版本就实现了这个plugin,当时借用的是validateNode这个函数,所以在新版本保持和老版本一致的功能...)

public class NodeAuthenticationProcessor implements GridSecurityProcessor, IgnitePlugin {

    ...
    
    @Override
    //Current implementation allows any Ignite node to join the Ignite Cluster Topology without authentication check
    public SecurityContext authenticateNode(ClusterNode clusterNode, SecurityCredentials securityCredentials) throws IgniteCheckedException {
        return new SecurityContextImpl(clusterNode.id(), new InetSocketAddress(F.first(clusterNode.addresses()), 0));
    }

    @Override
    //Current implementation allows any client to connect without authentication check
    public SecurityContext authenticate(AuthenticationContext authenticationContext) throws IgniteCheckedException {
        return new SecurityContextImpl(authenticationContext.subjectId(),authenticationContext.address());
    }

    @Override
    //Set to true to let the plugin inject into ignite
    public boolean enabled() {
        return true;
    }


    @Nullable
    @Override
    //It will be called to judge if a node can join topology
    public IgniteNodeValidationResult validateNode(ClusterNode clusterNode) {
        //Failed validation
        if(!nodeCanJoin(clusterNode)){
            String msg = "Access denied by " + this.getClass().getCanonicalName();
            return new IgniteNodeValidationResult(clusterNode.id(), msg, msg);
       //Pass validation
        }else
            return null;
    }

    //Current implementation always return SecurityContext because we don't really authenticate thin client.
    @Override
    public SecurityContext securityContext(UUID subjId) {
        return new SecurityContextImpl(subjId,null);
    }
    
    @Override
    /*
    false means only coordinator should run the authentication for joining node;
    true means all nodes can run the authentication for joining node;
    */
    public boolean isGlobalNodeAuthentication() {
        return false;
    }
    
    ...
}

第四步:传递一个SecurityCredentials 给ignite node

虽然我们不用SecurityCredentials 来验证一个node能不能加入到ignite cluster,但是在你实现了pulgin后,要想它能work,必须传递一个SecurityCredentials 给 ignite node;

因为在 ignite 2.13.0的源码里, 第一个加入到topology的node会检查自己是否有SecurityCredentials(在 localAuthentication() 这个函数里),源代码具体如下:

private void joinTopology() throws IgniteSpiException {
     ...
     while (true) {
            if (!sendJoinRequestMessage(joinReqMsg)) {
                if (log.isDebugEnabled())
                    log.debug("Join request message has not been sent (local node is the first in the topology).");
 
                if (!auth && spi.nodeAuth != null)
                   //call localAuthentication to check if it contains SecurityCredentials
                   localAuthentication(locCred);
     ...
     }
 
private void localAuthentication(SecurityCredentials locCred) {
        try {
            locNode.setAttributes(withSecurityContext(
                authenticateLocalNode(locNode, locCred, spi.nodeAuth),
                locNode.attributes(),
                spi.marshaller()
            ));
        }
        catch (IgniteException | IgniteCheckedException e) {
            throw new IgniteSpiException("Failed to authenticate local node (will shutdown local node).", e);
        }
    }
...

所以需要有一个方法来传递SecurityCredentials,比较常见的一个方式(来自于网络搜索)是定义一个继承自TcpDiscoverySpi 类去传递SecurityCredentials,代码如下:

public class CustomTcpDiscoverySpi extends TcpDiscoverySpi {
 
   
    //The SecurityCredentials is added in order to pass assert code in ignite;
    
    private final SecurityCredentials securityCredentials = new SecurityCredentials();
 
    @Override
    protected void initLocalNode(int srvPort, boolean addExtAddrAttr) {
       super.initLocalNode(srvPort, addExtAddrAttr);
       passSecurityCredentialsToNodeAttributes();
    }
 
    private void passSecurityCredentialsToNodeAttributes() {
        Map<String,Object> attributes = new HashMap<>(locNode.getAttributes());
        attributes.put(IgniteNodeAttributes.ATTR_SECURITY_CREDENTIALS, securityCredentials);
        this.locNode.setAttributes(attributes);
    }
}

 然后把以上自定义的类设置到ignite-config.xml里

...
<bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
       ...
        <property name="discoverySpi">
            <bean class="com.tekcomms.ia.provisioning.lookup.jcache.CustomTcpDiscoverySpi">
       ...

2.自定义的安全插件是如何在ignite work的

参考以下Ignite的源码(IgniteKernal.class)

private GridProcessor securityProcessor() throws IgniteCheckedException {
        
       /*If custom plguin proveider is defined, it's createComponent method will be called;
         In our case, it will call NodeAuthenticationPluginProvider.createComponent to
         return custom defined NodeAuthenticationProcessor object which implements GridSecurityProcessor.
       */
       GridSecurityProcessor prc = createComponent(GridSecurityProcessor.class, ctx);
 
        if (cfg.isAuthenticationEnabled() && !(prc instanceof IgniteAuthenticationProcessor)) {
            throw new IgniteCheckedException("Invalid security configuration: both authentication is enabled" +
                " and external security plugin is provided.");
        }
 
       //Custome implemented GridSecurityProcessor will be pass to create IgniteSecurityProcessor object.
       return prc != null && prc.enabled()
            ? new IgniteSecurityProcessor(ctx, prc)
            : new NoOpIgniteSecurityProcessor(ctx);
    }

 

posted on 2022-12-11 23:23  cherryjing0629  阅读(260)  评论(0编辑  收藏  举报

导航