Skip to content

Latest commit

 

History

History
295 lines (222 loc) · 15 KB

File metadata and controls

295 lines (222 loc) · 15 KB

JumpServer PAM Python SDK 与 Go Agent

应用可以使用 Python SDK 直接连接 JumpServer,也可以在应用主机运行 Go Agent。Agent 可在 Linux、macOS 和 Windows 前台运行;内置 systemd 安装仅支持 Linux。客户端启动时获取轮换凭据,并通过签名认证的凭据事件流 WebSocket 接收订阅账号快照和后续更新;credential.updated、重连快照中的新版本以及管理员手动发送的账号切换请求会触发取密。

Python 接口风格

方法、参数和响应属性使用 snake_case;类名使用 CapWords。客户端通过关键字参数配置,取密、确认等操作直接传参,响应具有类型提示。使用 with 自动关闭 HTTP 会话:

from jms_pam import Client
from jms_pam_config import client_options, instance_id


with Client(instance_id=instance_id, **client_options) as client:
    account = client.get_account(account_id="<account-id>")
    username = account.username
    password = account.secret

get_account 直接返回账号,包含密码、资产信息和缓存来源标记。

使用 account_id 获取事件指定的账号。应用完成连接验证、连接池切换等操作后,通过 event_id 上报 success 或 failed。收到事件或成功取密不代表应用成功。事件和重连快照条目包含 event_id、account_id、account_revision,应用前须检查账号版本。重启和手动切换指令先用 running 认领。业务处理须幂等,重连可能重复事件。

旧的 jms_pam.credential.v1 请求对象接口作为兼容入口保留,并发出 DeprecationWarning。新代码使用本页示例中的 Client 接口;升级时同步重新生成 SDK 接入配置。

手动发起策略新周期

在凭据策略详情的“基本设置”或“事件接收”中点击“发起新周期”。发起后会打开新周期的时间线:

  • 凭据变更订阅:为当前有应用授权的订阅账号重新发布 credential.updated,共用一个新的 operation_id。密码和版本不变,不生成改密开始、成功或失败事件。SDK 按事件账号 ID 获取凭据;Agent 收到更新通知后也会重新取密,同版本不重复交付或重启服务。回执仅表示收到通知。离线客户端重连时通过快照获取当前凭据。
  • 账号轮换:上一周期完成或取消后发起新周期。JumpServer 先验证备用账号能登录,再发布切换事件。客户端实际应用备用账号并确认;近期通过旧取密 API 使用过原主账号的应用,还需在切换后取得备用账号。与此同时,从切换时刻起观察原主账号的 JumpServer 取密记录;原主账号连续无成功取密达到配置时长(默认 7 天)才允许改密。期间再次取密会重置窗口。校验、切换、观察与改密共用一个周期。

管理 API:POST /api/v1/accounts/application-credentials/<policy-id>/start-cycle/,账号轮换需要策略修改和账号验证权限,返回 credential 和 cycle_id。策略停用、轮换未结束时不能发起新周期;订阅账号正在改密时须等待改密结束再重新发布。

手动发送应用事件

在应用列表的“更多 > 发送事件”,或应用详情的“事件处理 > 发送事件”中,选择事件类型、目标连接实例和有效期。

  • credential.switch.requested:要求应用切换到策略当前发布的账号和版本。该请求不会更换策略的发布账号;更换发布账号应在轮换策略中操作。应用取密后必须校验请求中的账号和版本,完成连接切换后通过 confirm_event(event_id=...) 回报成功。
  • application.restart.requested:SDK 应用调用自己实现的重启处理函数,并在健康检查成功后回报结果。Agent 仅支持接入时配置为 EnvironmentFile + restart 的 systemd 服务,先重启,再检查服务状态。

WebSocket 的 received 回执表示收到请求,不表示执行成功。SDK 的 execute_application_command(event, handler) 会先向 Core 申请执行,只有 accepted: true 才调用处理函数;重复投递不会再次执行。处理函数正常返回后报告成功,异常则报告失败。应用自行负责处理函数中的业务幂等和健康检查。Agent 的账号切换请求在应用按事件确认成功切换后才报告成功。

离线实例可在有效期内重新连接接收请求。无法使用 WebSocket 的 API 应用也可通过 AK/SK 签名轮询,使用固定的 instance_id;首次轮询会登记实例,之后管理员便可选择该实例发送事件:

  1. GET /api/v1/accounts/credential-client/commands/?instance_id=<instance-id> 获取待处理请求(SDK 对应 list_application_commands())。使用与凭据客户端相同的签名头,协议版本为 1。
  2. POST /api/v1/accounts/credential-client/command-result/,提交 instance_id、command_id 和 status: running。只有响应 accepted: true 才执行。
  3. 执行结束后向同一接口提交 status: success 或 status: failed;失败可附带不含敏感数据的 error_code。SDK 对应 report_application_command_result()。

