各位开发者大家好!我被困在一个角落里,我开始手足无措......这是情节:
load-balancer.example.com:443 (TCP passthrough)
/\
/ \
/ \
/ \
s1.example.com:443 s2.example.com:443
(SSL/SNI) (SSL/SNI)
目标是直接使用启用证书验证的
s1
对上游 s2
和 aiohttp
进行压力测试。由于负载均衡器不属于我,我不想对其进行压力测试。
load-balancer.example.com
openssl
使用 openssl s_connect s1.example.com:443 -servername load-balancer.example.com
cURL
需要curl 'https://load-balancer.example.com/' --resolve s1.example.com:443:load-balancer.example.com
并且也验证成功我能够在两个上游并行启动大量异步
ClientSession.get
请求,但对于每个请求,我需要以某种方式告诉 asyncio
或 aiohttp
使用 load-balancer.example.com
作为 server_hostname
,否则 SSL 握手失败了。
有没有一种简单的方法可以在设置 SSL 套接字时将
ClientSession
设置为使用特定的 server_hostname
?
有人已经做过类似的事情了吗?
编辑:这是最简单的片段,只有一个请求:
import aiohttp
import asyncio
async def main_async(host, port, uri, params=[], headers={}, sni_hostname=None):
if sni_hostname is not None:
print('Setting SNI server_name field ')
#
# THIS IS WHERE I DON'T KNOW HOW TO TELL aiohttp
# TO SET THE server_name FIELD TO sni_hostname
# IN THE SSL SOCKET BEFORE PERFORMING THE SSL HANDSHAKE
#
try:
async with aiohttp.ClientSession(raise_for_status=True) as session:
async with session.get(f'https://{host}:{port}/{uri}', params=params, headers=headers) as r:
body = await r.read()
print(body)
except Exception as e:
print(f'Exception while requesting ({e}) ')
if __name__ == "__main__":
asyncio.run(main_async(host='s1.example.com', port=443,
uri='/api/some/endpoint',
params={'apikey': '0123456789'},
headers={'Host': 'load-balancer.example.com'},
sni_hostname='load-balancer.example.com'))
当与真实主机一起运行时,它会抛出
Cannot connect to host s1.example.com:443 ssl:True
[SSLCertVerificationError: (1, '[SSL: CERTIFICATE_VERIFY_FAILED] '
certificate verify failed: certificate has expired (_ssl.c:1131)')])
请注意,错误
certificate has expired
表示向客户端建议的证书是默认证书,因为 SNI 主机名是 s1.example.com
,运行在那里的 Web 服务器不知道该名称。
当针对负载均衡器运行它时,它工作得很好,SSL 握手发生在提供证书的上游,并且一切都是有效的。
还要注意的是
sni_callback
没有帮助,因为它是在握手开始并收到证书后调用的(此时 server_hostname
无论如何都是只读属性)server_hostname
时似乎无法设置SSLContext
,尽管SSLContext.wrap_socket确实支持server_hostname
,但我无法完成这项工作我希望有人知道如何填写该片段中的评论块;-]
对于延迟回复表示歉意,但我最近遇到了同样的挑战。据我所知,流行的异步 HTTP 库
aiohttp
和任何其他当代异步 HTTP 库本身都不支持定义 SNI(服务器名称指示)字段。为了规避这一限制,我成功地使用了 urllib3
库并实现了类似于您提供的解决方法:
sni_proxy_ip_addr = "127.0.0.1" # your SNI proxy server
server_hostname: str = "ifconfig.co"
server_hostname_uri: str = "/json"
pool = urllib3.HTTPSConnectionPool(
sni_proxy_ip_addr,
port=443,
server_hostname=server_hostname
)
try:
resp = pool.request(
"GET",
server_hostname_uri,
headers={"Host": server_hostname},
assert_same_host=False,
retries=False
)
except urllib3.exceptions.MaxRetryError:
return {'original_ip_addr': sni_proxy_ip_addr}
except urllib3.exceptions.NewConnectionError:
return {'original_ip_addr': sni_proxy_ip_addr}
PS。谷歌吟游诗人以更正式的方式重写你的回复真是太棒了:)))))