如何将 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,按以下项目检查:
-
确认还原后的第一行和最后一行完全正确:
-----BEGIN RSA PRIVATE KEY----- -----END RSA PRIVATE KEY----- -
如果文件头是下面这种格式,它就不是传统的 PKCS#1 PEM 表示:
-----BEGIN OPENSSH PRIVATE KEY-----paramiko.RSAKey能否读取这种私钥,取决于实际算法、文件格式和 Paramiko 版本。先确认部署环境与本地使用的 Paramiko 版本一致。 -
检查应用设置中是否残留外层引号:
'-----BEGIN RSA PRIVATE KEY----- -
检查部署配置是否进行了二次转义。某些配置层可能会把
\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 服务器的主机密钥,使用已知主机配置或拒绝未知主机密钥。
备注:内容仅供参考。
