转载

InfoSphere Guardium 应用程序用户转换

简介

InfoSphere Guardium 是一个 IBM 数据库活动监视系统。它为整个组织的数据库提供了影响较小的审计和安全性。借助它,组织可以对数据库内正在发生的事务进行细粒度详细级别的跟踪,并且仅产生极小的入侵性和性能影响。

应用程序用户转换是识别最终用户正在哪个应用程序上执行哪些数据库事务的过程。它需要在整个应用程序层跟踪最终用户的活动,且跟踪活动将深入至数据库。因为创建数据库连接是一个相对高成本的操作,所以如果在应用程序用户每次登录时都创建一个新的数据库连接,会对性能造成很大的影响。为了解决此问题,应用程序服务器会创建由应用程序事务循环使用的可重用连接池。在创建连接池时,会使用一个具有较高特权的数据库用户来登录到数据库。然后,由应用程序负责对使用连接池从数据库获得的任何数据进行访问控制。

图 1. 通过连接池进行数据库访问

InfoSphere Guardium 应用程序用户转换

连接池使得应用程序的运行速度变快了许多。 不幸的是,它也会导致丢失有关哪些应用程序最终用户执行了某个特定数据库事务的信息。 这是一个问题。识别应用程序最终用户对任何具有严格审计需求的数据库环境都至关重要。例如,在医疗行业,了解哪些最终用户正在访问受保护电子健康信息 (EPHI) 可能是美国医疗保险流通和责任法案 (Health Insurance Portability and Accountability Act, HIPAA) 的合规性要求。在金融领域,应用程序用户识别有助于改进财务跟踪和问责制(accountability),这可能有助于遵守 2002 年颁布的萨班斯-奥克斯利法案 (Sarbanes-Oxley Act of 2002, SOX)。

本文将概述如何使用 5 种不同的方法来配置 InfoSphere Guardium,以解决此问题。本文还将介绍如何为特定的应用程序选择最佳方法。

总体上讲,Guardium 中的 5 种应用程序用户识别方法如下所示:

  1. 内置的应用程序用户转换
  2. 使用 Guardium Application Event API 识别用户切换
  3. 分析存储过程中的模式
  4. 基于应用程序服务器的 S-TAP 代理
  5. 数据库和应用程序服务器 API

回页首

内置的应用程序用户转换

Guardium 支持各种开箱即用的应用程序。这些应用程序包括 Oracle E-Business、PeopleSoft、Siebel、SAP 和 Business Objects。Guardium 开发组织对这些应用程序中的每个应用程序都进行了研究,并在应用程序执行的 SQL 中寻找模式。他们发现,每个应用程序都在事务运行期间执行的 SQL 语句中采用一致方式识别其用户。通过在 SQL 中识别这些模式,Guardium 可以提取出哪个应用程序最终用户正在执行数据库事务的信息。

使用 Siebel 的示例

为了帮助演示此功能,本文将详细介绍 Siebel CRM 应用程序用户转换。只要某个事务发生在 Siebel 中,就会为受影响的表更新指示最终用户的字段。可以配置 Guardium 在执行 Siebel 事务期间提取该信息。

例如,在更新 Siebel 中的营收数据时,在 Guardium 中记录了一个类似以下内容的 SQL UPDATE 事务:

UPDATE SIEBEL.S_REVIN    SET DB_LAST_UPD_SRC='User'.DB_LAST_UPD=current timestamp-current timezone,    LAST_UPD='2-11-04-19.49.43.000000',LAST_UPD_BY='8SIA-7ZPPT',   MODIFICATION_NUM=0000000000000000,ACCNT_ID='1-4YR'    WHERE ROW_ID='UA1-1V1TV AND MODIFICATION_NUM=0000000000000000

图 2 演示了在 Guardium 中是如何记录此 SQL 的。

图 2. Siebel CRM 在更新营收数据时运行的 UPDATE SQL 语句

InfoSphere Guardium 应用程序用户转换

可以配置 Guardium 来提取此语句的用户 ID (8SIA-7ZPPT) 和用户名 (FBROOKS)。这如图 3 所示:

图 3. Guardium 从 Update 语句中提取用户 ID 并将它映射到一个用户名

InfoSphere Guardium 应用程序用户转换