请求超过有效期会显示超时,不能再申请执行。已申请执行的重启若因进程退出而无法回报,需检查应用状态后由管理员决定是否重新发送。普通旧取密 API 保持不变,仅调用取密接口不会接收手动事件。

选择接入方式

方式 适用场景 应用需要完成的工作
Python SDK 应用可以修改 Python 代码并直接访问 JumpServer 监听事件、拉取变化的凭据、切换连接并上报事件应用结果
Go Agent 不希望应用保存 JumpServer 密钥,或需要文件、EnvironmentFile、本地 Socket 交付 加载并验证 Agent 交付的凭据,成功应用后按事件 ID 确认

Go Agent 接入

Linux x86_64 (amd64) · Linux ARM64 · SHA256SUMS

# 在下载的 Agent 和 SHA256SUMS 所在目录执行。
case "$(uname -m)" in
  x86_64) agent_arch=amd64 ;;
  aarch64|arm64) agent_arch=arm64 ;;
  *) echo "Unsupported CPU architecture"; exit 1 ;;
esac
sha256sum --ignore-missing --check SHA256SUMS && \
sudo install -m 0755 "./jms-pam-agent-linux-${agent_arch}" /usr/local/bin/jms-pam-agent

从当前 JumpServer 下载与应用主机 CPU 架构匹配的 Agent:在应用管理中打开应用接入向导,选择 Agent 并下载配置,按照向导生成的部署命令操作,每个实例使用稳定且唯一的实例 ID。以 systemd 服务安装时,需要 Linux、systemd 和管理员权限。启动后,在向导中确认连接实例已在线。

macOS、非 root Linux 或 Windows 前台运行时,在向导中选择 JSON 或 Socket 交付,按照对应的 init-local 和 run --local --config 命令操作。只初始化一次,重启时复用生成的本机配置。前台模式以当前用户运行,不执行 systemd 动作;Windows 使用私有 ACL 而不是 POSIX 权限位。

/etc/jms-pam-agent/agent.json: 0600; state_file, event_file, delivery.delivery_root, delivery.socket_path.

  • JSON: /opt/jumpserver-pam/credentials/<credential-key>.json
  • EnvironmentFile: /opt/jumpserver-pam/credentials/<credential-key>.env
  • Unix Socket: /run/jms-pam-agent/agent.sock

JSON 交付文件,EnvironmentFile 对接固定 systemd 服务,Unix Socket 提供本地 API。Agent 在交付成功后保存交付版本,应用验证并使用后才保存生效版本。Socket 归配置的应用用户所有,权限为 0600,本地请求应以该用户执行。

本机 rules 配置目标文件、JSON/EnvironmentFile 或可信模板,以及可选的 systemd reload/restart 或固定可执行脚本。脚本通过标准输入接收凭据 JSON,参数固定、有超时,并应在验证业务生效后返回成功。Core 不能新增脚本路径或扩大本机能力。修改私有配置后重启 Agent。

下载的引导配置已包含 Agent 身份和交付设置。只需在 rules 中声明业务使用的账号 ID,再配置更新业务文件、生效动作和运行中连接验证。账号设置 allow_account_switch 后可沿用同一规则处理 A/B 双向轮换;可选 credential_check 用于改文件前验证新账号。状态、事件、Socket 路径和 300 秒对账周期均有默认值。rules 为空时按凭据写默认文件。

{
  "endpoint": "https://jumpserver.example.com",
  "app_id": "<application-id>",
  "app_secret": "<application-secret>",
  "org_id": "<org-id>",
  "instance_id": "orders-node-1",
  "delivery": {
    "delivery_mode": "json",
    "delivery_root": "/opt/jumpserver-pam/credentials",
    "app_user": "orders"
  },
  "rules": []
}

rules:

[
  {
    "accounts": [
      {
        "account_id": "<primary-account-id>",
        "allow_account_switch": true
      }
    ],
    "config_update": {
      "file": "/etc/order-service/config.yml",
      "fields_map": {
        "DB_USER": "username",
        "DB_PASSWORD": "secret"
      }
    },
    "service_action": {
      "unit": "order-service.service",
      "operation": "restart"
    },
    "application_check": {
      "path": "/usr/local/libexec/jms-pam/check-running-db",
      "confirm_on_success": true
    }
  }
]
jms-pam-agent get_accounts
jms-pam-agent get_secret '<account-id>'
sudo jms-pam-agent check-config
sudo systemctl restart jms-pam-agent

