<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Expect - 她和她的猫</title>
    <link>https://her-cat.com/tags/expect/</link>
    <description>Expect的文章列表 - 她和她的猫</description>
    <image>
      <title>她和她的猫</title>
      <url>https://her-cat.com/assets/favorite.jpeg</url>
      <link>https://her-cat.com/assets/favorite.jpeg</link>
    </image>
    <generator>Hugo -- 0.148.1</generator>
    <language>zh</language>
    <lastBuildDate>Sun, 01 Jun 2025 23:49:06 +0800</lastBuildDate>
    <atom:link href="https://her-cat.com/tags/expect/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>1Password 自动化登录堡垒机</title>
      <link>https://her-cat.com/posts/2025/05/31/1password-ssh-automation/</link>
      <pubDate>Sat, 31 May 2025 01:33:06 +0800</pubDate>
      <guid>https://her-cat.com/posts/2025/05/31/1password-ssh-automation/</guid>
      <description>在日常工作中，我经常需要登录堡垒机。传统的登录方式需要手动输入密码和一次性密码（OTP），操作繁琐且效率低下。即使改为 SSH 密钥登录，也还是需要输入一次性密码。每天登录个几次下来，就有点烦人了。</description>
      <content:encoded><![CDATA[<p>在日常工作中，我经常需要登录堡垒机。传统的登录方式需要手动输入密码和一次性密码（OTP），操作繁琐且效率低下。即使改为 SSH 密钥登录，也还是需要输入一次性密码。每天登录个几次下来，就有点烦人了。</p>
<p>最近，我在许久没打开过的涛叔博客上，看到了他最新发布的文章：《<a href="https://taoshu.in/unix/expect.html">自动化登录堡垒机</a>》。在文章中，他使用 expect 来模拟输入密码，实现自动化登录堡垒机。</p>
<p>说实话，我之前并不知道 expect 这个工具。看了文章后，我发现是时候将我的半自动化登录流程升级为全自动化了。</p>
<h2 id="方案一使用-1password-cli">方案一：使用 1Password CLI</h2>
<p>首先，需要安装 <a href="https://1password.com/zh-cn/developers/cli">1Password CLI</a>：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># macOS</span>
</span></span><span class="line"><span class="cl">brew install 1password-cli
</span></span><span class="line"><span class="cl"><span class="c1"># 其它系统</span>
</span></span><span class="line"><span class="cl"><span class="c1"># https://developer.1password.com/docs/cli/get-started/#step-1-install-1password-cli</span>
</span></span></code></pre></div><p>接着打开 1Password 的设置，启用「与 1Password CLI 集成」：</p>
<p><img alt="启用与 1Password CLI 集成" decoding="async" height="680" loading="lazy" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 768px, 1024px" src="/posts/2025/05/31/1password-ssh-automation/enable-1password-cli.png" srcset="/posts/2025/05/31/1password-ssh-automation/enable-1password-cli_hu_6f81d4e5fd139b8c.png 384w, /posts/2025/05/31/1password-ssh-automation/enable-1password-cli_hu_eeff03840e95a8d6.png 768w" style="max-width: 100%; height: auto; aspect-ratio: 1.1471;" width="780"></p>
<p>完成配置后，就可以在终端访问 1Password 了，下面是一些常用的命令：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># 获取保险库列表</span>
</span></span><span class="line"><span class="cl">op vault list
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 获取项目列表</span>
</span></span><span class="line"><span class="cl">op item list
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 获取项目信息</span>
</span></span><span class="line"><span class="cl">op item get <span class="o">{</span>id/name<span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 获取项目信息（包含密码）</span>
</span></span><span class="line"><span class="cl">op item get <span class="o">{</span>id/name<span class="o">}</span> --reveal
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 获取 JSON 格式的项目信息（包含密码）</span>
</span></span><span class="line"><span class="cl">op item get <span class="o">{</span>id/name<span class="o">}</span> --reveal --format json
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 获取项目的一次性密码</span>
</span></span><span class="line"><span class="cl">op item get <span class="o">{</span>id/name<span class="o">}</span> --otp
</span></span></code></pre></div><p>除了 <code>expect</code> 和 <code>op</code> 之外，还需要安装 jq 工具，用于解析 JSON，用来从 1Password 的项目信息中提取出密码和一次性密码。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># macOS</span>
</span></span><span class="line"><span class="cl">brew install jq
</span></span><span class="line"><span class="cl"><span class="c1"># Debian/Ubuntu</span>
</span></span><span class="line"><span class="cl">sudo apt-get install jq
</span></span><span class="line"><span class="cl"><span class="c1"># RedHat/CentOS</span>
</span></span><span class="line"><span class="cl">yum install jq
</span></span></code></pre></div><p>对原始脚本的改动很简单，只需要将脚本中的「自动读取动态口令」和「硬编码的密码」改为使用上述命令获取即可。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/expect
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="c1"># 自动调整窗口大小</span>
</span></span><span class="line"><span class="cl"><span class="nb">trap</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="nb">set</span> XZ <span class="o">[</span>stty rows   <span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="nb">set</span> YZ <span class="o">[</span>stty columns<span class="o">]</span>
</span></span><span class="line"><span class="cl">    stty rows <span class="nv">$XZ</span> columns <span class="nv">$YZ</span> &lt; <span class="nv">$spawn_out</span><span class="o">(</span>slave,name<span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span> WINCH
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">set</span> OP_ITEM_ID <span class="s2">&#34;Bastion-Host-Login&#34;</span>  <span class="c1"># 使用项目名称</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 使用 1Password CLI 获取堡垒机的项目信息</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">{[</span>catch <span class="o">{</span><span class="nb">exec</span> op item get <span class="si">${</span><span class="nv">OP_ITEM_ID</span><span class="si">}</span> --reveal --format json<span class="o">}</span> data<span class="o">]}</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    puts <span class="s2">&#34;错误: 无法获取1Password数据，请确保已登录op CLI&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 从 JSON 中提取密码</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">{[</span>catch <span class="o">{</span><span class="nb">exec</span> <span class="nb">echo</span> <span class="nv">$data</span> <span class="p">|</span> jq -r <span class="o">{</span>.fields<span class="o">[]</span> <span class="p">|</span> <span class="k">select</span><span class="o">(</span>.id <span class="o">==</span> <span class="s2">&#34;password&#34;</span><span class="o">)</span> <span class="p">|</span> .value<span class="o">}}</span> password<span class="o">]}</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    puts <span class="s2">&#34;错误: 无法解析密码&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 从 JSON 中提取一次性密码</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">{[</span>catch <span class="o">{</span><span class="nb">exec</span> <span class="nb">echo</span> <span class="nv">$data</span> <span class="p">|</span> jq -r <span class="o">{</span>.fields<span class="o">[]</span> <span class="p">|</span> <span class="k">select</span><span class="o">(</span>.type <span class="o">==</span> <span class="s2">&#34;OTP&#34;</span><span class="o">)</span> <span class="p">|</span> .totp<span class="o">}}</span> totp<span class="o">]}</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    puts <span class="s2">&#34;错误: 无法解析TOTP&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 发起 ssh 会话</span>
</span></span><span class="line"><span class="cl">spawn ssh foo@example.zz.ac
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 自动输入登录密码</span>
</span></span><span class="line"><span class="cl">expect <span class="s2">&#34;Password:&#34;</span>
</span></span><span class="line"><span class="cl">send <span class="s2">&#34;</span><span class="nv">$password</span><span class="s2">\r&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 自动输入动态口令</span>
</span></span><span class="line"><span class="cl">expect <span class="s2">&#34;Verification code:&#34;</span>
</span></span><span class="line"><span class="cl">send <span class="s2">&#34;</span><span class="nv">$totp</span><span class="s2">\r&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 将终端控制权交还给 ssh 会话，完成登录</span>
</span></span><span class="line"><span class="cl">interact
</span></span></code></pre></div><p>在上面的脚本中，使用了 <code>catch</code> 命令用来捕获异常，类似于编程语言中的 <code>try-catch</code>。如果命令执行失败，<code>catch</code> 会返回非零值（通常是 1），命令执行成功，<code>catch</code> 会返回 0，并将命令执行结果保存到 result 中。</p>
<p>在使用脚本前，你需要将 <code>OP_ITEM_ID</code> 的值替换成项目的实际 ID 或者名称。可以使用以下命令获取：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># 列出所有项目，找到堡垒机相关的项目</span>
</span></span><span class="line"><span class="cl">op item list
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 或者搜索特定名称</span>
</span></span><span class="line"><span class="cl">op item list <span class="p">|</span> grep <span class="s2">&#34;堡垒机&#34;</span>
</span></span></code></pre></div><p>例如：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nb">set</span> OP_ITEM_ID <span class="s2">&#34;Bastion-Host-Login&#34;</span>  <span class="c1"># 使用项目名称</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 或者</span>
</span></span><span class="line"><span class="cl"><span class="nb">set</span> OP_ITEM_ID <span class="s2">&#34;abc123def456&#34;</span>        <span class="c1"># 使用项目ID</span>
</span></span></code></pre></div><h2 id="1password-cli-的性能问题">1Password CLI 的性能问题</h2>
<p>原本以为到这里就结束了，但是在实际使用过程中我发现了一个问题：这个脚本非常慢！慢到什么程度呢？在不需要认证的情况下，从执行命令到登录成功，需要 3 秒左右，最慢的时候差不多需要 10 秒。</p>
<p>自动化的目的是为了提升效率，现在操作虽然自动化了，但时间效率没有提升多少，跟手动登录差不多，这显然无法满足需求。</p>
<p>研究了一会儿，发现问题出在 <code>op</code> 命令获取密码太慢了，不仅获取密码慢，获取一次性密码也慢。我尝试过指定保险库、使用 <code>--fields</code> 选项减少获取的字段，没什么效果。</p>
<p>Google 搜索发现很早之前就有这个问题，但都没有得到解决。</p>
<ul>
<li><a href="https://www.reddit.com/r/1Password/comments/w9owk7/1password_cli_v_260_is_still_slow/">1password cli v 2.6.0 is still slow.</a></li>
<li><a href="https://www.1password.community/discussions/developers/cli-is-sotoo-slow/84280">CLI is so/too slow</a></li>
<li><a href="https://www.1password.community/discussions/developers/cli-commands-are-very-slow/79324">CLI commands are very slow</a></li>
<li><a href="https://www.1password.community/discussions/developers/why-is-the-cli-so-slow-any-tips/26756">Why is the CLI so slow? Any tips?</a></li>
</ul>
<p>官方回复说在 macOS 上会默认开启缓存来提升执行速度，在命令中加上 <code>--debug</code> 选项，确实可以看到使用了缓存，并且也命中了缓存。但是，为什么还这么慢？</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">op item get xxxx --reveal --debug
</span></span><span class="line"><span class="cl">23:19PM <span class="p">|</span> DEBUG <span class="p">|</span> Session delegation enabled
</span></span><span class="line"><span class="cl">23:19PM <span class="p">|</span> DEBUG <span class="p">|</span> NM request: NmRequestAccounts
</span></span><span class="line"><span class="cl">23:19PM <span class="p">|</span> DEBUG <span class="p">|</span> NM response: Success
</span></span><span class="line"><span class="cl">23:19PM <span class="p">|</span> DEBUG <span class="p">|</span> NM request: NmRequestAccounts
</span></span><span class="line"><span class="cl">23:19PM <span class="p">|</span> DEBUG <span class="p">|</span> NM response: Success
</span></span><span class="line"><span class="cl">23:19PM <span class="p">|</span> DEBUG <span class="p">|</span> InitDefaultCache: successfully initialized cache
</span></span><span class="line"><span class="cl">23:19PM <span class="p">|</span> DEBUG <span class="p">|</span> EncryptedKeysets: Cache hit on keyset
</span></span><span class="line"><span class="cl">23:19PM <span class="p">|</span> DEBUG <span class="p">|</span> AllVaults: cache hit on vault xxxxxxxxxxxxxxxxx
</span></span><span class="line"><span class="cl">23:19PM <span class="p">|</span> DEBUG <span class="p">|</span> AllVaults: cache hit on vault xxxxxxxxxxxxxxxxx
</span></span><span class="line"><span class="cl">23:19PM <span class="p">|</span> DEBUG <span class="p">|</span> AllVaults: cache hit on vault xxxxxxxxxxxxxxxxx
</span></span><span class="line"><span class="cl">23:19PM <span class="p">|</span> DEBUG <span class="p">|</span> VaultItems: cache hit on vault items of vault xxxxxxxxxxxxxxxxx
</span></span><span class="line"><span class="cl">23:19PM <span class="p">|</span> DEBUG <span class="p">|</span> VaultItems: cache hit on vault items of vault xxxxxxxxxxxxxxxxx
</span></span><span class="line"><span class="cl">23:19PM <span class="p">|</span> DEBUG <span class="p">|</span> VaultItems: cache hit on vault items of vault xxxxxxxxxxxxxxxxx
</span></span><span class="line"><span class="cl">23:19PM <span class="p">|</span> DEBUG <span class="p">|</span> VaultItems: cache hit on vault items of vault xxxxxxxxxxxxxxxxx
</span></span><span class="line"><span class="cl">23:19PM <span class="p">|</span> DEBUG <span class="p">|</span> Item: VaultItems cache hit <span class="k">for</span> vault xxxxxxxxxxxxxxxxx - validating staleness using item version
</span></span><span class="line"><span class="cl">23:19PM <span class="p">|</span> DEBUG <span class="p">|</span> Item: cache hit on item xxxxxxxxxxxxxxxxx of vault xxxxxxxxxxxxxxxxx
</span></span></code></pre></div><p>折腾无果后，既然 1Password CLI 的性能问题无法解决，不如换个思路：用 SSH 密钥解决认证问题，用独立的 2FA 工具处理 OTP。</p>
<h2 id="方案二使用-ssh-密钥--2fa">方案二：使用 SSH 密钥 + 2FA</h2>
<p>1Password 还提供了另一项功能：<a href="https://developer.1password.com/docs/ssh/agent/">1Password SSH Agent</a>。你可以将本地的 SSH 密钥保存到 1Password 上。每次需要使用 SSH 密钥时，都会弹出一个 1Password 的授权框，上面显示了哪个客户端正在请求使用哪个 SSH 密钥，你可以使用指纹或者密码同意这次授权。</p>
<p><img alt="1Password SSH 认证请求" decoding="async" height="370" loading="lazy" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 768px, 1024px" src="/posts/2025/05/31/1password-ssh-automation/1password-ssh-auth-request.png" srcset="/posts/2025/05/31/1password-ssh-automation/1password-ssh-auth-request_hu_44c8d042c797ab42.png 384w" style="max-width: 100%; height: auto; aspect-ratio: 1.0811;" width="400"></p>
<p>这样带来的好处是：</p>
<ul>
<li>本地不用再存储任何私钥文件，避免了恶意扫描导致私钥被泄露的风险</li>
<li>基于 1Password 的同步功能，可以在任何登录了 1Password 账号的设备上使用这些 SSH 密钥</li>
</ul>
<blockquote>
<p>为了使用这项功能，你需要在 1Password 的设置中启用「使用 SSH Agent」。</p></blockquote>
<h3 id="配置-1password-ssh-agent">配置 1Password SSH Agent</h3>
<p>首先，在 1Password 上创建一个 SSH 密钥项目，你可以选择将堡垒机的密钥导入进来，也可以生成一个新的私钥。</p>
<p><img alt="创建 SSH Key" decoding="async" height="1024" loading="lazy" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 768px, 1024px" src="/posts/2025/05/31/1password-ssh-automation/create-ssh-key.png" srcset="/posts/2025/05/31/1password-ssh-automation/create-ssh-key_hu_2a24c93c73000c49.png 384w, /posts/2025/05/31/1password-ssh-automation/create-ssh-key_hu_26f98f716f0c71ba.png 768w" style="max-width: 100%; height: auto; aspect-ratio: 0.9004;" width="922"></p>
<p>创建完成后，将公钥保存到 <code>~/.ssh/bastion.pub</code>，避免 SSH 密钥过多，产生 <a href="https://developer.1password.com/docs/ssh/agent/advanced#ssh-server-six-key-limit">Too many authentication failures</a> 问题。</p>
<p><img alt="下载公钥" decoding="async" height="382" loading="lazy" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 768px, 1024px" src="/posts/2025/05/31/1password-ssh-automation/download-pub-key.png" srcset="/posts/2025/05/31/1password-ssh-automation/download-pub-key_hu_9bde06b84ea59664.png 384w" style="max-width: 100%; height: auto; aspect-ratio: 1.2723;" width="486"></p>
<p>然后在 <code>~/.ssh/config</code> 文件中添加以下内容：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">Host bastion
</span></span><span class="line"><span class="cl">    HostName example.zz.ac  <span class="c1"># 替换为堡垒机的地址</span>
</span></span><span class="line"><span class="cl">    Port <span class="m">22</span>                 <span class="c1"># 替换为堡垒机的端口</span>
</span></span><span class="line"><span class="cl">    IdentitiesOnly yes
</span></span><span class="line"><span class="cl">    IdentityFile ~/.ssh/bastion.pub
</span></span><span class="line"><span class="cl">    IdentityAgent <span class="s2">&#34;~/.1password/agent.sock&#34;</span>
</span></span></code></pre></div><p>这时候在终端中执行 <code>ssh bastion</code> 命令，你就可以免密登录到堡垒机了，但是还需要输入一次性密码，接下来我们解决这个问题。</p>
<h3 id="配置-2fa-工具">配置 2FA 工具</h3>
<p>执行以下命令安装 <a href="https://github.com/rsc/2fa">2fa</a> 并添加一次性密码。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># 安装</span>
</span></span><span class="line"><span class="cl">go install rsc.io/2fa@latest
</span></span><span class="line"><span class="cl"><span class="c1"># 添加一次性密码</span>
</span></span><span class="line"><span class="cl">2fa -add bastion
</span></span><span class="line"><span class="cl">2fa key <span class="k">for</span> bastion: 
</span></span><span class="line"><span class="cl"><span class="c1"># 获取一次性密码</span>
</span></span><span class="line"><span class="cl">2fa bastion
</span></span><span class="line"><span class="cl"><span class="m">211762</span>
</span></span></code></pre></div><p>我对脚本做了一些优化，增加了超时处理、登录后执行命令等：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/expect
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="c1"># 自动调整窗口大小</span>
</span></span><span class="line"><span class="cl"><span class="nb">trap</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="nb">set</span> XZ <span class="o">[</span>stty rows   <span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="nb">set</span> YZ <span class="o">[</span>stty columns<span class="o">]</span>
</span></span><span class="line"><span class="cl">    stty rows <span class="nv">$XZ</span> columns <span class="nv">$YZ</span> &lt; <span class="nv">$spawn_out</span><span class="o">(</span>slave,name<span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span> WINCH
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 自动读取动态口令</span>
</span></span><span class="line"><span class="cl">spawn 2fa bastion
</span></span><span class="line"><span class="cl">expect -re <span class="s2">&#34;(.*)\n&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">set</span> totp <span class="nv">$expect_out</span><span class="o">(</span>1,string<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 发起 ssh 会话</span>
</span></span><span class="line"><span class="cl">spawn ssh example.zz.ac
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 等待 OTP 提示并自动输入</span>
</span></span><span class="line"><span class="cl">expect <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;Verification code:&#34;</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        send <span class="s2">&#34;</span><span class="nv">$totp</span><span class="s2">\r&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl">    timeout <span class="o">{</span>
</span></span><span class="line"><span class="cl">        puts <span class="s2">&#34;超时: 未收到 OTP 提示&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 等待登录成功（通常是 shell 提示符）</span>
</span></span><span class="line"><span class="cl">expect <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;*</span>$<span class="s2">&#34;</span> <span class="o">{</span> <span class="o">}</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;*#&#34;</span> <span class="o">{</span> <span class="o">}</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;*&gt;&#34;</span> <span class="o">{</span> <span class="o">}</span>
</span></span><span class="line"><span class="cl">    timeout <span class="o">{</span>
</span></span><span class="line"><span class="cl">        puts <span class="s2">&#34;登录超时&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 你可以在登录成功后执行一些命令</span>
</span></span><span class="line"><span class="cl"><span class="c1"># send &#34;ls -l\r&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 将终端控制权交还给 ssh 会话，完成登录</span>
</span></span><span class="line"><span class="cl">interact
</span></span></code></pre></div><p>将上面脚本保存到文件中，就可以愉快的登录堡垒机了。实测下来，响应速度基本在 1 秒左右，相比之前的 3-10 秒有了非常大的提升。更重要的是，整个流程的可靠性也更好了。</p>
<h2 id="总结">总结</h2>
<p>虽然最终没能用上 1Password CLI 有点遗憾（主要是太慢了），但好在找到了替代方案，日常使用体验还是很不错的。期待 1Password 官方后续能优化一下 CLI 的性能吧。</p>
]]></content:encoded>
    </item><follow_challenge>
      <feedId>58021783493571598</feedId>
      <userId>56882619875632128</userId>
    </follow_challenge>
  </channel>
</rss>