该示例展示了如何通过正确地配置 Guardium,让审计员和合规专员能够轻松地查看哪个应用程序用户正在执行哪个数据库事务。

配置

在介绍 Siebel 示例后,本文将查看内置的应用程序用户转换配置。针对 Oracle E-Business、PeopleSoft、Siebel、SAP 和 Business Objects 的配置各不相同,但针对每种应用程序的配置都有一些共同线索。

第一步是在 Guardium Administration Console 中配置翻译流程,如图 4 所示。

图 4. Administration Console 中的 Application User Translation 菜单

InfoSphere Guardium 应用程序用户转换

可以在 Application User Translation 面板中为应用程序的数据库指定一系列的参数。如果 Guardium 需要建立到数据库的连接来导入用户 ID 与用户名的映射,那么还需要提供一个数据库用户名和密码(Oracle E-Business 和 Siebel 要求这么做)。直接在 SQL 模式下公开用户名时,只需指定应用程序的 IP 和端口号(比如在用于 PeopleSoft 和 Business Objects 时)。

图 5. 针对 Siebel、Business Objects、Oracle E-Business 和 PeopleSoft 的配置的示例

InfoSphere Guardium 应用程序用户转换

在将这些参数输入到 Guardium Administration Console 中后,必须重新启动 Guardium 检查引擎,然后应用程序用户转换才能生效,如图 6 所示。

图 6. Guardium 中的检查引擎配置

InfoSphere Guardium 应用程序用户转换

这是 PeopleSoft 和 Business Objects 的全部要求。 Oracle E-Business 和 Siebel 需要执行额外的步骤,导入从应用程序用户 ID 到应用程序用户名的映射。为了导入这些映射,必须运行一个应用程序用户转换导入流程,然后计划定期运行该流程。此流程的运行控制可以在 Administration Console 中找到,如图 7 所示。

图 7. 用于映射导入的配置对话框

InfoSphere Guardium 应用程序用户转换

在配置了内置的应用程序用户转换并运行它之后,可以查看一些预先包装的 Guardium 报告来了解应用程序用户活动。例如,对于 Oracle E-Business,提供了 EBS Processes Database AccessEBS Application Access 报告。 您可能需要填充 Guardium 中的一些组,然后这些报告才能正确运行。要了解哪些组需要填充,可以查看报告查询定义,如图 8 所示。

图 8. EBS Application Access 报告的查询定义

InfoSphere Guardium 应用程序用户转换

您还可以构建自己的报告来显示应用程序用户数据。为此,可以创建一个报告并添加来自 Access PeriodApp User Name 实体的 App User Name 字段。在查看报告时,请确保 Aliases 已打开。

关于 SAP 的一些特别说明

在 Application User Translation 配置面板中,SAP 在 Application Type List 中有一个条目 (SAP Observed)。无需使用该面板来执行配置,SAP 应用程序用户转换就可以正常工作。将它保留在 Administration Console 中只是出于历史原因。相反,要配置用于 SAP 的应用程序用户转换,需要填充 Guardium 中的 SAP App ServersSAP DB Servers 组,如图 9 所示。

图 9. 为了配置 SAP 应用程序用户转换而填充的两个组

InfoSphere Guardium 应用程序用户转换

在填充了应用程序服务器和数据库服务器 IP 后,必须重新启动检查引擎,应用程序用户转换才能生效。

数据库方法

有一个针对 Siebel 和 SAP 的额外选项没有讨论,该选项是基于数据库的翻译,如图 10 所示。

图 10. 在 Administration Console 中为 Siebel 和 SAP 选择的数据库应用程序用户转换

InfoSphere Guardium 应用程序用户转换

这里没有实际执行任何翻译。Guardium 会只导入该应用程序已捕获的审计数据。这是一种较老的、更据侵略性的方法,因此 observed 方法是推荐的配置。如果必须使用数据库方法代替 observed 方法,则必须启用 SAP 和 Siebel 的审计功能。对于 Siebel,这意味着 Docking:Transaction Logging 参数必须设置为 TRUE 。对于 SAP, rdisp/vb_delete_after_execution 参数必须设置为 2 。此外,对于 SAP,必须定期运行具有 DELETErsm13002 事务来清理报告。

回页首

InfoSphere Guardium Application Event API

