实施救援模式

https://bugs.launchpad.net/ironic/+bug/1526449

在 Ironic 中实现 Nova 的救援/取消救援功能。同时在 IPA 中实现一个扩展,以执行与救援相关的任务。救援节点后,它将运行一个救援 ramdisk,配置 rescue_password,并在指定的网络接口上监听 ssh。

问题描述

Ironic 目前未实现 Nova 的救援/取消救援接口。因此,最终用户在故障排除或修复异常和配置错误的节点时,选择很少。

提议的变更

  • 在 Ironic virt 驱动程序中实现 rescue() 和 unrescue()(无需规范要求):https://blueprints.launchpad.net/nova/+spec/ironic-rescue-mode

  • 在 Nova 中添加 InstanceRescueFailure 和 InstanceUnRescueFailure 异常

  • 向 Nova 驱动程序添加方法,以轮询 Ironic,等待节点进行救援和取消救援,具体取决于情况。

  • 将明文密码存储在 Ironic 节点的 instance_info 中,以便在救援节点中使用。

  • 添加将密码注入 OS 的方法。

  • 根据状态机影响部分修改 Ironic 状态机

  • 添加 AgentRescue 驱动程序(实现 base.RescueInterface)。此实现仅通过硬件类型可用。由于经典驱动程序将很快被弃用,因此不支持在经典驱动程序中使用。

  • 对于经典驱动程序,救援接口将被设置为“None”。当对经典驱动程序管理的节点调用此接口的任何方法时,将生成类型为“UnsupportedDriverExtension”的异常。

  • 添加周期性任务 _check_rescue_timeouts,如果救援 ramdisk 启动需要的时间超过 rescue_callback_timeout 秒,则使救援过程失败。

  • 添加 Conductor 方法:do_node_rescue 和 do_node_unrescue

  • 添加 Conductor RPC 调用:do_node_rescue 和 do_node_unrescue(并增加 API 版本)

  • 添加 conductor.rescue_callback_timeout 配置选项

  • 向 Ironic Python Agent 添加救援相关功能,包括设置救援密码和启动所需的网络配置自动化功能

  • 关于在多租户环境中构建救援 ramdisk的最佳实践文档

  • 添加 rescue_network 配置,其中包含救援代理应启动到的网络的 UUID。出于安全原因,在多租户环境中,这应与用于配置和清理的网络分开。

标准的(非错误)救援和取消救援过程如下

标准救援过程

  1. 用户在节点上调用 Nova rescue()。

  2. Nova ComputeManager 调用 virt 驱动程序的 rescue() 方法,并将 rescue_password 作为参数传递。

  3. Virt 驱动程序调用 node.set_provision_state(RESCUE),并将 rescue_password 作为参数。

  4. Virt 驱动程序循环等待 provision_state 更改,并相应地更新 Nova 状态。

  5. Ironic API 接收 set_provision_state 调用,并执行 do_node_rescue RPC 调用(ACTIVE -> RESCUING)。

  6. Ironic conductor 在 instance_info 中设置救援密码,并将调用传递给适当的驱动程序。

  7. 驱动程序启动救援 ramdisk(RESCUING -> RESCUEWAIT),使用配置的启动驱动程序。在此过程中,Ironic 会将节点置于 rescue_network 上,如 ironic.conf 中配置的那样。

  8. Agent ramdisk 启动,执行查找(ironic-api 中的 /v1/lookup),获取节点信息,并开始心跳(ironic-api 中的 /v1/heartbeat)。

  9. 收到心跳后,conductor 调用 finalize_rescue(ironic-api 中的 /v1/commands),并提供 config drive 和 rescue password(RESCUEWAIT -> RESCUING),并从 instance_info 中删除 rescue password,因为它不再需要。

  10. Agent 设置密码,从 config drive 中的信息配置网络,并停止 agent 服务。

  11. conductor 翻转网络端口,将节点放回租户网络,状态设置为 RESCUE。

标准取消救援过程

  1. 用户在节点上调用 Nova unrescue()。

  2. Nova 调用 Ironic unrescue() virt 驱动程序。

  3. Virt 驱动程序调用 node.set_provision_state(ACTIVE)。

  4. Virt 驱动程序循环等待 provision_state 更改,并相应地更新 Nova 状态。

  5. Ironic API 接收 set_provision_state 调用,并执行 do_node_unrescue RPC 调用。

  6. Ironic conductor 将调用传递给适当的驱动程序。

  7. 驱动程序执行启动节点所需的动作,并将 provision 状态设置为 ACTIVE。

使用独立 Ironic 进行救援/取消救援

  1. 使用动词“rescue”调用 Ironic provision 状态 API,并将 rescue password 作为参数。

  2. 完成实例救援后,使用动词“unrescue”调用 Ironic provision 状态 API

备选方案

  • 继续不支持救援和取消救援。

  • 使用控制台访问获得类似救援的访问权限到 OS,尽管这可能无法解决丢失密码的问题。

数据模型影响

基本上没有。我们将使用 instance_info 存储和随后检索 rescue_password,同时救援节点。

