Goal: I need a Python 3 wrapper for Chef's REST API. Because its Python-3, PyChef is out of the question.
目标:我需要一个用于Chef的REST API的Python 3包装器。因为它的Python-3,PyChef是不可能的。
Problem: I am trying to replicate the Chef request with Python RSA. But the wrapper results in an error message: "Invalid signature for user or client 'XXX'".
问题:我正在尝试使用Python RSA复制Chef请求。但是包装器会导致错误消息:“用户或客户端'XXX'的签名无效”。
I approached the wrapper by trying to replicate the cURL script shown in Chef Authentication and Authorization with cURL using a Python RSA package: RSA Signing and verification.
我通过尝试使用Python RSA包复制cURL脚本身份验证和授权中显示的cURL脚本来接近包装器:RSA签名和验证。
Here's my rewrite. It could be simpler but I started getting paranoid about line breaks and headers order, so added a few unnecessary things:
这是我的重写。它可能更简单,但我开始变得偏执关于换行符和标题顺序,所以添加了一些不必要的东西:
import base64
import hashlib
import datetime
import rsa
import requests
import os
from collections import OrderedDict
body = ""
path = "/nodes"
client_name = "anton"
client_key = "/Users/velvetbaldmime/.chef/anton.pem"
# client_pub_key = "/Users/velvetbaldmime/.chef/anton.pub"
hashed_body = base64.b64encode(hashlib.sha1(body.encode()).digest()).decode("ASCII")
hashed_path = base64.b64encode(hashlib.sha1(path.encode()).digest()).decode("ASCII")
timestamp = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")
canonical_request = 'Method:GET\\nHashed Path:{hashed_path}\\nX-Ops-Content-Hash:{hashed_body}\\nX-Ops-Timestamp:{timestamp}\\nX-Ops-UserId:{client_name}'
canonical_request = canonical_request.format(
hashed_body=hashed_body, hashed_path=hashed_path, timestamp=timestamp, client_name=client_name)
headers = "X-Ops-Timestamp:{timestamp}\nX-Ops-Userid:{client_name}\nX-Chef-Version:0.10.4\nAccept:application/json\nX-Ops-Content-Hash:{hashed_body}\nX-Ops-Sign:version=1.0"
headers = headers.format(
hashed_body=hashed_body, hashed_path=hashed_path, timestamp=timestamp, client_name=client_name)
headers = OrderedDict((a.split(":", 2)[0], a.split(":", 2)[1]) for a in headers.split("\n"))
headers["X-Ops-Timestamp"] = timestamp
with open(client_key, 'rb') as privatefile:
keydata = privatefile.read()
privkey = rsa.PrivateKey.load_pkcs1(keydata)
with open("pubkey.pem", 'rb') as pubfile:
keydata = pubfile.read()
pubkey = rsa.PublicKey.load_pkcs1_openssl_pem(keydata)
signed_request = base64.b64encode(rsa.sign(canonical_request.encode(), privkey, "SHA-1"))
dummy_sign = base64.b64encode(rsa.sign("hello".encode(), privkey, "SHA-1"))
print(dummy_sign)
def chunks(l, n):
n = max(1, n)
return [l[i:i + n] for i in range(0, len(l), n)]
auth_headers = OrderedDict(("X-Ops-Authorization-{0}".format(i+1), chunk) for i, chunk in enumerate(chunks(signed_request, 60)))
all_headers = OrderedDict(headers)
all_headers.update(auth_headers)
# print('curl '+' \\\n'.join("-H {0}: {1}".format(i[0], i[1]) for i in all_headers.items())+" \\\nhttps://chef.local/nodes")
print(requests.get("https://chef.local"+path, headers=all_headers).text)
At each step I tried to check if the variables have the same result as their counterparts in the curl script.
在每一步中,我都试图检查变量是否与curl脚本中的对应变量具有相同的结果。
The problem seems to be at signing stage - there's an obvious discrepancy between the output of python's packages and my mac's openssl tools. Due to this discrepancy, Chef returns {"error":["Invalid signature for user or client 'anton'"]}
. Curl script with the same values and keys works fine.
这个问题似乎正处于签署阶段 - python软件包的输出和我的mac的openssl工具之间存在明显的差异。由于这种差异,Chef返回{“error”:[“用户或客户'anton'的无效签名”}}。具有相同值和键的卷曲脚本工作正常。
dummy_sign = base64.b64encode(rsa.sign("hello".encode(), privkey, "SHA-1"))
from Python has the value of
dummy_sign = base64.b64encode(rsa.sign(“hello”.encode(),privkey,“SHA-1”))来自Python的值具有
N7QSZRD495vV9cC35vQsDyxfOvbMN3TcnU78in911R54IwhzPUKnJTdFZ4D/KpzyTVmVBPoR4nY5um9QVcihhqTJQKy+oPF+8w61HyR7YyXZRqmx6sjiJRffC4uOGb5Wjot8csAuRSeUuHaNTl6HCcfRKnwUZnB7SctKoK6fXv0skWN2CzV9CjfHByct3oiy/xAdTz6IB+fLIwSQUf1k7lJ4/CmLJLP/Gu/qALkvWOYDAKxmavv3vYX/kNhzApKgTYPMw6l5k1aDJGRVm9Ch/BNQbg1WfZiT6LK+m4KAMFbTORfEH45KGWBCj9zsyETyMCAtUycebjqMujMqEwzv7w==
while the output of echo -n "hello" | openssl rsautl -sign -inkey ~/.chef/anton.pem | openssl enc -base64
is
而echo -n“hello”|的输出openssl rsautl -sign -inkey~ / .chef / anton.pem | openssl enc -base64是
WfoASF1f5DPT3CVPlWDrIiTwuEnjr5yCV+WIlbQLFmwm3nfhIqfTPLyTM56SwTSg
CKdboVU4EBFxC3RsU2aPpELqRH6+Fnl2Tl273vo6kLzvC/8+tUBTdNZdzSPhx6S8
x+6wzVFXsd3QeGAWoHkEgTKodSByFzARnZFxO2JzUe4dnygijwruHdf9S4ldrRo6
eaShwaxuNzM0cIl+Umz5iym3cCD6GFL13njmXZs3cHRLesBtLKA7pNxJ1UDf2WN2
OK09aK+bHaM4jl5HeQ2SdNzBQIKvyDcxX4Divnf2I/0tzD16J6BEMGCfTfsI2f3K
TVGulq81+sH9zo8lGnpDrw==
I couldn't find the information on default hashing algorithm in openssl for rsautl
, but I guess it's SHA-1.
我无法在openssl中找到有关rsautl的默认哈希算法的信息,但我猜它是SHA-1。
At this point I don't really know which way to look, hope anyone can help make it right.
在这一点上,我真的不知道哪种方式看,希望任何人都可以帮助做到正确。
3 个解决方案
#1
From Chef Authentication and Authorization with cURL,
从cURL的Chef身份验证和授权,
timestamp=$(date -u "+%Y-%m-%dT%H:%M:%SZ")
time is in UTC, so in Python, it has to be
时间是UTC,所以在Python中,它必须是
timestamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
openssl
equivalent of Python,
openssl相当于Python,
dummy_sign = base64.b64encode(rsa.sign("hello".encode(), privkey, "SHA-1"))
is
echo -n hello|openssl dgst -sha1 -sign ~/.chef/anton.pem -keyform PEM|openssl enc -base64
In Python code, you're signing the message digest, SHA-1, of the message. That's called detached signature.
在Python代码中,您正在签署消息的消息摘要SHA-1。那叫做分离签名。
echo -n "hello" | openssl rsautl -sign -inkey ~/.chef/anton.pem | openssl enc -base64
but this one signs the whole message, without making digest.
echo -n“你好”| openssl rsautl -sign -inkey~ / .chef / anton.pem | openssl enc -base64但这个签署了整个消息,没有做消化。
Python rsa
module has no equivalent of openssl rsautl -sign
. So I defined a function to fill that space.
Python rsa模块没有openssl rsautl -sign的等价物。所以我定义了一个填充该空间的函数。
from rsa import common, transform, core, varblock
from rsa.pkcs1 import _pad_for_signing
def pure_sign(message, priv_key):
'''Signs the message with the private key.
:param message: the message to sign. Can be an 8-bit string or a file-like
object. If ``message`` has a ``read()`` method, it is assumed to be a
file-like object.
:param priv_key: the :py:class:`rsa.PrivateKey` to sign with
:return: a message signature block.
:raise OverflowError: if the private key is too small to contain the
requested hash.
'''
keylength = common.byte_size(priv_key.n)
padded = _pad_for_signing(message, keylength)
payload = transform.bytes2int(padded)
encrypted = core.encrypt_int(payload, priv_key.d, priv_key.n)
block = transform.int2bytes(encrypted, keylength)
return block
Test;
openssl
echo -n hello|openssl rsautl -sign -inkey .chef/anton.pem |base64
foIy6HVpfIpNk4hMYg8YWCEZwZ7w4Qexr6KXDbJ7/vr5Jym56joofkn1qUak57iSercqQ1xqBsIT
fo6bDs2suYUKu15nj3FRQ54+LcVKjDrUUEyl2kfJgVtXLsdhzYj1SBFJZnbz32irVMVytARWQusy
b2f2GQKLTogGhCywFFyhw5YpAHmKc2CQIHw+SsVngcPrmVAAtvCZQRNV5zR61ICipckNEXnya8/J
Ga34ntyELxWDradY74726OlJSgszpHbAOMK02C4yx7OU32GWlPlsZBUGAqS5Tu4MSjlD1f/eQBsF
x/pn8deP4yuR1294DTP7dsZ9ml64ZlcIlg==
Python
base64.b64encode(pure_sign.pure_sign(b'hello',prik)).decode()
'foIy6HVpfIpNk4hMYg8YWCEZwZ7w4Qexr6KXDbJ7/vr5Jym56joofkn1qUak57iSercqQ1xqBsITfo6bDs2suYUKu15nj3FRQ54+LcVKjDrUUEyl2kfJgVtXLsdhzYj1SBFJZnbz32irVMVytARWQusyb2f2GQKLTogGhCywFFyhw5YpAHmKc2CQIHw+SsVngcPrmVAAtvCZQRNV5zR61ICipckNEXnya8/JGa34ntyELxWDradY74726OlJSgszpHbAOMK02C4yx7OU32GWlPlsZBUGAqS5Tu4MSjlD1f/eQBsFx/pn8deP4yuR1294DTP7dsZ9ml64ZlcIlg=='
Change the line;
换行;
signed_request = base64.b64encode(rsa.sign(canonical_request.encode(), privkey, "SHA-1"))
to
signed_request = base64.b64encode(pure_sign(canonical_request.encode(), privkey))
#2
I recently wrote a chef client library for python 2.7 & 3.x, built on top of pyca/cryptography and requests. It includes built-in support for Chef's authentication protocol:
我最近为python 2.7和3.x编写了一个厨师客户端库,它建立在pyca / cryptography和request之上。它包含对Chef认证协议的内置支持:
https://github.com/samstav/okchef
>>> import chef
>>>
>>> client = chef.ChefClient('https://api.opscode.com')
>>> client.authenticate('chef-user', '~/chef-user.pem')
>>> response = client.get('/users/chef-user')
>>> print(response.json())
{'display_name': 'chef-user',
'email': 'chef-user@example.com',
'first_name': 'Chef',
'last_name': 'User',
'middle_name': '',
'public_key': '-----BEGIN PUBLIC KEY-----\nMIIBIj...IDAQAB\n-----END PUBLIC KEY-----\n',
'username': 'chef-user'}
I created a separate repository for the code which handles the rsa/authentication bits:
我为处理rsa / authentication位的代码创建了一个单独的存储库:
https://github.com/samstav/requests-chef
The nuts and bolts for the auth implementation are in this file:
auth实现的章节在此文件中:
https://github.com/samstav/requests-chef/blob/master/requests_chef/mixlib_auth.py
#3
You may automate your interaction with Chef - using these tools:
您可以使用这些工具自动化与Chef的交互:
Note:
As jww
commented, the OP mentioned that he doesn't want to use Selenium.
However, I wanted my answer to be complete (it may be used by others (including me) besides the OP), I included Selenium in the list.
正如jww评论的那样,OP提到他不想使用Selenium。但是,我希望我的答案是完整的(除了OP之外可能被其他人(包括我)使用),我在列表中包含了Selenium。
#1
From Chef Authentication and Authorization with cURL,
从cURL的Chef身份验证和授权,
timestamp=$(date -u "+%Y-%m-%dT%H:%M:%SZ")
time is in UTC, so in Python, it has to be
时间是UTC,所以在Python中,它必须是
timestamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
openssl
equivalent of Python,
openssl相当于Python,
dummy_sign = base64.b64encode(rsa.sign("hello".encode(), privkey, "SHA-1"))
is
echo -n hello|openssl dgst -sha1 -sign ~/.chef/anton.pem -keyform PEM|openssl enc -base64
In Python code, you're signing the message digest, SHA-1, of the message. That's called detached signature.
在Python代码中,您正在签署消息的消息摘要SHA-1。那叫做分离签名。
echo -n "hello" | openssl rsautl -sign -inkey ~/.chef/anton.pem | openssl enc -base64
but this one signs the whole message, without making digest.
echo -n“你好”| openssl rsautl -sign -inkey~ / .chef / anton.pem | openssl enc -base64但这个签署了整个消息,没有做消化。
Python rsa
module has no equivalent of openssl rsautl -sign
. So I defined a function to fill that space.
Python rsa模块没有openssl rsautl -sign的等价物。所以我定义了一个填充该空间的函数。
from rsa import common, transform, core, varblock
from rsa.pkcs1 import _pad_for_signing
def pure_sign(message, priv_key):
'''Signs the message with the private key.
:param message: the message to sign. Can be an 8-bit string or a file-like
object. If ``message`` has a ``read()`` method, it is assumed to be a
file-like object.
:param priv_key: the :py:class:`rsa.PrivateKey` to sign with
:return: a message signature block.
:raise OverflowError: if the private key is too small to contain the
requested hash.
'''
keylength = common.byte_size(priv_key.n)
padded = _pad_for_signing(message, keylength)
payload = transform.bytes2int(padded)
encrypted = core.encrypt_int(payload, priv_key.d, priv_key.n)
block = transform.int2bytes(encrypted, keylength)
return block
Test;
openssl
echo -n hello|openssl rsautl -sign -inkey .chef/anton.pem |base64
foIy6HVpfIpNk4hMYg8YWCEZwZ7w4Qexr6KXDbJ7/vr5Jym56joofkn1qUak57iSercqQ1xqBsIT
fo6bDs2suYUKu15nj3FRQ54+LcVKjDrUUEyl2kfJgVtXLsdhzYj1SBFJZnbz32irVMVytARWQusy
b2f2GQKLTogGhCywFFyhw5YpAHmKc2CQIHw+SsVngcPrmVAAtvCZQRNV5zR61ICipckNEXnya8/J
Ga34ntyELxWDradY74726OlJSgszpHbAOMK02C4yx7OU32GWlPlsZBUGAqS5Tu4MSjlD1f/eQBsF
x/pn8deP4yuR1294DTP7dsZ9ml64ZlcIlg==
Python
base64.b64encode(pure_sign.pure_sign(b'hello',prik)).decode()
'foIy6HVpfIpNk4hMYg8YWCEZwZ7w4Qexr6KXDbJ7/vr5Jym56joofkn1qUak57iSercqQ1xqBsITfo6bDs2suYUKu15nj3FRQ54+LcVKjDrUUEyl2kfJgVtXLsdhzYj1SBFJZnbz32irVMVytARWQusyb2f2GQKLTogGhCywFFyhw5YpAHmKc2CQIHw+SsVngcPrmVAAtvCZQRNV5zR61ICipckNEXnya8/JGa34ntyELxWDradY74726OlJSgszpHbAOMK02C4yx7OU32GWlPlsZBUGAqS5Tu4MSjlD1f/eQBsFx/pn8deP4yuR1294DTP7dsZ9ml64ZlcIlg=='
Change the line;
换行;
signed_request = base64.b64encode(rsa.sign(canonical_request.encode(), privkey, "SHA-1"))
to
signed_request = base64.b64encode(pure_sign(canonical_request.encode(), privkey))
#2
I recently wrote a chef client library for python 2.7 & 3.x, built on top of pyca/cryptography and requests. It includes built-in support for Chef's authentication protocol:
我最近为python 2.7和3.x编写了一个厨师客户端库,它建立在pyca / cryptography和request之上。它包含对Chef认证协议的内置支持:
https://github.com/samstav/okchef
>>> import chef
>>>
>>> client = chef.ChefClient('https://api.opscode.com')
>>> client.authenticate('chef-user', '~/chef-user.pem')
>>> response = client.get('/users/chef-user')
>>> print(response.json())
{'display_name': 'chef-user',
'email': 'chef-user@example.com',
'first_name': 'Chef',
'last_name': 'User',
'middle_name': '',
'public_key': '-----BEGIN PUBLIC KEY-----\nMIIBIj...IDAQAB\n-----END PUBLIC KEY-----\n',
'username': 'chef-user'}
I created a separate repository for the code which handles the rsa/authentication bits:
我为处理rsa / authentication位的代码创建了一个单独的存储库:
https://github.com/samstav/requests-chef
The nuts and bolts for the auth implementation are in this file:
auth实现的章节在此文件中:
https://github.com/samstav/requests-chef/blob/master/requests_chef/mixlib_auth.py
#3
You may automate your interaction with Chef - using these tools:
您可以使用这些工具自动化与Chef的交互:
Note:
As jww
commented, the OP mentioned that he doesn't want to use Selenium.
However, I wanted my answer to be complete (it may be used by others (including me) besides the OP), I included Selenium in the list.
正如jww评论的那样,OP提到他不想使用Selenium。但是,我希望我的答案是完整的(除了OP之外可能被其他人(包括我)使用),我在列表中包含了Selenium。