在了解了内置的应用程序用户转换后,将查看 Guardium Application Event API。不同于内置的应用程序用户转换,Guardium Application Event API 将适用于任何应用程序,在会话的控制权从一个应用程序用户转移到另一个用户时,可以配置或修改该应用程序来执行 SQL 语句。

Application Event API 调用是简单的无操作 (no-op) SQL 语句,它们在应用程序用户接管连接池中的一个连接之前和之后执行。这些 no-op 调用告诉 Guardium 哪个应用程序用户目前正在使用数据库会话。这些调用不会在数据库上执行任何更改,但可以通过 Guardium 进行检测。 通常,这些 no-op 调用可以在单行的特殊表上执行,比如 DB2 LUWs SYSDUMMY1 表或 ORACLE DBs DUAL 表。

对于应用程序用户转换,Guardium AppEventAPI 调用通常采用以下形式:

SELECT 'GuardAppEvent:Start',  'GuardAppEventUserName:user_name' FROM DUMMY_TABLE; -- SQL Transactions related to the user named user_name SELECT 'GuardAppEvent:Released'  FROM DUMMY_TABLE;

该概念很容易使用数据库命令行工具来演示。 考虑在 DB2 LUW 命令行接口中运行以下 SQL 代码:

SELECT 'GuardAppEvent:Start',  'GuardAppEventUserName:fbrooks' FROM SYSIBM.SYSDUMMY1; SELECT count(*) FROM ITEMS; SELECT 'GuardAppEvent:Released'  FROM SYSIBM.SYSDUMMY1;
SELECT 'GuardAppEvent:Start',  'GuardAppEventUserName:kzuse' FROM SYSIBM.SYSDUMMY1; SELECT count(*) FROM ITEMS;  SELECT 'GuardAppEvent:Released'  FROM SYSIBM.SYSDUMMY1;

图 11. 从 Linux 中的 DB2 命令行执行 Guardium Application Event API

InfoSphere Guardium 应用程序用户转换

如图 12 所示,Guardium 中的会话显示了如何让用户 fbrooks 成为第一条 SELECT count(*) 语句的参数。该会话还显示了在第二条 SELECT count(*) 语句中,用户如何切换成了 kzuse。

图 12. 让应用程序用户成为 SELECT count(*) 语句的参数

InfoSphere Guardium 应用程序用户转换

通常,Application Event API 需要执行一次应用程序更改才能使用。客户需要修改应用程序代码,以便无论何时在连接池中为一个会话切换了应用程序用户,都会执行 no-op SQL。但是,有一些应用程序非常灵活,允许您在不更改代码的情况下执行 no-op SQL 调用。

回页首

通过存储过程识别应用程序用户

通过存储过程识别应用程序用户类似于上一节中讨论的 Guardium Application Event API,因为它使用 SQL 语句来定义单个数据库会话中的用户之间的边界。这一次,使用了存储过程来代替 null SQL 语句。如果应用程序在合用连接中发生应用程序用户切换时,以可预测的方式执行某个存储过程,那么可以配置 Guardium 来应用此设置,并将属性分配给该用户。

例如,如图 13 所示,考虑让一个应用程序在单个数据库会话中执行这组语句。

图 13. 一个 Oracle 数据库会话,其应用程序用户边界是通过对 set_application_property 存储过程的调用来定义的

InfoSphere Guardium 应用程序用户转换

每次在合用连接中发生应用程序用户切换时,都会使用参数 user_name 来调用 set_application_property。可以配置 Guardium 来检测此存储过程调用,并使用第二个参数(如图 14 所示)作为当前的应用程序用户名。

图 14. 在通过配置 Guardium 将存储过程识别为应用程序用户边界时的 Guardium 输出

InfoSphere Guardium 应用程序用户转换

配置

为了让 Guardium 能够通过前面显示的存储过程调用来识别用户,需要在 Guardium Administration Console 中定义一个新的自定义 ID 过程。该过程的名称将是发生应用程序用户切换时应用程序调用的存储过程的名称,在本例中为 set_application_property 。为了避免调用使用 user_date 作为第一个参数的相同过程,可以通过为第一个参数创建一个条件值来指定第一个参数必须为 user_name 。最后,您将指定第二个参数,该参数将充当 Application Event Entity 中的 Username 字段。然后,可以将 Application Event Username 字段添加到您的报告中,以便查看应用程序用户,如图 15 所示。

