# 安全
# 1.认证
# 1.1.Ignite认证
通过将节点配置中的authenticationEnabled
属性配置为true
,可以开启Ignite的认证功能。但是开启认证需要至少一个数据区开启了持久化存储。
启动的第一个节点必须启用身份认证。启动时,Ignite会创建一个名为ignite
和密码为ignite
的账户,该账户可用于创建其他账户。
可以使用下面的命令管理用户:
# 1.2.在客户端中提供凭据
当在集群中配置认证后,所有的客户端都需要提供用户凭据,对于特定的客户端,请参见下面章节的介绍:
# 2.SSL/TLS
本章节说明如何在集群节点之间(服务端和客户端节点)与接入集群的瘦客户端之间配置SSL/TLS加密。
# 2.1.注意事项
为了确保足够的安全性,建议每个节点(服务端或客户端)在节点的密钥库(包括私钥)中都有自己的唯一证书。所有其他服务端节点都必须信任此证书。
# 2.2.节点的SSL/TLS
要为集群节点启用SSL/TLS,需要在节点配置中配置SSLContext
工厂。可以使用默认的工厂类org.apache.ignite.ssl.SslContextFactory
,该工厂使用可配置的密钥库初始化SSL上下文。
警告
确认使用的JVM版本解决了SSL连接中可能出现死锁的问题,如果JVM受到了该问题的影响但无法更新,则需将TcpDiscoverySpi.soLinger
参数设置为非负值。
以下是SslContextFactory
配置示例:
密钥库必须包含节点的证书,包括其私钥。信任库必须包含所有其他集群节点的信任证书。
可以定义其他的属性,例如密钥算法,密钥存储类型或信任管理器,具体请参见SslContextFactory属性章节的介绍。
启动节点后,应该在日志中看到以下消息:
Security status [authentication=off, tls/ssl=on]
# 2.3.瘦客户端和JDBC/ODBC的SSL/TLS
Ignite对所有客户端(包括瘦客户端和JDBC/ODBC连接)使用相同的SSL/TLS属性,这些属性是在客户端连接器配置中配置的。客户端连接器配置是通过IgniteConfiguration.clientConnectorConfiguration
属性定义的。
要为客户端连接启用SSL/TLS,需要将sslEnabled
属性设置为true
并在客户端连接器配置中提供SslContextFactory
。节点配置的SSLContextFactory
可以重用,也可以配置仅用于客户端连接的SSLContext
工厂。
然后,以相同方式在客户端上配置SSL,具体请参见特定客户端的文档。
下面是在客户端连接设置中配置SslContextFactory
的示例:
如果要重用为节点配置的SSLContext
工厂,则只需将sslEnabled
属性设置为true
,ClientConnectorConfiguration
会在IgniteConfiguration
中查找SSLContext
配置:
# 2.4.禁用证书验证
某些情况下需要禁用证书认证(比如连接到一个自签名的服务器时),这可以通过使用禁用的信任管理器实现,它可以通过SslContextFactory.getDisabledTrustManager
获得。
# 2.5.升级证书
如果SSL证书即将过期或已被破坏,则可以在不关闭整个集群的情况下安装新证书。
以下是更新证书的过程:
首先,确保所有集群节点都信任新证书。如果信任库包含根证书,并且新证书由同一CA签发,则可能不需要此步骤。对不信任证书的节点重复以下过程:
a.将新证书导入节点的信任库; b.正常重启节点; c.对所有服务端节点重复这些步骤。
将新证书(包括私钥)导入到相应节点的密钥库中,删除旧证书后正常重启节点,对要更新的所有证书重复此过程。
# 2.6.SslContextFactory属性
SslContextFactory
支持如下的属性:
属性 | 描述 | 默认值 |
---|---|---|
keyAlgorithm | 设置密钥管理器算法,用于创建密钥管理器。 | SunX509 |
keyStoreFilePath | 密钥库文件路径,该参数为必须参数,否则SSL上下文没有密钥管理器无法初始化 | 无 |
keyStorePassword | 密钥库密码 | 无 |
keyStoreType | 密钥库类型 | JKS |
protocol | 安全传输协议,支持的协议见这里 | TLS |
trustStoreFilePath | 信任库文件路径 | 无 |
trustStorePassword | 信任库密码 | 无 |
trustStoreType | 信任库类型 | JKS |
trustManagers | 配置好的信任管理器 | 无 |
# 3.透明数据加密
# 3.1.介绍
# 3.1.1.概述
透明数据加密(TDE)使得用户可以对静态数据进行加密。
如果开启了Ignite的原生持久化,加密可以在表/缓存级开启,其中下面的数据会被加密:
- 磁盘上的数据;
- WAL记录。
如果开启了表/缓存级加密,Ignite会生成一个密钥(叫做缓存加密密钥),然后使用这个密钥来对缓存的数据进行加密/解密。缓存加密密钥由系统缓存持有,并且用户无法访问。如果该密钥需要发送到其它节点或者保存到磁盘(节点停止),它会使用用户提供的密钥(主密钥)进行加密。
每个服务端节点中的配置都要指定相同的主密钥,为了保证这一点,一种方法是将JKS文件从一个节点复制到其他节点。如果尝试使用不同的密钥启用TDE,则具有不同密钥的节点将无法加入集群(摘要不同,该节点会被拒绝)。
Ignite使用的是JDK提供的加密算法,AES/CBC/PKCS5Padding
用于WAL记录的加密,AES/CBC/NoPadding
用于加密磁盘上的数据页面,要了解更多实现的细节,可以看KeystoreEncryptionSpi。
# 3.1.2.限制
在上生产之前,TDE有一些限制需要了解:
加密
- 无法对现有的缓存/表进行加密/解密。
快照和恢复
- 快照操作期间加密、主密钥/缓存组密钥变更是不允许的。
# 3.1.3.配置
要开启集群的加密功能,需要在每个服务端节点的配置中提供一个主密钥,配置示例如下:
配置好主密钥后,就可以向下面这样为每个缓存开启加密了:
# 3.1.4.主密钥生成示例
带有主密钥的密钥存储库可以使用keytool
工具来生成,如下:
user:~/tmp:[]$ java -version
java version "1.8.0_161"
Java(TM) SE Runtime Environment (build 1.8.0_161-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)
user:~/tmp:[]$ keytool -genseckey \
-alias ignite.master.key \
-keystore ./ignite_keystore.jks \
-storetype PKCS12 \
-keyalg aes \
-storepass mypassw0rd \
-keysize 256
user:~/tmp:[]$ keytool \
-storepass mypassw0rd \
-storetype PKCS12 \
-keystore ./ignite_keystore.jks \
-list
Keystore type: PKCS12
Keystore provider: SunJSSE
Your keystore contains 1 entry
ignite.master.key, 07.11.2018, SecretKeyEntry,
# 3.1.5.代码示例
# 3.2.主密钥轮换
# 3.2.1.概述
主密钥会对缓存键进行加密,加密的缓存键存储在磁盘上,具体参见上一章节的介绍。
Ignite 2.9引入了主密钥更改过程,这样用户就可以通过重新加密缓存键将Ignite切换到新的主密钥。
如果主密钥已被泄露或在加密期结束时(密钥有效期),则需要进行主密钥轮换。
# 3.2.2.前提条件
每个服务端节点的EncryptionSpi
都应持有新的主密钥,集群应处于激活状态。
# 3.2.3.配置
主密钥通过名字进行标识,当集群第一次启动时,会使用配置中指定的主密钥名,具体请参见TDE配置章节的介绍。
节点会在集群第一次激活以及每次主密码变更时把主密码名保存在磁盘上(本地MetaStorage
),如果某个节点重启,他会从本地的MetaStorage
中读取主密码名。
# 3.2.4.变更主密码
提示
在主密码更改过程中,将禁止缓存的启动和新节点的加入。
Ignite的如下接口支持修改主密码:
- 命令行工具;
- JMX;
- 编程方式。
# 3.2.4.1.命令行工具
Ignite中位于$IGNITE_HOME/bin
目录下的control.sh|bat
脚本,可以从命令行对主密码的变更过程进行管理,见下面的命令:
# Print the current master key name.
control.sh|bat --encryption get_master_key_name
# Change the master key.
control.sh|bat --encryption change_master_key newMasterKeyName
# 3.2.4.2.JMX
还可以通过EncryptionMXBean
接口来管理主密码变更过程:
方法 | 描述 |
---|---|
getMasterKeyName() | 获取当前主密码名 |
changeMasterKey(String masterKeyName) | 开启主密码变更过程 |
# 3.2.4.3.编程方式
主密码变更过程也可以通过编程方式来管理:
// Gets the current master key name.
String name = ignite.encryption().getMasterKeyName();
// Starts master key change process.
IgniteFuture<Void> future = ignite.encryption().changeMasterKey("newMasterKeyName");
# 3.2.5.恢复故障节点的主密码
如果在主密钥更改过程中某个节点故障,它将无法使用旧的主密钥加入集群。在启动恢复期间,该节点应重新加密本地组密钥。实际的主密码名应在节点启动前通过系统属性IGNITE_MASTER_KEY_NAME_TO_CHANGE_BEFORE_STARTUP
设置,当集群激活后,节点会将主密码名保存在本地的MetaStorage
中。
提示
建议在成功恢复后删除系统该属性。否则当节点重启时,可能会使用无效的主密钥名。
# 3.2.6.附加的主密钥生成示例
Ignite根据JDK提供的加密算法实现提供了KeystoreEncryptionSpi
,具体请参见密钥库主密码生成示例,使用keytool
还可以生成一个附加的主密钥,示例如下:
user:~/tmp:[]$ keytool \
-storepass mypassw0rd \
-storetype PKCS12 \
-keystore ./ignite_keystore.jks \
-list
Keystore type: PKCS12
Keystore provider: SunJSSE
Your keystore contains 1 entry
ignite.master.key, 15.01.2019, SecretKeyEntry,
user:~/tmp:[]$ keytool -genseckey \
-alias ignite.master.key2 \
-keystore ./ignite_keystore.jks \
-storetype PKCS12 \
-keyalg aes \
-storepass mypassw0rd \
-keysize 256
user:~/tmp:[]$ keytool \
-storepass mypassw0rd \
-storetype PKCS12 \
-keystore ./ignite_keystore.jks \
-list
Keystore type: PKCS12
Keystore provider: SunJSSE
Your keystore contains 2 entries
ignite.master.key, 15.01.2019, SecretKeyEntry,
ignite.master.key2, 15.01.2019, SecretKeyEntry,
# 3.3.缓存加密密钥轮换
# 3.3.1.概述
缓存组加密密钥用于加密磁盘上的缓存数据。用户创建新的加密缓存后,将生成一个新的加密密钥,并将其传播到集群中的所有服务端节点。因此同一个缓存组在每个节点都是相同的缓存加密密钥。具体信息请参见透明数据加密。
Ignite 2.10引入了用于更改缓存加密密钥的功能,它允许更改缓存组加密密钥以及在运行时重新加密现有数据。
当密钥被泄露或加密周期(密钥有效期)结束时,需要轮换缓存加密密钥。
更改缓存加密密钥的过程包括两个连续的阶段:
- 轮换缓存组密钥。此过程为每个服务端节点上的一个或多个指定的缓存组添加新的加密密钥,并在写入新数据时使用新密钥。注意在此阶段禁止新节点加入;
- 使用新的加密密钥重新加密现有(存档的)缓存数据。
第二阶段可能需要一段时间。这取决于现有数据量。在此期间会保留旧密钥以读取存档的数据。要了解使用什么密钥加密数据,每个加密密钥都有一个标识符,默认为零。标识符值随着每次新轮换而增加,缓存组中所有节点的加密密钥(以及加密密钥ID)均相同。
提示
只有在完全更改了缓存组的加密密钥(两个阶段)之后,才可以对缓存加密密钥进行二次轮换。
# 3.3.2.环境要求
集群应处于激活状态。
# 3.3.3.更改加密密钥
Ignite支持通过如下接口更改缓存加密密钥:
- 命令行工具;
- JMX;
- 编程方式。
# 3.3.3.1.命令行工具
Ignite将位于$IGNITE_HOME/bin
目录下的control.sh|bat
脚本作为管理缓存加密密钥更改过程的工具,命令如下:
# View the cache group encryption key identifiers.
control.sh|bat --encryption cache_key_ids cacheGroupName
# Change the cache encryption key.
control.sh|bat --encryption change_cache_key cacheGroupName
# 3.3.3.2.JMX
还可以通过EncryptionMXBean
接口更改缓存加密密钥:
方法 | 描述 |
---|---|
changeCacheGroupKey(String cacheOrGrpName) | 开启缓存加密密钥更改过程 |
# 3.3.3.3.编程方式
缓存加密密钥更改过程也可以直接通过代码控制:
// Starts cache group encryption key change process.
// This future will be completed when the new encryption key is set for writing on
// all nodes in the cluster and re-encryption of existing cache data is initiated.
IgniteFuture<Void> fut = ignite.encryption().changeCacheGroupKey(Collections.singleton("encrypted-cache"));
# 3.3.4.重新加密管理
重新加密现有数据可能需要一段时间。这是一个容错操作,在节点重启后会自动继续。当使用新密钥对所有本地分区进行加密时,将自动删除先前的加密密钥,并从磁盘中删除可能包含使用先前密钥加密的条目的最后一个预写日志段。
注意
重新加密使用预写日志进行物理恢复,并且可能会影响缓存操作的性能。
有几个选项可以管理重新加密对性能的影响:
- 在运行时使用配置参数或命令行限制重新加密速率;
- 使用命令行命令暂时中止重新加密。
Ignite 2.10引入了一个新的EncryptionConfiguration
配置项,它是DataStorageConfiguration
的一部分。
属性 | 默认值 | 描述 |
---|---|---|
reencryptionRateLimit | 0(无限制) | 重新加密速率限制(MB/s) |
reencryptionBatchSize | 100 | 在检查点锁定下的重新加密过程中扫描的页面数 |
# 3.3.4.1.使用XML配置限制重新加密速率
<bean class="org.apache.ignite.configuration.IgniteConfiguration">
<property name="dataStorageConfiguration">
<bean class="org.apache.ignite.configuration.DataStorageConfiguration">
<property name="encryptionConfiguration">
<bean class="org.apache.ignite.configuration.EncryptionConfiguration">
<!-- Set re-encryption rate limit to 10.3 MB/s. -->
<property name="reencryptionRateLimit" value="10.3"/>
</bean>
</property>
</bean>
</property>
</bean>
# 3.3.4.2.通过命令行工具控制重新加密过程
control.sh|bat
脚本提供了更改重新加密速率以及在运行时暂停和恢复后台重新加密的功能。
提示
节点重启后,暂停的后台重新加密将自动继续,并且速率限制设置为“无限制”(默认),或取自本地XML配置(如果有)。
# View the cache group re-encryption status.
control.sh|bat --encryption reencryption_status cacheGroupName
# Suspend re-encryption of the cache group.
control.sh|bat --encryption suspend_reencryption cacheGroupName
# Resume (suspended) re-encryption of the cache group.
control.sh|bat --encryption resume_reencryption cacheGroupName
# View the re-encryption rate limit.
control.sh|bat --encryption reencryption_rate_limit
# Set the re-encryption rate limit to 2.5 MB/s.
control.sh|bat --encryption reencryption_rate_limit 2.5
# Set re-encryption rate to 'unlimited' ('0').
control.sh|bat --encryption reencryption_rate_limit 0
还可以使用缓存组指标章节中描述的JMX指标来获得重新加密状态。
# 4.沙箱
# 4.1.概述
Ignite可以通过各种API(包括计算任务、事件过滤器、消息监听器等)执行自定义逻辑。这种用户定义的逻辑可以利用Java API来访问主机资源。例如,它可以创建/更新/删除文件或系统属性、打开网络连接、使用反射和其他API来完全控制主机环境。Ignite沙箱基于Java沙箱模型,可以限制通过Ignite API执行的用户定义逻辑的范围。
# 4.2.激活Ignite沙箱
Ignite沙箱的激活涉及SecurityManager
实例的配置和GridSecurityProcessor
实现的创建。
# 4.2.1.安装SecurityManager
因为Ignite沙箱基于Java沙箱模型,而SecurityManager是该模型的重要组成部分,因此需要安装它。SecurityManager
负责检查当前有效的安全策略,还会执行访问控制检查。应用运行时不会自动安装安全管理器,如果将Ignite作为单独的应用运行,则必须使用-Djava.security.manager
命令行参数(设置java.security.manager
属性的值)调用Java虚拟机。还有一个-Djava.security.policy
命令行参数,用于定义使用哪些策略文件,如果在命令行中未指定-Djava.security.policy
,那么将使用安全属性文件中指定的策略文件。
提示
将安全管理器和策略命令行参数添加到{IGNITE-HOME}/bin/ignite.sh|ignite.bat
脚本中可能会很方便。
提示
Ignite应该具有足够的权限才能正常运行,最直接的方法是给Ignite赋予java.security.AllPermission
权限,但是应记住“给予尽可能低的权限”的安全原则。
# 4.2.2.提供GridSecurityProcessor实现
当前Ignite未提供现成的GridSecurityProcessor
接口实现,但是可以自定义开发该接口的实现并作为自定义插件的一部分。
该GridSecurityProcessor
接口有一个sandboxEnabled
方法,可用于管理Ignite沙箱内部用户定义代码的执行。该方法默认返回false
,表示没有沙箱。如果要使用Ignite沙箱,则需要覆写sandboxEnabled
方法并返回true
。
如果启用了Ignite沙箱,则可以看到以下信息:
[INFO] Security status [authentication=on, sandbox=on, tls/ssl=off]
# 4.3.权限
用户定义的代码总是代表启动其执行的安全主体执行。安全主体的沙盒权限定义用户代码可以执行的操作。Ignite沙箱使用SecuritySubject#sandboxPermissions
方法获取这些权限。
提示
用户定义的代码在Ignite沙箱中运行时,可以使用Ignite的公共API,而无需授予任何其他权限。
如果安全主体没有足够的权限来执行对安全性敏感的操作,则会抛出AcccessControlException
:
// Get compute instance over all nodes in the cluster.
IgniteCompute compute = Ignition.ignite().compute();
compute.broadcast(() -> {
// If the Ignite Sandbox is turned on, the lambda code is executed with restrictions.
// You can use the public API of Ignite without granting any permissions.
Ignition.localIgnite().cache("some.cache").get("key");
// If the current security subject doesn't have the java.util.PropertyPermission("secret.property", "read") permission,
// a java.security.AccessControlException appears here.
System.getProperty("secret.property");
});
如果要访问上面片段中显示的系统属性,则可以看到以下带有异常的输出:
java.security.AccessControlException: access denied ("java.util.PropertyPermission" "secret.property" "read")
18624049226