状态机影响

  • 向 Ironic 状态机添加状态:RESCUING、RESCUEWAIT、RESCUE、RESCUEFAIL、UNRESCUING、UNRESCUEFAIL。

  • 向 Ironic 状态机添加转换

    • ACTIVE -> RESCUING(启动救援)

    • RESCUING -> RESCUE(救援成功)

    • RESCUING -> RESCUEWAIT(可选,等待外部回调)

    • RESCUING -> RESCUEFAIL(救援失败)

    • RESCUEWAIT -> RESCUING(回调成功)

    • RESCUEWAIT -> RESCUEFAIL(回调失败或中止)

    • RESCUEWAIT -> DELETING(在不等待的情况下删除实例)

    • RESCUE -> RESCUING(重新救援节点)

    • RESCUE -> DELETING(删除救援实例)

    • RESCUE -> UNRESCUING(取消救援节点)

    • UNRESCUING -> UNRESCUEFAIL(取消救援失败)

    • UNRESCUING -> ACTIVE(取消救援成功)

    • UNRESCUEFAIL -> RESCUING(在取消救援失败后重新救援节点)

    • UNRESCUEFAIL -> UNRESCUING(在取消救援失败后重新取消救援节点)

    • UNRESCUEFAIL -> DELETING(删除取消救援失败的实例)

    • RESCUEFAIL -> RESCUING(在救援失败后重新救援)

    • RESCUEFAIL -> UNRESCUING(在救援失败后取消救援)

    • RESCUEFAIL -> DELETING(在救援失败后删除实例)

  • 添加状态机动词

    • RESCUE

    • UNRESCUE

REST API 影响

修改 provision 状态 API 以支持此规范中描述的状态和转换。 此外,增加 API 微版本。 包含此规范(以及相关、未来的微版本)引入的状态的节点将无法被使用早期微版本的客户端修改。

客户端 (CLI) 影响

“ironic” CLI

没有,因为预计此 CLI 将在功能着陆之前被弃用。

“openstack baremetal” CLI

OSC 命令行将需要额外的代码来处理新状态。 将添加新的命令行选项 rescueunrescue 以支持救援和取消救援操作。

RPC API 影响

将 do_node_rescue 和 do_node_unrescue 添加到 Conductor RPC API。

驱动程序 API 影响

在 base.py 中为 RescueInterface 添加一个新的方法 clean_up()。 此方法将在 RESCUEWAIT 超时/失败或完成救援操作时执行任何必要的清理。 一些清理任务是从节点中删除救援密码。 如果 ironic 正在管理 ramdisk 启动,则应清理 ramdisk 启动环境。 它将具有如下默认实现

class RescueInterface(BaseInterface):
    def clean_up(self, task):
        pass

Nova 驱动程序影响

在 Nova 驱动程序中实现 rescue() 和 unrescue()。 添加支持方法,包括 _wait_for_rescue() 和 _wait_for_unrescue()。

Ramdisk 影响

希望支持救援的 agent 应该
  • 读取并理解 ipa-api-url 内核参数以配置 API 端点

  • 实现 ironic 的查找 API 调用的客户端
    • rescue_password 将在 Ironic 上的查找返回的节点对象中的 instance_info 中。 它可以放置在 linux 样式的 /etc/shadow 条目中,以启用新的用户帐户。

  • 实现心跳到 Ironic 中的适当 API 端点
    • 收到心跳后,agent 应该启动任何需要重新配置网络的操作,例如重新 DHCP,因为 Ironic conductor 将完成所有操作以完成救援 - 包括将节点移出具有访问 Ironic API 的网络(如果相关)。

    • 网络重新配置完成后,agent 进程应关闭。 救援完成。

IPA 将添加一个救援扩展,实现上述功能。

安全影响

rescue_password 必须从 Nova 发送到 Ironic,然后发送到救援节点。 如果在此过程的任何步骤中,此密码被拦截或更改,攻击者可以获得救援节点的 root 访问权限。

此外,查找端点需要将 rescue password 作为救援启动后的第一次查找的响应返回。 这意味着正确执行的时序攻击可以恢复密码,但由于这也会导致救援失败(尽管节点状态已更改),因此最坏的情况是拒绝服务。

涉及救援 ramdisk 的安全漏洞是另一种攻击来源。 这不同于现有的 ramdisk 问题,因为救援完成后,租户将可以访问 ramdisk。 这意味着部署者可能需要确保 ramdisk 中不存在任何机密信息(例如自定义清理步骤或固件)。

IPA 完全未经验证。 如果在节点救援后 IPA 端点仍然可用,那么具有访问租户网络权限的攻击者将能够利用 IPA 的 REST API 获得对主机的特权访问权限。 因此,IPA 本身应关闭,或者在救援操作期间应充分隔离网络。

其他最终用户影响

我们将向 OSC Client 添加救援和取消救援命令。

可扩展性影响

无。

性能影响

无。

其他部署者影响

添加 conductor.rescue_callback_timeout 配置选项。

多租户部署者很可能需要支持两个 ramdisk - 一个运行 IPA 用于正常的节点配置任务,另一个运行 IPA 用于救援模式(禁用非救援端点)。 这是为了确保安全清理所需的完整工具和身份验证不会提供给租户。

此外,在某些环境中,运营商可能不想在救援 ramdisk 内部使用完整的 Ironic Python Agent,因为它需要 python 或 linux 为中心的性质。 他们可以使用静态编译的软件,例如 onmetal-rescue-agent [0] 来执行完成清理所需的查找和心跳。

开发人员影响

无。

实现

负责人

主要负责人

JayF

其他贡献者

Shivanand Tendulker (stendulker) Aparna (aparnavtce)

工作项

请参阅提议的更改。

依赖项

  • 更新 Nova 中的 Ironic virt 驱动程序以支持此功能。

测试

必须添加单元测试和 Tempest 测试。

升级和向后兼容性

不了解救援相关状态的客户端可能无法正确处理处于这些状态的节点。

文档影响

编写文档。

参考资料