图 15. Guardium Stored Procedure Configuration 窗口

InfoSphere Guardium 应用程序用户转换

这种应用程序用户识别方法是大量使用存储过程(或通过调用存储过程来创建用户边界)的应用程序的理想选择。另外,一些应用程序可能不够灵活,无法调用 no-op select 语句来利用 Guardium Application Event API,但可以配置它们来执行合适的存储过程调用。此方法对这些应用程序而言也是一个理想方法。

回页首

Application Server S-TAP(应用程序服务器 S-TAP)

在某些情况下,可能无法或不希望更改应用程序源代码,所以无法使用 Guardium Application Event API。应用程序也可能不使用存储过程来定义用户边界。在这些情况的部分情况中,可以使用 Guardium S-TAP 的监视网络流量的能力来识别应用程序用户。在此方案中,使用了 S-TAP 将应用程序最终用户与数据库活动相关联。在承载应用程序服务器而不是数据库服务器的机器上安装 S-TAP。然后,将它配置为监视向应用程序服务器传入的 HTTP 流量,过滤出用户名,然后通过一个应用程序服务器会话 ID 将它们与数据库活动相关联。在 Guardium 中,这称为 Application Server User Identification 或 Application Server S-TAP。

此方法的优势是,无需更改应用程序或重新配置应用程序服务器就可以生效。因此它是一个非常快、非常容易的解决方案。不足之处在于,它同样无法适用于所有企业应用程序。可能的问题包括加密用户名或对用户名执行哈希运算的身份验证机制不兼容。 Application Server S-TAP 最适合用于使用了某种登录表单身份验证的标准 Java 企业应用程序。

分析应用程序

配置 Application Server S-TAP 的第一步是分析应该审计的应用程序。

您需要找出以下三种信息:

  • 应用程序服务器的 HTTP 端口是什么
  • 如何在登录期间在 HTTP 流量中识别应用程序用户名
  • 如何识别与该用户名有关联的 Java 会话 ID

尽管有各种不同的工具可用于分析 Web 流量,但最容易使用的工具之一是名为 Live HTTP headers 的 Firefox 插件。安装它之后,很容易查看 Web 登录期间传输的 HTTP 流量,如图 16 所示。

图 16. Plants By Websphere 应用程序的登录屏幕

InfoSphere Guardium 应用程序用户转换

对于本文,将使用演示应用程序 Plants By Websphere 作为示例。使用用户名 jon@doe.com 登录到该应用程序时,您会在 Live HTTP headers 窗口中看到 HTTP 流量,如图 17 所示。

图 17. 用于登录的 HTTP 流量

InfoSphere Guardium 应用程序用户转换

在 Plants By Websphere 应用程序中,您感兴趣的 HTTP 调用是使用参数 action=loginAccountServlet 的 Post 调用。您可以看到,用户名是在 HTML POST 调用的内容中传输的。实际的用户名有前缀 userid= 和后缀 &。

Java 会话 ID 是通过该调用在 cookie 中传输的,它有前缀 JSESSIONID= 和后缀 &

配置 Application Server S-TAP

要启用 Application Server S-TAP,需要使用从 HTTP 流中检索的信息来配置 Guardium 收集器,如图 18 所示。

图 18. Guardium Administration Console

InfoSphere Guardium 应用程序用户转换

该配置是在 Guardium Administration Console 中的 Administrative ConsoleLocal TapsS-TAP control 下完成的。这包括 S-TAP 的所有主要配置设置,如图 19 所示。要让此配置生效,在配置 S-TAP 时,需要将它安装在承载应用程序服务器而不是数据库服务器的机器上。

图 19. Application Server S-TAP 的配置

InfoSphere Guardium 应用程序用户转换

图 19 显示了针对 Plants By Websphere 示例的 Application Server S-TAP 配置。这些设置对应于您在 HTTP 流量中看到的值。Ports 值是承载应用程序的应用程序服务器的 HTTP 端口号。Web 应用程序使用前缀 userid= 和后缀 & 来传输应用程序用户名。数据库访问是使用会话 ID 的前缀 JSESSIONID= 和后缀 & 在 HTTP cookie 中传输的,该会话 ID 需要与应用程序用户有关联。Guardium 使用模式条目在 HTTP 流中确认位置。

