PAYATHON 2026

如何将 SSH 私钥配置为 Azure Function 环境变量?

技术老齐

结论

私钥可以保存为单行环境变量。把原有换行写成字面量 \n,在 Python 中还原为真正的换行符,再交给 Paramiko 读取。

在 Azure Function 应用设置中,不要给配置值添加单引号或双引号,也不要删除私钥的头尾标记。配置值应类似:

-----BEGIN RSA PRIVATE KEY-----\n<key line 1>\n<key line 2>\n...\n<key line 25>\n-----END RSA PRIVATE KEY-----

读取后执行:

private_key_text = os.environ["SSH_PKEY"].replace("\\n", "\n")
key = paramiko.RSAKey.from_private_key(io.StringIO(private_key_text))

本地 .env 文件中的引号会由 dotenv 解析器处理,不会成为变量值的一部分。Azure 应用设置没有这层解析。在其中添加引号,Paramiko 读到的值通常会包含这些引号,因而无法识别私钥。

配置环境变量

在 Azure Function 的应用设置中创建 SSH_PKEY,把整段私钥压缩成一行,并将每个原始换行写成下面两个字符:

\n

例如,原始私钥是:

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA...
K9wV7Q2...
-----END RSA PRIVATE KEY-----

应用设置中的值应为:

-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA...\nK9wV7Q2...\n-----END RSA PRIVATE KEY-----

配置时需要注意:

  • 整个值外面不要加单引号或双引号。
  • 保留 -----BEGIN RSA PRIVATE KEY----- 和 -----END RSA PRIVATE KEY-----。
  • \n 必须是字面量反斜杠和字母 n,不能直接粘贴多行内容。
  • 私钥正文中不能混入空格、引号或其他字符。

Python 代码示例

import io
import os

import paramiko


ssh_hostname = os.environ["SSH_HOSTNAME"]
ssh_username = os.environ["SSH_USERNAME"]
ssh_pkey_raw = os.environ["SSH_PKEY"]

# 将环境变量里的字面量 \n 还原为真正的换行符
ssh_pkey_text = ssh_pkey_raw.replace("\\n", "\n").strip() + "\n"

key = paramiko.RSAKey.from_private_key(
    io.StringIO(ssh_pkey_text)
)

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

ssh.connect(
    hostname=ssh_hostname,
    username=ssh_username,
    pkey=key,
)

这里用 os.environ[...] 比 str(os.getenv(...)) 更合适。变量不存在时,os.environ[...] 会立即抛出 KeyError,而 str(os.getenv(...)) 会把缺失值转换成字符串 "None",可能掩盖配置错误。

如果私钥有密码,还要传入密码:

key = paramiko.RSAKey.from_private_key(
    io.StringIO(ssh_pkey_text),
    password=os.environ["SSH_PKEY_PASSWORD"],
)

原写法为什么没有生效

在 .env 文件中,下面的单引号属于 dotenv 语法:

SSH_PKEY='-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----'

load_dotenv() 读取配置时会去掉外层引号,并保留实际换行。Azure 应用设置不是 .env 文件解析器。直接填入单引号后,Paramiko 读到的内容可能以 '-----BEGIN 开头、以 -----' 结尾,因此无法识别。

在每行末尾添加 \n 后,还要显式还原换行。环境变量保存的是字面量 \n,io.StringIO() 不会自动转换它:

ssh_pkey_raw.replace("\\n", "\n")

私钥的头尾标记也必须保留,Paramiko 需要用它们判断私钥格式。

使用 Base64 保存为单行

如果部署脚本、ARM/Bicep 模板或其他配置层可能转义反斜杠,可以先对整个私钥文件进行 Base64 编码,再把编码结果保存到环境变量中。Base64 输出本身就是单行文本。

生成编码值:

python -c 'import base64, pathlib; print(base64.b64encode(pathlib.Path("id_rsa").read_bytes()).decode("ascii"))'

将输出保存为 SSH_PKEY_BASE64,然后在函数中解码:

import base64
import io
import os

import paramiko


private_key_bytes = base64.b64decode(
    os.environ["SSH_PKEY_BASE64"],
    validate=True,
)

private_key_text = private_key_bytes.decode("utf-8")

key = paramiko.RSAKey.from_private_key(
    io.StringIO(private_key_text)
)

Base64 只能解决单行存储和转义问题,不能加密私钥。拥有应用设置读取权限的人仍可还原私钥。

报错检查

如果仍然出现 not a valid RSA private key file,按以下项目检查:

  1. 确认还原后的第一行和最后一行完全正确:

    -----BEGIN RSA PRIVATE KEY-----
    -----END RSA PRIVATE KEY-----
    
  2. 如果文件头是下面这种格式,它就不是传统的 PKCS#1 PEM 表示:

    -----BEGIN OPENSSH PRIVATE KEY-----
    

    paramiko.RSAKey 能否读取这种私钥,取决于实际算法、文件格式和 Paramiko 版本。先确认部署环境与本地使用的 Paramiko 版本一致。

  3. 检查应用设置中是否残留外层引号:

    '-----BEGIN RSA PRIVATE KEY-----
    
  4. 检查部署配置是否进行了二次转义。某些配置层可能会把 \n 变成 \\n。遇到这种情况,应在最终运行环境中确认值的结构,但不要输出完整私钥。

诊断时只记录不会泄露私钥正文的信息:

private_key_text = os.environ["SSH_PKEY"].replace("\\n", "\n").strip() + "\n"

lines = private_key_text.splitlines()

print("Private key line count:", len(lines))
print("Private key header:", lines[0] if lines else "<empty>")
print("Private key footer:", lines[-1] if lines else "<empty>")

不要记录私钥正文,也不要把完整环境变量写入异常信息。

生产环境建议

环境变量方案需要的改动较少,但私钥仍保存在 Function App 配置中。生产环境可以把私钥存入 Azure Key Vault,由函数通过托管身份读取,或者使用适合当前部署方式的 Key Vault 引用。无论使用哪种方式,都要限制读取权限,避免私钥出现在日志、部署输出或错误跟踪系统中。

paramiko.AutoAddPolicy() 会自动信任未知服务器的主机密钥,因此存在连接到错误服务器的风险。生产环境应保存并校验目标 SFTP 服务器的主机密钥,使用已知主机配置或拒绝未知主机密钥。

备注:内容仅供参考。