本地 API 与确认

curl --fail --silent --show-error \
  --unix-socket '/run/jms-pam-agent/agent.sock' \
  http://localhost/v1/health

curl --fail --silent --show-error \
  --unix-socket '/run/jms-pam-agent/agent.sock' \
  'http://localhost/v1/credentials/<credential-key>'

交替轮换需要先验证真实连接、切换应用连接池并释放旧连接,再通过事件 ID 上报应用成功,连接验证失败时不得确认。

/usr/local/bin/jms-pam-agent confirm '<event-id>' \
  --socket '/run/jms-pam-agent/agent.sock'

通过事件 ID 确认应用结果。本地确认先持久化;confirmed 表示 Core 已接受,pending 表示之后重试。不能因为文件写入成功或服务重启就确认。

排查问题

sudo systemctl start jms-pam-agent
sudo systemctl status jms-pam-agent --no-pager
sudo journalctl -u jms-pam-agent -n 100 --no-pager
sudo systemctl restart jms-pam-agent

Agent 在启动、相关事件和每 300 秒进行同步。网络故障保留已获取的最新授权凭据;身份或授权被拒绝时阻断 Socket 取密,成功签名同步后恢复。已交付文件保留。SIGINT/SIGTERM 会关闭服务、连接和事件读取线程。

Python SDK 接入

需要 Python 3.9+。使用应用的 Python 或虚拟环境,从 PyPI 或企业内部镜像安装 jms-pam,然后在接入向导下载应用身份配置。

python3 -m pip install jms-pam

将 jms_pam_config.py 放在应用旁边。client_options 包含应用身份材料,不要提交或写入日志。每个副本使用稳定且唯一的 instance_id.

from jms_pam import Client
from jms_pam_config import client_options, instance_id


with Client(instance_id=instance_id, **client_options) as client:
    account = client.get_account(account_id="<account-id>")
    username = account.username
    password = account.secret

事件与凭据生效

使用 account_id 获取事件指定的账号。应用完成连接验证、连接池切换等操作后,通过 event_id 上报 success 或 failed。收到事件或成功取密不代表应用成功。事件和重连快照条目包含 event_id、account_id、account_revision,应用前须检查账号版本。重启和手动切换指令先用 running 认领。业务处理须幂等,重连可能重复事件。

from jms_pam import Client
from jms_pam_config import client_options, instance_id


def apply_account(account):
    # Validate a new connection, switch the pool, then release old connections.
    # Never log account.secret or authentication headers.
    raise NotImplementedError("Implement the application's account update first")


def restart_application():
    raise NotImplementedError("Implement application restart and health check first")


class ApplicationClient(Client):
    def apply_event(self, event):
        if event.get("event") == "application.restart.requested":
            restart_application()
            return
        account = self.get_account(
            account_id=event["account_id"], allow_local_fallback=False,
        )
        if account.revision != event["account_revision"]:
            raise ValueError("The event account version is superseded")
        apply_account(account)

    def handle_event(self, event):
        event_id = event["event_id"]
        if event.get("command_id"):
            claim = self.report_application_command_result(
                command_id=event["command_id"], status="running",
            )
            if not claim.accepted:
                return
        try:
            self.apply_event(event)
        except Exception:
            self.confirm_event(
                event_id=event_id, status="failed", error_code="application_failed",
            )
            raise
        # A fetch or delivery receipt never confirms application success.
        self.confirm_event(event_id=event_id)

    def run(self):
        for event in self.watch_credential_events():
            if event.get("command_id"):
                updates = [event]
            elif event.get("event") == "snapshot":
                updates = event.get("credentials", [])
                # Release application connections absent from the new scope.
            elif event.get("event") == "credential.updated":
                updates = [event]
            elif event.get("event") == "credential.revoked":
                # Release connections for event["account_id"].
                continue
            else:
                continue
            for update in updates:
                try:
                    self.handle_event(update)
                except Exception as error:
                    self.on_event_error(error, update)


with ApplicationClient(instance_id=instance_id, **client_options) as client:
    client.run()

常用方法

  • get_account(account_id=..., allow_local_fallback=True)
  • account.username / account.secret / account.asset / account.from_local
  • confirm_event(event_id=..., status="success" | "failed", error_code=...)
  • watch_credential_events(stop_event=...)
  • list_application_commands()
  • report_application_command_result(command_id=..., status="running")
  • clone() / close()

get_credential 和基于 key 的确认接口保留兼容旧接入;新接入使用 get_account 和 confirm_event。事件结果流程需要同步更新 Core,仅升级 SDK 1.0.2 不会增加服务端能力。