在更改参数后,需要应用这些更改。然后重新启动 S-TAP,状态应该是良好的。

图 20. Application Server S-TAP 报告

InfoSphere Guardium 应用程序用户转换

在配置了 Application Server User Identification 后,Guardium 会将应用程序用户名存储在条目 Access Period 中的字段 Application User 中。可以将此字段添加到报告中。在前面显示的图 20 中,可以看到一个包含数据库用户名的报告,该用户名代表与 JDBC Datasource 和 Application User 相关的用户,它是在登录期间使用的实际应用程序用户。

Application Server S-TAP 摘要

在内置功能不可用时,Application Server S-TAP 是为 Java Application Server 应用程序识别应用程序用户的一种非常简单的方法。它不需要对现有应用程序执行任何更改,这使它成为一个非常具有吸引力的解决方案。但有些考虑因素需要考虑。

要让 Application Server S-TAP 发挥作用,需要在 HTTP 流中以明文形式传输用户名。一些身份验证机制(例如,常规的基于 HTTP 的身份验证)已执行了哈希运算或 Base64 编码,而且不受支持。通常,表单登录以明文形式传输用户名。

此方法并不适用于所有应用程序。它取决于使用连接池的方式。例如,它要求对数据库的访问与处理 HTTP 请求的相同进程或线程进行同步。一个很好的示例是基于 servlet 的应用程序,该应用程序中的一个 servlet 处理 HTTP 请求,并在此过程中接收来自连接池的一个连接来访问该数据库。

HTTPS 连接也不受支持。这通常不是关键问题,因为 S-TAP 安装在应用程序服务器上。在一般的企业配置中,只有流向 Web 服务器的流量采用了 HTTPS 加密,而且 Web 与应用程序服务器之间的连接是标准 HTTP。

回页首

数据库和应用程序服务器 API

Guardium 不是唯一一个在将应用程序用户从应用程序层传播到数据库层时出问题的审计系统。许多本机数据库审计解决方案也存在同样的问题。这些解决方案通过向其系统中添加额外的 API 来解决它。随着时间的推移,人们构建了应用程序服务器来利用这些 API,因此只需执行极少的应用程序开发工作就可以实现审计。好消息是,Guardium 专门设计用来检测这些 API 的使用情况,并相应地分配合适的应用程序用户。

使用 DB2 的示例

为了演示此过程,我们将讨论使用 IBM DB2 LUW 数据库和 WebSphere Application Server (WAS) 的示例。第一个示例是标准 JDBC Connection.setClientInfo API 的 DB2 LUW 实现。 考虑按照以下方式使用 setClientInfo API 的应用程序:

Class.forName("com.ibm.db2.jcc.DB2Driver"); Properties props = new Properties(); props.setProperty("user", "guard"); props.setProperty("password", "password"); Connection  connection = DriverManager.getConnection("jdbc:db2://10.10.9.28:50001/orders" , props);  connection.setClientInfo("ClientUser", "fbrooks"); execSelectOnItems(connection);  connection.setClientInfo("ClientUser", "kzuse"); execSelectOnItems(connection);

这是一个人为设计的应用程序。在真实的应用程序中,不会出现像这样的用户名硬编码。应用程序用户将以变量的形式插入。很可能通过会话 ID 来识别用户。在这里,只使用了常量 fbooks 和 kzuse 来帮助演示这些概念。execSelectOnItems 方法对数据库运行一个简单的 SELECT 语句,以获取名为 ITEMS 的表中的记录数量。Guardium 获取数据库流量,并将调用 setClientInfo("ClientUser", "fbrooks") 后执行的任何 SQL 分配给用户 fbrooks。无需执行配置,如图 21 所示。

图 21. Guardium 中的 setClientInfo 调用的外观

InfoSphere Guardium 应用程序用户转换

第二个示例是使用了受信任上下文的 DB2 连接池。 受信任上下文允许配置 DB2 LUW 来信任一个 IP/用户名对。然后,它允许在切换数据库用户名期间有选择地跳过重新身份验证和重新连接。不同于 setClientInfo API,通过受信任上下文切换用户名将允许 DB2 执行与该用户相关的任何访问控制。实际的 DB2 用户会被切换。借助受信任的上下文,既可以获得高性能,也可以使用现代数据库提供的所有访问控制机制。受信任上下文的另一个好处是,Guardium 会注意到用户切换。对于 Guardium,该切换看起来像是某种特殊的连接语句。为了演示这一点,我们采用了将在应用程序服务器中执行的以下代码。

