常见的防火墙穿墙问题
我们假设一个客户端和服务器需要在不受信任的网络通信,并且客户端和服务器主机驻留在专用网络防火墙后面:
场景1:在一个典型的网络中的客户端请求。
尽管上图看起来相当简单,但是存在以下问题:
1、 服务器上的防火墙上必须打开专用的端口并配置为向服务器转发消息。
2、 如果服务器使用多个端点(例如,支持TCP和SSL),然后防火墙端口必须为每个端点转发消息。
3、 客户端的代理必须为了使用服务器的“公共”端点而配置防火墙的主机名和专用端口。
4、 如果服务器返回一个请求的代理服务器,代理服务器不能包含服务器的专用端点,因为该端点无法访问客户端
更为复杂的情况下,下面的说明在从服务器添加到客户端的回调。回调意味着客户端也是一个服务器,因此,所有与之前的插图相关的问题在客户端同样适用。
场景2:在一个典型网络中的回调。
如果这个场景还不够复杂,下面说明添加多个客户端和服务器。每个额外的服务端(包括客户端需要回调)为防火墙管理员增加为了传递请求而另开端口的工作。
场景三:多个客户端和多个服务端在典型网络中采用回调
显然,这些场景如果没有预先衡量好的话会非常复杂。幸运的是,ICE提供了Glacier2的解决方案。
关于Glacier2
Glacier2是ICE应用的路由防火墙,最小化影响客户端和服务端(防火墙管理员)的防火墙穿越方案。在下面的例子中,Glacier2成为ICE应用的服务端防火墙。Glacier2如何消除多个防火墙穿越的复杂性下图中描述的不是很明显。
复杂的网络环境是一个不可争议的事实。不幸的是,确保一个企业网络的安全性而增加应用了应用程序的复杂性和管理开销。Glacier2帮助企业降低这些成本,提供高效、安全的ICE应用路由。
Glacier2具有以下优点和局限性:
优点:
1、 使用Glacier2时,客户端程序只需要很小的变化。
2、 只需要一个前端端口就可以支持任意数量的服务端,Glacier2路由器很容易从一个端口转发防火墙接收连接。
3、 后端服务器起的连接数减少了。Glacier2扮演着连接集中器的角色,为每个后端服务器建立一个单独连接传递来自客户端任何端口的请求。同样,来自后端服务器发送回调Glacier2为目的的连接也集中起来了。
4、 服务器不知道Glacier2的存在,所以使用Glacier2的时候不需要做任何修改。从服务器角度来看,Glacier2只是另一个本地的客户端,因此服务器不在需要发布他们创建的代理端点。此外,后端服务例如Icegrid能继续透明的使用Glacier2的路由。
5、 通过Glacier2回调不需要从客户端到服务器端建立新连接。换而言之,一个从客户端到服务器端的回调是基于现有的连接,从而消除了在客户端防火墙支持回调的相关管理要求。
6、 Glacier2不需要了解应用层定义,因此是非常有效的:他的路由请求和应答消息没有解析消息内容。
7、 除了其转发ICE请求的基础功能,glacier2提供用户自定义Session管理和认证,支持限制超时和请求缓存以及批量请求。
局限性:
l 数据报协议不支持,如UDP
l 客户端回调对象的标识必须使用Glacier2提供的类型
Glacier2是如何工作的?
Ice核心支持一个通用的路由设置,由Ice::Router 接口解析,这个接口允许第三方服务器拦截一个正确配置的代理请求,并将其提供给预期的服务器。Glacier2是一个该服务的实现,其他实现也是可以的。
Glacier2通常运行在一个主机的专用网络防火墙后面的的端口转发,但他也可以访问公共和专用网络主机操作。这种配置如下,Glacier2必须对每个网络都有端点。
在客户端,为使用Glacier2作为路由器代理必须做配置。这种配置可以通过communicator创建的所有代理静态完成,或者以编程的方式为特定的代理。配置为使用路由的代理被称为路由代理。
当一个客户端调用一个路由代理上的操作,客户端连接到一个Glacier2的客户端点,把Glacier2当做就是服务端来发送请求。Glacier2在私有网络上建立客户端-服务器发送连接,转发到服务器的请求并返回结果(如果有)到客户端。Glacier2本质上是作为远程客户端在本地的代理。
如果服务器返回一个代理作为操作结果,该代理包含在私有网络中服务器的端点。(记住,服务器不知道Glacier2的存在,因此客户端假定代理是可用的)。
在路由的情况下,客户将要尝试使用这种代理会接收到一个异常。
然而,当配置一个路由器时,客户端忽略代理的端点,而总请求发送到路由器的客户端点。
在私有网络中的Glacier2的服务器端点,仅当服务器回调客户端的时候使用。
开始使用Glacier2
使用glacier2
在使用glacier2最小配置包括下列任务:
为路由器写一个配置文件
为路由器写一个密码文件(glacier2还支持其他的方式对用户进行身份验证)
决定是否使用路由器的内部会话管理器,或者提供自己的会话管理器。
在一个主机上启动路由器访问公共和私有网络。
修改客户端配置使用上路由器
修改客户端创建路由会话
确保路由器会话保持活跃,只要客户端需要他。
配置路由器
下面的路由器配置属性建立必要的端点,并定义当一个会话到期的时间:
Glacier2.Client.Endpoints=tcp -h 5.6.7.8 -p 4063
Glacier2.SessionTimeout=60
Glacier2.Client.Endpoints定义的端点被Ice运行环境用来客户端直接与路由器进行交会。他也是从路由器代理发送请求的端点。这个端点必须在公用网络接口上定义,因为它必须能被客户端访问。此外,端点使用一个固定的端口,因为客户端可能会被静态地配置为这个端点代理。端口号4063(Tcp)和4064(SSL)是互联网数字分配结构分配给glacier2专用的。
为了使用Glacier2的路由器客户端必须创建会话。我们配置Glacier2.SessionTimeout属性是使路由器销毁已经闲置了60s的会话。它不是强制性定义的一个超时,但他是建议,否则会话状态可能会在路由器中积累。
请注意,此配置使路由器能够将客户端的请求转发给服务器。其他配置以支持从服务器到客户端的回调。
你还必须决定使用哪一种身份验证方案。一个基于文件的机制是可用的,也可以用其他更复杂的策略。如果客户端通过路由器访问一个位置服务,额外的路由器配置通常也是必须的。
创建一个密码文件
路由器最简单的身份验证机制就是建立一个用户名和密码对组成的访问控制列表文件。密码是使用模块化的加密编码方式(MCF)。
一个MCF的一般结构编码的密码散列标识符:$标识$内容,这里的标识符标识用哈希方案,
内容是指他的内容。Glacier2支持两种类型的MCF编码的密码哈希值:
在windows和os x下:
PBKDF2 使用SHA-1, SHA-256, 或者SHA-512摘要算法
· $pbkdf2-digest$rounds$salt$ for SHA-256 and SHA-512.
· $pbkdf2$rounds$salt$ for SHA-1.
在linux下:
SHA-256或SHA-512摘要算法
Glacier2.CryptPasswords属性指定密码文件。
Glacier2.CryptPasswords=passwords
密码文件的格式相当简单。每个用户名密码对为一行,用空格分隔。例如,下面密码文件包含一个用户名密码测试条目:
test $5$rounds=110000$5rM9XIDChkgEu.S3$ov7yip4NOi1wymAZmamEv1uKPQRB0WzasoJsWMpRT19
ICE散列密码帮助脚本
你可以使用ice散列密码帮助脚本生成这些用户名密码对。这个脚本需要python和pip的支持。要安装此脚本运行:
> pip install zeroc-icehashpassword
现在你可以使用命令icehashpassword
> icehashpassword
Password:
$5$rounds=110000$5rM9XIDChkgEu.S3$ov7yip4NOi1wymAZmamEv1uKPQRB0WzasoJsWMpRT19
您还可以指定几个可选参数:
-d MESSAGE_DIGEST_ALGORITHM, --digest=MESSAGE_DIGEST_ALGORITHM
-s SALT_SIZE, --salt=SALT_SIZE
-r ROUNDS, --rounds=ROUNDS
例如:
> python icehashpassword.py -r 25000 -s 32 -d sha256
Password:
$pbkdf2-sha256$25000$pJSSEuKcs9aaE.J8711LyRlD6H0P4Tyn9J7znpNyLsU$Yx7NNLDfwwLeMbZV84X2rBYBnvrXvK/TDIQiIGabQIM
注意icehashpassword在windows或者OS X上生成PBKDF2哈希与在linux上。
启动路由器
路由器支持以下命令行选项:
$ glacier2router -h
Usage: glacier2router [options]
Options:
-h, --help Show this message.
-v, --version Display the Ice version.
--nowarn Suppress warnings.
当启动时路由器无法连接到权限验证对象或会话管理器时候,--nowarn选项能隐藏路由器启动时的警告信息。
其他命令行选项的支持,包括那些允许路由器作为windows的服务运行或者Unix的守护进程,ice有使用程序帮助你将路由器作为Windows的服务来安装。
假使我们配置属性存储在一个config的文件中,你可以通过如下指令启动路由器:
$ glacier2router --Ice.Config=config
配置Glacier2客户端
以下属性配置一个使用Glacier2路由器的客户端:
Ice.Default.Router=Glacier2/router:tcp -h 5.6.7.8 -p 4063
Ice.RetryIntervals=-1
Ice.Default.Router属性定义路由器代理,它的端点必须与Glacier2.Client.Endpoints相匹配。
设定Ice.RetryIntervals属性为-1,不允许自动重试。
Glacier2对象标识
一个Glacier2路由器主机有两个公共对象。这两个对象的默认身份标识为Glacier2/router和Glacier2/admin,与Glacier2::Router 和 Glacier2::Admin接口一一对应。如果一个应用程序想使用多个不同路由器,指定唯一的标识这些对象属性的不通知的glacier2.instancename配置路由器是一个好办法,举例:
Glacier2.InstanceName=PublicRouter
这个属性改变对象标识的范畴,成为PublicRouter/router和PublicRouter/admin。客户端的配置也必须要改变,以适应新的标识:
Ice.Default.Router=PublicRouter/router:tcp -h 5.6.7.8 -p 4063:tcp -h 6.10.7.8 -p 4063
创建一个Glacier2的会话
会话管理是由Glacier2::Router接口提供:
Slice
module Glacier2 {
exception PermissionDeniedException {
string reason;
};
interface Router extends Ice::Router {
Session* createSession(string userId, string password)
throws PermissionDeniedException,
CannotCreateSessionException;
Session* createSessionFromSecureConnection()
throws PermissionDeniedException,
CannotCreateSessionException;
idempotent string getCategoryForClient();
void refreshSession()
throws SessionNotExistException;
void destroySession()
throws SessionNotExistException;
idempotent long getSessionTimeout();
idempotent int getACMTimeout();
};
};
这个接口定义了两个创建会话的操作:createSession和createSessionFromSecureConnection。路由器要求每个客户端使用其中一个操作创建会话,只有当会话已经创建路由器才能代表客户端转发请求。
createSession操作需要一个用户名和密码,根据路由器的配置,返回其中一个session的代理或者空。当使用默认的身份验证方案时,给定的用户名和密码必须与路由器的密码文件中的条目相匹配才能创建会话。
createSessionFromSecureConnection方法不需要用户名和密码,因为他的客户端认证是基于SSL的证书。
创建一个会话,客户端通常从communicator获得路由器代理,强制将代理转型为Glacier2::Router接口类型,并调用其中一个创建方法。下面的实例代码演示C++中是如何操作的,在其他语言中代码写法也相似。
C++
Ice::RouterPrx defaultRouter = communicator->getDefaultRouter();
Glacier2::RouterPrx router = Glacier2::RouterPrx::checkedCast(defaultRouter);
string username = ...;
string password = ...;
Glacier2::SessionPrx session;
try
{
session = router->createSession(username, password);
}
catch(const Glacier2::PermissionDeniedException& ex)
{
cout << "permission denied:\n" << ex.reason << endl;
}
catch(const Glacier2::CannotCreateSessionException& ex)
{
cout << "cannot create session:\n" << ex.reason << endl;
}
如果路由器配置了会话管理器,createSession和createSessionFromSecureConnection操作可能返回一个实现了Glacier2::Session接口的代理对象(或者一个应用程序特定的派生接口)。如果没有配置会话管理器,则客户端接收一个空代理。
由创建操作返回的非空会话代理对象必须与创建它的路由器进行配置,因为会话对象仅通过路由器访问。如果路由器被配置为客户端默认路由器的时候,createSession或createSessionFromSecureConnection被调用,如上面的例子中,会话代理已经正确配置所以客户端不需要别的配置。否则,客户端必须使用ice_router代理方法显示配置会话代理和路由器。
如果客户端想主动结束会话,它必须在路由器代理上调用destroySession方法。如果一个客户端没有结束会话,路由器会在session过期时自动销毁它。客户端可以通过调用getSessionTimeout方法获得session不活动的时间,如果有必要可以通过定期调用refreshSession来保持会话。一个更简单的解决方法来保持会话是调用getACMTimeout,并且使用这个值来配置客户端连接路由器的活动连接管理行为:
int acmTimeout = router->getACMTimeout();
if (acmTimeout > 0) {
Ice::ConnectionPtr conn = router->ice_getCachedConnection();
conn->setACM(acmTimeout, IceUtil::None, Ice::HeartbeatAlways);
}
客户端调用getACMTimeout(Ice3.6新特性)获取路由器服务器端的ACM超时时间,并将这个值传递给这个路由器的连接的setACM方法。从而保证了客户端和路由器都使用一致的值设置。客户端也能自动保持心跳连接,所以能保持活动的连接,不会被路由器的ACM从服务器端关闭连接。
或者,你可以配置客户端的ACM利用Ice.ACM.Timeout和Ice.ACM.Heartbeat属性。请注意,这些属性影响所有客户端的连接。一般来说,我们建议直接配置连接,就像上面那样。
getCategoryForClient方法被用于在双向连接中实现回调函数。
Glacier2会话过期
一个glacier2的路由器可能被配置为在一段时间不活动后就销毁。这个特性允许路由器以及一个自定义会话管理器来回收会话中的资源,但是它要求路由器和它的客户端之间的协调。
理想的情况下,你会选择一个足够长的时间来适应你的客户端使用。例如,一个每5秒调用服务器端操作的客户端选择会话超时时间为30秒是合理的选择。然而,超时可能造成一个不同客户端的长时间不活动,如调用的时候需要进行人机交互。
如果你不能肯定你的客户端使用模式,我们建议配置超时时间长一点,以免会话过期。最简单的解决方案是启用活动连接管理的心跳功能,其中Ice运行会定期自动发送心跳消息。Glacier2的路由器将心跳消息理解为客户端仍旧是活跃的,并且希望继续保持会话。ACM的设置可以配置所有的被通信器创建的连接,或单独为一个特定的连接。小心确保客户端的ACM超时时间和路由器的会话超时时间兼容。
注意:如果一个会话超时,下一个客户端调用会造成ConnectionLostException。要重新建立会话,客户端必须显示地重建它。如果客户端使用回调函数,它也需要重新创建毁掉是配置并且重新注册他的回调服务。
Glacier2会话销毁
一个路由器会话过期或者客户端主动销毁的时候,会话会被销毁。如果在试图连接时报错路由器也会销毁一个会话。这些错误以Run-time exception,SocketException,TimeoutException和ProtocolException的方式展现。换而言之,如果当glacier2试图建立到后端服务器连接时候发生这些异常,或者传递请求到目的服务端的时候产生异常,路由器都会自动销毁这些会话。