Refactoring GET request handling. (#45051)
(cherry picked from commit 26edeb7cce
)
This commit is contained in:
parent
949ffe6da7
commit
e8a6bdc17a
2 changed files with 47 additions and 51 deletions
|
@ -81,29 +81,6 @@ def nopad_b64(data):
|
||||||
return base64.urlsafe_b64encode(data).decode('utf8').replace("=", "")
|
return base64.urlsafe_b64encode(data).decode('utf8').replace("=", "")
|
||||||
|
|
||||||
|
|
||||||
def simple_get(module, url):
|
|
||||||
resp, info = fetch_url(module, url, method='GET')
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
try:
|
|
||||||
content = resp.read()
|
|
||||||
except AttributeError:
|
|
||||||
content = info.get('body')
|
|
||||||
|
|
||||||
if content:
|
|
||||||
if info['content-type'].startswith('application/json'):
|
|
||||||
try:
|
|
||||||
result = module.from_json(content.decode('utf8'))
|
|
||||||
except ValueError:
|
|
||||||
raise ModuleFailException("Failed to parse the ACME response: {0} {1}".format(url, content))
|
|
||||||
else:
|
|
||||||
result = content
|
|
||||||
|
|
||||||
if info['status'] >= 400:
|
|
||||||
raise ModuleFailException("ACME request failed: CODE: {0} RESULT: {1}".format(info['status'], result))
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def read_file(fn, mode='b'):
|
def read_file(fn, mode='b'):
|
||||||
try:
|
try:
|
||||||
with open(fn, 'r' + mode) as f:
|
with open(fn, 'r' + mode) as f:
|
||||||
|
@ -469,12 +446,12 @@ class ACMEDirectory(object):
|
||||||
https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.1.1
|
https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.1.1
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, module):
|
def __init__(self, module, account):
|
||||||
self.module = module
|
self.module = module
|
||||||
self.directory_root = module.params['acme_directory']
|
self.directory_root = module.params['acme_directory']
|
||||||
self.version = module.params['acme_version']
|
self.version = module.params['acme_version']
|
||||||
|
|
||||||
self.directory = simple_get(self.module, self.directory_root)
|
self.directory, dummy = account.get_request(self.directory_root)
|
||||||
|
|
||||||
# Check whether self.version matches what we expect
|
# Check whether self.version matches what we expect
|
||||||
if self.version == 1:
|
if self.version == 1:
|
||||||
|
@ -512,7 +489,6 @@ class ACMEAccount(object):
|
||||||
# account_key path and content are mutually exclusive
|
# account_key path and content are mutually exclusive
|
||||||
self.key = module.params['account_key_src']
|
self.key = module.params['account_key_src']
|
||||||
self.key_content = module.params['account_key_content']
|
self.key_content = module.params['account_key_content']
|
||||||
self.directory = ACMEDirectory(module)
|
|
||||||
|
|
||||||
# Grab account URI from module parameters.
|
# Grab account URI from module parameters.
|
||||||
# Make sure empty string is treated as None.
|
# Make sure empty string is treated as None.
|
||||||
|
@ -533,6 +509,8 @@ class ACMEAccount(object):
|
||||||
# Make sure self.jws_header is updated
|
# Make sure self.jws_header is updated
|
||||||
self.set_account_uri(self.uri)
|
self.set_account_uri(self.uri)
|
||||||
|
|
||||||
|
self.directory = ACMEDirectory(module, self)
|
||||||
|
|
||||||
def get_keyauthorization(self, token):
|
def get_keyauthorization(self, token):
|
||||||
'''
|
'''
|
||||||
Returns the key authorization for the given token
|
Returns the key authorization for the given token
|
||||||
|
@ -566,7 +544,7 @@ class ACMEAccount(object):
|
||||||
else:
|
else:
|
||||||
return _sign_request_openssl(self._openssl_bin, self.module, payload64, protected64, key_data)
|
return _sign_request_openssl(self._openssl_bin, self.module, payload64, protected64, key_data)
|
||||||
|
|
||||||
def send_signed_request(self, url, payload, key_data=None, jws_header=None):
|
def send_signed_request(self, url, payload, key_data=None, jws_header=None, parse_json_result=True):
|
||||||
'''
|
'''
|
||||||
Sends a JWS signed HTTP POST request to the ACME server and returns
|
Sends a JWS signed HTTP POST request to the ACME server and returns
|
||||||
the response as dictionary
|
the response as dictionary
|
||||||
|
@ -596,17 +574,19 @@ class ACMEAccount(object):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
content = info.get('body')
|
content = info.get('body')
|
||||||
|
|
||||||
if content:
|
if content or not parse_json_result:
|
||||||
if info['content-type'].startswith('application/json') or 400 <= info['status'] < 600:
|
if (parse_json_result and info['content-type'].startswith('application/json')) or 400 <= info['status'] < 600:
|
||||||
try:
|
try:
|
||||||
result = self.module.from_json(content.decode('utf8'))
|
decoded_result = self.module.from_json(content.decode('utf8'))
|
||||||
# In case of badNonce error, try again (up to 5 times)
|
# In case of badNonce error, try again (up to 5 times)
|
||||||
# (https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-6.6)
|
# (https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-6.6)
|
||||||
if (400 <= info['status'] < 600 and
|
if (400 <= info['status'] < 600 and
|
||||||
result.get('type') == 'urn:ietf:params:acme:error:badNonce' and
|
decoded_result.get('type') == 'urn:ietf:params:acme:error:badNonce' and
|
||||||
failed_tries <= 5):
|
failed_tries <= 5):
|
||||||
failed_tries += 1
|
failed_tries += 1
|
||||||
continue
|
continue
|
||||||
|
if parse_json_result:
|
||||||
|
result = decoded_result
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ModuleFailException("Failed to parse the ACME response: {0} {1}".format(url, content))
|
raise ModuleFailException("Failed to parse the ACME response: {0} {1}".format(url, content))
|
||||||
else:
|
else:
|
||||||
|
@ -614,6 +594,31 @@ class ACMEAccount(object):
|
||||||
|
|
||||||
return result, info
|
return result, info
|
||||||
|
|
||||||
|
def get_request(self, uri, parse_json_result=True, headers=None):
|
||||||
|
resp, info = fetch_url(self.module, uri, method='GET', headers=headers)
|
||||||
|
|
||||||
|
try:
|
||||||
|
content = resp.read()
|
||||||
|
except AttributeError:
|
||||||
|
content = info.get('body')
|
||||||
|
|
||||||
|
if parse_json_result:
|
||||||
|
result = {}
|
||||||
|
if content:
|
||||||
|
if info['content-type'].startswith('application/json'):
|
||||||
|
try:
|
||||||
|
result = self.module.from_json(content.decode('utf8'))
|
||||||
|
except ValueError:
|
||||||
|
raise ModuleFailException("Failed to parse the ACME response: {0} {1}".format(uri, content))
|
||||||
|
else:
|
||||||
|
result = content
|
||||||
|
else:
|
||||||
|
result = content
|
||||||
|
|
||||||
|
if info['status'] >= 400:
|
||||||
|
raise ModuleFailException("ACME request failed: CODE: {0} RESULT: {1}".format(info['status'], result))
|
||||||
|
return result, info
|
||||||
|
|
||||||
def set_account_uri(self, uri):
|
def set_account_uri(self, uri):
|
||||||
'''
|
'''
|
||||||
Set account URI. For ACME v2, it needs to be used to sending signed
|
Set account URI. For ACME v2, it needs to be used to sending signed
|
||||||
|
|
|
@ -330,7 +330,7 @@ account_uri:
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from ansible.module_utils.acme import (
|
from ansible.module_utils.acme import (
|
||||||
ModuleFailException, fetch_url, write_file, nopad_b64, simple_get, pem_to_der, ACMEAccount,
|
ModuleFailException, write_file, nopad_b64, pem_to_der, ACMEAccount,
|
||||||
HAS_CURRENT_CRYPTOGRAPHY, cryptography_get_csr_domains, cryptography_get_cert_days,
|
HAS_CURRENT_CRYPTOGRAPHY, cryptography_get_csr_domains, cryptography_get_cert_days,
|
||||||
set_crypto_backend,
|
set_crypto_backend,
|
||||||
)
|
)
|
||||||
|
@ -553,7 +553,7 @@ class ACMEClient(object):
|
||||||
status = ''
|
status = ''
|
||||||
|
|
||||||
while status not in ['valid', 'invalid', 'revoked']:
|
while status not in ['valid', 'invalid', 'revoked']:
|
||||||
result = simple_get(self.module, auth['uri'])
|
result, dummy = self.account.get_request(auth['uri'])
|
||||||
result['uri'] = auth['uri']
|
result['uri'] = auth['uri']
|
||||||
if self._add_or_update_auth(domain, result):
|
if self._add_or_update_auth(domain, result):
|
||||||
self.changed = True
|
self.changed = True
|
||||||
|
@ -590,7 +590,7 @@ class ACMEClient(object):
|
||||||
status = result['status']
|
status = result['status']
|
||||||
while status not in ['valid', 'invalid']:
|
while status not in ['valid', 'invalid']:
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
result = simple_get(self.module, order)
|
result, dummy = self.account.get_request(order)
|
||||||
status = result['status']
|
status = result['status']
|
||||||
|
|
||||||
if status != 'valid':
|
if status != 'valid':
|
||||||
|
@ -611,11 +611,7 @@ class ACMEClient(object):
|
||||||
Download and parse the certificate chain.
|
Download and parse the certificate chain.
|
||||||
https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.4.2
|
https://tools.ietf.org/html/draft-ietf-acme-acme-14#section-7.4.2
|
||||||
'''
|
'''
|
||||||
resp, info = fetch_url(self.module, url, headers={'Accept': 'application/pem-certificate-chain'})
|
content, info = self.account.get_request(url, parse_json_result=False, headers={'Accept': 'application/pem-certificate-chain'})
|
||||||
try:
|
|
||||||
content = resp.read()
|
|
||||||
except AttributeError:
|
|
||||||
content = info.get('body')
|
|
||||||
|
|
||||||
if not content or not info['content-type'].startswith('application/pem-certificate-chain'):
|
if not content or not info['content-type'].startswith('application/pem-certificate-chain'):
|
||||||
raise ModuleFailException("Cannot download certificate chain from {0}: {1} (headers: {2})".format(url, content, info))
|
raise ModuleFailException("Cannot download certificate chain from {0}: {1} (headers: {2})".format(url, content, info))
|
||||||
|
@ -642,9 +638,9 @@ class ACMEClient(object):
|
||||||
parsed_link = re.match(r'<(.+)>;rel="(\w+)"', link)
|
parsed_link = re.match(r'<(.+)>;rel="(\w+)"', link)
|
||||||
if parsed_link and parsed_link.group(2) == "up":
|
if parsed_link and parsed_link.group(2) == "up":
|
||||||
chain_link = parsed_link.group(1)
|
chain_link = parsed_link.group(1)
|
||||||
chain_result, chain_info = fetch_url(self.module, chain_link, method='GET')
|
chain_result, chain_info = self.account.get_request(chain_link, parse_json_result=False)
|
||||||
if chain_info['status'] in [200, 201]:
|
if chain_info['status'] in [200, 201]:
|
||||||
chain.append(self._der_to_pem(chain_result.read()))
|
chain.append(self._der_to_pem(chain_result))
|
||||||
|
|
||||||
if cert is None or current:
|
if cert is None or current:
|
||||||
raise ModuleFailException("Failed to parse certificate chain download from {0}: {1} (headers: {2})".format(url, content, info))
|
raise ModuleFailException("Failed to parse certificate chain download from {0}: {1} (headers: {2})".format(url, content, info))
|
||||||
|
@ -669,9 +665,9 @@ class ACMEClient(object):
|
||||||
parsed_link = re.match(r'<(.+)>;rel="(\w+)"', link)
|
parsed_link = re.match(r'<(.+)>;rel="(\w+)"', link)
|
||||||
if parsed_link and parsed_link.group(2) == "up":
|
if parsed_link and parsed_link.group(2) == "up":
|
||||||
chain_link = parsed_link.group(1)
|
chain_link = parsed_link.group(1)
|
||||||
chain_result, chain_info = fetch_url(self.module, chain_link, method='GET')
|
chain_result, chain_info = self.account.get_request(chain_link, parse_json_result=False)
|
||||||
if chain_info['status'] in [200, 201]:
|
if chain_info['status'] in [200, 201]:
|
||||||
chain = [self._der_to_pem(chain_result.read())]
|
chain = [self._der_to_pem(chain_result)]
|
||||||
|
|
||||||
if info['status'] not in [200, 201]:
|
if info['status'] not in [200, 201]:
|
||||||
raise ModuleFailException("Error new cert: CODE: {0} RESULT: {1}".format(info['status'], result))
|
raise ModuleFailException("Error new cert: CODE: {0} RESULT: {1}".format(info['status'], result))
|
||||||
|
@ -698,7 +694,7 @@ class ACMEClient(object):
|
||||||
raise ModuleFailException("Error new order: CODE: {0} RESULT: {1}".format(info['status'], result))
|
raise ModuleFailException("Error new order: CODE: {0} RESULT: {1}".format(info['status'], result))
|
||||||
|
|
||||||
for auth_uri in result['authorizations']:
|
for auth_uri in result['authorizations']:
|
||||||
auth_data = simple_get(self.module, auth_uri)
|
auth_data, dummy = self.account.get_request(auth_uri)
|
||||||
auth_data['uri'] = auth_uri
|
auth_data['uri'] = auth_uri
|
||||||
domain = auth_data['identifier']['value']
|
domain = auth_data['identifier']['value']
|
||||||
if auth_data.get('wildcard', False):
|
if auth_data.get('wildcard', False):
|
||||||
|
@ -774,11 +770,7 @@ class ACMEClient(object):
|
||||||
else:
|
else:
|
||||||
# For ACME v2, we obtain the order object by fetching the
|
# For ACME v2, we obtain the order object by fetching the
|
||||||
# order URI, and extract the information from there.
|
# order URI, and extract the information from there.
|
||||||
resp, info = fetch_url(self.module, self.order_uri)
|
result, info = self.account.get_request(self.order_uri)
|
||||||
try:
|
|
||||||
result = resp.read()
|
|
||||||
except AttributeError:
|
|
||||||
result = info.get('body')
|
|
||||||
|
|
||||||
if not result:
|
if not result:
|
||||||
raise ModuleFailException("Cannot download order from {0}: {1} (headers: {2})".format(self.order_uri, result, info))
|
raise ModuleFailException("Cannot download order from {0}: {1} (headers: {2})".format(self.order_uri, result, info))
|
||||||
|
@ -786,9 +778,8 @@ class ACMEClient(object):
|
||||||
if info['status'] not in [200]:
|
if info['status'] not in [200]:
|
||||||
raise ModuleFailException("Error on downloading order: CODE: {0} RESULT: {1}".format(info['status'], result))
|
raise ModuleFailException("Error on downloading order: CODE: {0} RESULT: {1}".format(info['status'], result))
|
||||||
|
|
||||||
result = self.module.from_json(result.decode('utf8'))
|
|
||||||
for auth_uri in result['authorizations']:
|
for auth_uri in result['authorizations']:
|
||||||
auth_data = simple_get(self.module, auth_uri)
|
auth_data, dummy = self.account.get_request(auth_uri)
|
||||||
auth_data['uri'] = auth_uri
|
auth_data['uri'] = auth_uri
|
||||||
domain = auth_data['identifier']['value']
|
domain = auth_data['identifier']['value']
|
||||||
if auth_data.get('wildcard', False):
|
if auth_data.get('wildcard', False):
|
||||||
|
|
Loading…
Reference in a new issue