Object[] objects = new Object[6]; Properties properties = new Properties(); byte[] cookie = new byte[1]; Connection con; DB2ConnectionPoolDataSource ds1 =  new DB2ConnectionPoolDataSource();        ds1.setServerName("10.10.9.28"); ds1.setPortNumber(50001); ds1.setDatabaseName("orders"); ds1.setDriverType (4);    objects = ds1.getDB2TrustedPooledConnection("guard", "password", properties);  DB2PooledConnection pooledCon = (com.ibm.db2.jcc.DB2PooledConnection)objects[0]; cookie = (byte[])objects[1]; con = pooledCon.getDB2Connection(cookie, "fbrooks", null, null, null, null, properties); execSelectItems(con); con = pooledCon.getDB2Connection(cookie, "kzuse", null, null, null, null, properties); execSelectItems(con);

在应用程序服务器中执行此代码将类似于图 22 中的 Guardium 中的示例。

图 22. 在 Guardium 中如何通过受信任上下文进行用户切换

InfoSphere Guardium 应用程序用户转换

DB User Name 字段指定了最初进行应用程序服务器登录时使用的用户。这是受信任上下文的受信任用户。 application user 字段指定了连接池中使用的当前用户。数据库与应用程序用户之间的界线已经开始变得模糊,因为 kzuse 和 fbrooks 是具有数据库级特权的真正的数据库用户,但同时也是应用程序用户。还有适用于其他数据库的类似 API,而且 Guardium 也应该能够注意到大部分类似的 API。

前面显示的受信任上下文和 setClientInfo 示例都会处理应用程序可以如何直接访问 API。对于大多数企业应用程序,它们将这些类型的调用留给了应用程序服务器。 WebSphere 就是这样一个可以执行这些调用的应用程序服务器。 在下一节中将讨论 WebSphere 如何自动使用 DB2 受信任上下文。

使用 WebSphere 的示例

您可以配置 WebSphere 7,在应用程序的资源引用的登录配置中使用 DB2 受信任上下文。在这么做时,可以配置 WebSphere 来自动利用受信任上下文,如图 23 所示。有关如何执行此配置的详细说明,可在“参考资料”部分找到。

图 23. 一个被配置为在 WebSphere 中使用受信任上下文的数据源资源引用

InfoSphere Guardium 应用程序用户转换

还可以配置 WebSphere,使用插件(比如数据存储帮助器)来调用 Guardium Application Event API 或数据库 API。“参考资料”部分的 developerworks 文章为 WebSphere 开发特殊 DataStoreHelper 插件来插入之前和之后执行的 SQL详细描述了此过程。WebLogic 也有类似的功能。它将该过程称为凭证映射。还可以在“参考资料”部分找到关于如何针对凭证映射来配置 WebLogic 的参考资料。

回页首

结束语

数据库审计软件的关键问题之一是识别与数据库活动相关的应用程序最终用户。出现该问题是因为,出于性能原因,应用程序服务器实现了数据库连接池。

您已经学习了在 Guardium 中识别应用程序用户的 5 种不同的技术。每种技术都拥有自己的优缺点,并适用于某些情况,例如下面这些情况。

  • 对于受支持的应用程序(比如 Siebel 或 SAP),内置的应用程序用户转换显然是最佳操作方案。
  • 如果可以修改应用程序的 SQL 调用,那么 Guardium Application Event API 将是确保应用程序用户被跟踪的最稳定、最健全的方法。
  • 对于许多使用存储过程调用来实现自己的用户管理的应用程序,分析存储过程中的模式是一种可行的解决方案。
  • 如果无法更改应用程序源代码,而且相关应用程序是一个标准的 Jave 企业应用程序,那么 Application Server S-TAP 可能是解决该问题的快速而又轻松的方法。
  • 如果应用程序服务器和数据库支持使用 API 来实现应用程序用户传播,那么可以启用此功能。

在本文中,我们介绍了数据库审计软件中的应用程序用户识别问题。我们概述了 Guardium 中可用的应用程序用户识别技术,并演示了如何实现它们。我们还提供了在何时使用其中每项技术的指导。

正文到此结束
Loading...