Remove vendored copy of ipaddress (#287)
* Remove vendored copy of ipaddress. * Forgot an import. * Remove sanity ignores and checks related to ipaddress. * Remove octal IPv4 address. Such IPs are no longer accepted by ipaddress in Python's standard library (CVE-2021-29921). * Remove unused import. Co-authored-by: Sviatoslav Sydorenko <wk.cvs.github@sydorenko.org.ua> Co-authored-by: Sviatoslav Sydorenko <wk.cvs.github@sydorenko.org.ua>pull/303/head
parent
ed03841fd1
commit
871a185ecb
|
@ -0,0 +1,3 @@
|
||||||
|
breaking_changes:
|
||||||
|
- "acme_* modules - removed vendored copy of the Python library ``ipaddress``. If you are using Python 2.x, please make sure to install the library (https://github.com/ansible-collections/community.crypto/pull/287)."
|
||||||
|
- "compatibility module_utils - removed vendored copy of the Python library ``ipaddress`` (https://github.com/ansible-collections/community.crypto/pull/287)."
|
|
@ -24,8 +24,8 @@ notes:
|
||||||
principle be used with any CA providing an ACME endpoint, such as
|
principle be used with any CA providing an ACME endpoint, such as
|
||||||
L(Buypass Go SSL,https://www.buypass.com/ssl/products/acme)."
|
L(Buypass Go SSL,https://www.buypass.com/ssl/products/acme)."
|
||||||
requirements:
|
requirements:
|
||||||
- python >= 2.6
|
|
||||||
- either openssl or L(cryptography,https://cryptography.io/) >= 1.5
|
- either openssl or L(cryptography,https://cryptography.io/) >= 1.5
|
||||||
|
- ipaddress
|
||||||
options:
|
options:
|
||||||
account_key_src:
|
account_key_src:
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -12,6 +12,7 @@ import copy
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import locale
|
import locale
|
||||||
|
import traceback
|
||||||
|
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import missing_required_lib
|
||||||
from ansible.module_utils.urls import fetch_url
|
from ansible.module_utils.urls import fetch_url
|
||||||
|
@ -38,6 +39,14 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.utils import
|
||||||
nopad_b64,
|
nopad_b64,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import ipaddress
|
||||||
|
except ImportError:
|
||||||
|
HAS_IPADDRESS = False
|
||||||
|
IPADDRESS_IMPORT_ERROR = traceback.format_exc()
|
||||||
|
else:
|
||||||
|
HAS_IPADDRESS = True
|
||||||
|
|
||||||
|
|
||||||
def _assert_fetch_url_success(module, response, info, allow_redirect=False, allow_client_error=True, allow_server_error=True):
|
def _assert_fetch_url_success(module, response, info, allow_redirect=False, allow_client_error=True, allow_server_error=True):
|
||||||
if info['status'] < 0:
|
if info['status'] < 0:
|
||||||
|
@ -327,6 +336,9 @@ def get_default_argspec():
|
||||||
|
|
||||||
|
|
||||||
def create_backend(module, needs_acme_v2):
|
def create_backend(module, needs_acme_v2):
|
||||||
|
if not HAS_IPADDRESS:
|
||||||
|
module.fail_json(msg=missing_required_lib('ipaddress'), exception=IPADDRESS_IMPORT_ERROR)
|
||||||
|
|
||||||
backend = module.params['select_crypto_backend']
|
backend = module.params['select_crypto_backend']
|
||||||
|
|
||||||
# Backend autodetect
|
# Backend autodetect
|
||||||
|
|
|
@ -29,7 +29,10 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import nopad_b64
|
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import nopad_b64
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress
|
try:
|
||||||
|
import ipaddress
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
_OPENSSL_ENVIRONMENT_UPDATE = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C')
|
_OPENSSL_ENVIRONMENT_UPDATE = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C')
|
||||||
|
@ -216,7 +219,7 @@ class OpenSSLCLIBackend(CryptoBackend):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _normalize_ip(ip):
|
def _normalize_ip(ip):
|
||||||
try:
|
try:
|
||||||
return to_native(compat_ipaddress.ip_address(to_text(ip)).compressed)
|
return to_native(ipaddress.ip_address(to_text(ip)).compressed)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# We don't want to error out on something IPAddress() can't parse
|
# We don't want to error out on something IPAddress() can't parse
|
||||||
return ip
|
return ip
|
||||||
|
|
|
@ -16,8 +16,6 @@ import time
|
||||||
|
|
||||||
from ansible.module_utils.common.text.converters import to_bytes
|
from ansible.module_utils.common.text.converters import to_bytes
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress
|
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
|
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
|
||||||
nopad_b64,
|
nopad_b64,
|
||||||
)
|
)
|
||||||
|
@ -28,6 +26,11 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
|
||||||
ModuleFailException,
|
ModuleFailException,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import ipaddress
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def create_key_authorization(client, token):
|
def create_key_authorization(client, token):
|
||||||
'''
|
'''
|
||||||
|
@ -110,7 +113,7 @@ class Challenge(object):
|
||||||
# https://www.rfc-editor.org/rfc/rfc8737.html#section-3
|
# https://www.rfc-editor.org/rfc/rfc8737.html#section-3
|
||||||
if identifier_type == 'ip':
|
if identifier_type == 'ip':
|
||||||
# IPv4/IPv6 address: use reverse mapping (RFC1034, RFC3596)
|
# IPv4/IPv6 address: use reverse mapping (RFC1034, RFC3596)
|
||||||
resource = compat_ipaddress.ip_address(identifier).reverse_pointer
|
resource = ipaddress.ip_address(identifier).reverse_pointer
|
||||||
if not resource.endswith('.'):
|
if not resource.endswith('.'):
|
||||||
resource += '.'
|
resource += '.'
|
||||||
else:
|
else:
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"disabled": true,
|
|
||||||
"all_targets": true,
|
|
||||||
"ignore_self": true,
|
|
||||||
"extensions": [
|
|
||||||
".py"
|
|
||||||
],
|
|
||||||
"prefixes": [
|
|
||||||
"plugins/module_utils/compat/"
|
|
||||||
],
|
|
||||||
"output": "path-message",
|
|
||||||
"requirements": [
|
|
||||||
"requests"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,143 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright (c) 2018 Ansible Project
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""
|
|
||||||
This test checks whether the libraries we're bundling are out of date and need to be synced with
|
|
||||||
a newer upstream release.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from __future__ import (absolute_import, division, print_function)
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
|
|
||||||
import packaging.specifiers
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
|
|
||||||
BUNDLED_RE = re.compile(b'\\b_BUNDLED_METADATA\\b')
|
|
||||||
|
|
||||||
|
|
||||||
def get_bundled_libs(paths):
|
|
||||||
"""
|
|
||||||
Return the set of known bundled libraries
|
|
||||||
|
|
||||||
:arg paths: The paths which the test has been instructed to check
|
|
||||||
:returns: The list of all files which we know to contain bundled libraries. If a bundled
|
|
||||||
library consists of multiple files, this should be the file which has metadata included.
|
|
||||||
"""
|
|
||||||
bundled_libs = set()
|
|
||||||
bundled_libs.add('plugins/module_utils/compat/ipaddress.py')
|
|
||||||
|
|
||||||
return bundled_libs
|
|
||||||
|
|
||||||
|
|
||||||
def get_files_with_bundled_metadata(paths):
|
|
||||||
"""
|
|
||||||
Search for any files which have bundled metadata inside of them
|
|
||||||
|
|
||||||
:arg paths: Iterable of filenames to search for metadata inside of
|
|
||||||
:returns: A set of pathnames which contained metadata
|
|
||||||
"""
|
|
||||||
|
|
||||||
with_metadata = set()
|
|
||||||
for path in paths:
|
|
||||||
with open(path, 'rb') as f:
|
|
||||||
body = f.read()
|
|
||||||
|
|
||||||
if BUNDLED_RE.search(body):
|
|
||||||
with_metadata.add(path)
|
|
||||||
|
|
||||||
return with_metadata
|
|
||||||
|
|
||||||
|
|
||||||
def get_bundled_metadata(filename):
|
|
||||||
"""
|
|
||||||
Retrieve the metadata about a bundled library from a python file
|
|
||||||
|
|
||||||
:arg filename: The filename to look inside for the metadata
|
|
||||||
:raises ValueError: If we're unable to extract metadata from the file
|
|
||||||
:returns: The metadata from the python file
|
|
||||||
"""
|
|
||||||
with open(filename, 'r') as module:
|
|
||||||
for line in module:
|
|
||||||
if line.strip().startswith('_BUNDLED_METADATA'):
|
|
||||||
data = line[line.index('{'):].strip()
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise ValueError('Unable to check bundled library for update. Please add'
|
|
||||||
' _BUNDLED_METADATA dictionary to the library file with'
|
|
||||||
' information on pypi name and bundled version.')
|
|
||||||
metadata = json.loads(data)
|
|
||||||
return metadata
|
|
||||||
|
|
||||||
|
|
||||||
def get_latest_applicable_version(pypi_data, constraints=None):
|
|
||||||
"""Get the latest pypi version of the package that we allow
|
|
||||||
|
|
||||||
:arg pypi_data: Pypi information about the data as returned by
|
|
||||||
``https://pypi.org/pypi/{pkg_name}/json``
|
|
||||||
:kwarg constraints: version constraints on what we're allowed to use as specified by
|
|
||||||
the bundled metadata
|
|
||||||
:returns: The most recent version on pypi that are allowed by ``constraints``
|
|
||||||
"""
|
|
||||||
latest_version = "0"
|
|
||||||
if constraints:
|
|
||||||
version_specification = packaging.specifiers.SpecifierSet(constraints)
|
|
||||||
for version in pypi_data['releases']:
|
|
||||||
if version in version_specification:
|
|
||||||
if LooseVersion(version) > LooseVersion(latest_version):
|
|
||||||
latest_version = version
|
|
||||||
else:
|
|
||||||
latest_version = pypi_data['info']['version']
|
|
||||||
|
|
||||||
return latest_version
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Entrypoint to the script"""
|
|
||||||
|
|
||||||
paths = sys.argv[1:] or sys.stdin.read().splitlines()
|
|
||||||
|
|
||||||
bundled_libs = get_bundled_libs(paths)
|
|
||||||
files_with_bundled_metadata = get_files_with_bundled_metadata(paths)
|
|
||||||
|
|
||||||
for filename in files_with_bundled_metadata.difference(bundled_libs):
|
|
||||||
print('{0}: ERROR: File contains _BUNDLED_METADATA but needs to be added to'
|
|
||||||
' test/sanity/code-smell/update-bundled.py'.format(filename))
|
|
||||||
|
|
||||||
for filename in bundled_libs:
|
|
||||||
try:
|
|
||||||
metadata = get_bundled_metadata(filename)
|
|
||||||
except ValueError as e:
|
|
||||||
print('{0}: ERROR: {1}'.format(filename, e))
|
|
||||||
continue
|
|
||||||
except (IOError, OSError) as e:
|
|
||||||
if e.errno == 2:
|
|
||||||
print('{0}: ERROR: {1}. Perhaps the bundled library has been removed'
|
|
||||||
' or moved and the bundled library test needs to be modified as'
|
|
||||||
' well?'.format(filename, e))
|
|
||||||
|
|
||||||
pypi_r = requests.get('https://pypi.org/pypi/{0}/json'.format(metadata['pypi_name']))
|
|
||||||
pypi_data = pypi_r.json()
|
|
||||||
|
|
||||||
constraints = metadata.get('version_constraints', None)
|
|
||||||
latest_version = get_latest_applicable_version(pypi_data, constraints)
|
|
||||||
|
|
||||||
if LooseVersion(metadata['version']) < LooseVersion(latest_version):
|
|
||||||
print('{0}: UPDATE {1} from {2} to {3} {4}'.format(
|
|
||||||
filename,
|
|
||||||
metadata['pypi_name'],
|
|
||||||
metadata['version'],
|
|
||||||
latest_version,
|
|
||||||
'https://pypi.org/pypi/{0}/json'.format(metadata['pypi_name'])))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
|
@ -1,7 +1,3 @@
|
||||||
plugins/module_utils/acme/__init__.py empty-init
|
plugins/module_utils/acme/__init__.py empty-init
|
||||||
plugins/module_utils/compat/ipaddress.py future-import-boilerplate
|
|
||||||
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate
|
|
||||||
plugins/module_utils/compat/ipaddress.py no-assert
|
|
||||||
plugins/module_utils/compat/ipaddress.py no-unicode-literals
|
|
||||||
plugins/module_utils/crypto/__init__.py empty-init
|
plugins/module_utils/crypto/__init__.py empty-init
|
||||||
plugins/modules/acme_account_info.py validate-modules:return-syntax-error
|
plugins/modules/acme_account_info.py validate-modules:return-syntax-error
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
plugins/module_utils/acme/__init__.py empty-init
|
plugins/module_utils/acme/__init__.py empty-init
|
||||||
plugins/module_utils/compat/ipaddress.py future-import-boilerplate
|
|
||||||
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate
|
|
||||||
plugins/module_utils/compat/ipaddress.py no-assert
|
|
||||||
plugins/module_utils/compat/ipaddress.py no-unicode-literals
|
|
||||||
plugins/module_utils/crypto/__init__.py empty-init
|
plugins/module_utils/crypto/__init__.py empty-init
|
||||||
plugins/modules/acme_account_info.py validate-modules:return-syntax-error
|
plugins/modules/acme_account_info.py validate-modules:return-syntax-error
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
plugins/module_utils/acme/__init__.py empty-init
|
plugins/module_utils/acme/__init__.py empty-init
|
||||||
plugins/module_utils/compat/ipaddress.py future-import-boilerplate
|
|
||||||
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate
|
|
||||||
plugins/module_utils/compat/ipaddress.py no-assert
|
|
||||||
plugins/module_utils/compat/ipaddress.py no-unicode-literals
|
|
||||||
plugins/module_utils/crypto/__init__.py empty-init
|
plugins/module_utils/crypto/__init__.py empty-init
|
||||||
plugins/modules/acme_account_info.py validate-modules:return-syntax-error
|
plugins/modules/acme_account_info.py validate-modules:return-syntax-error
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
plugins/module_utils/acme/__init__.py empty-init
|
plugins/module_utils/acme/__init__.py empty-init
|
||||||
plugins/module_utils/compat/ipaddress.py future-import-boilerplate
|
|
||||||
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate
|
|
||||||
plugins/module_utils/compat/ipaddress.py no-assert
|
|
||||||
plugins/module_utils/compat/ipaddress.py no-unicode-literals
|
|
||||||
plugins/module_utils/crypto/__init__.py empty-init
|
plugins/module_utils/crypto/__init__.py empty-init
|
||||||
plugins/modules/acme_account_info.py validate-modules:return-syntax-error
|
plugins/modules/acme_account_info.py validate-modules:return-syntax-error
|
||||||
|
|
|
@ -1,6 +1,2 @@
|
||||||
plugins/module_utils/acme/__init__.py empty-init
|
plugins/module_utils/acme/__init__.py empty-init
|
||||||
plugins/module_utils/compat/ipaddress.py future-import-boilerplate
|
|
||||||
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate
|
|
||||||
plugins/module_utils/compat/ipaddress.py no-assert
|
|
||||||
plugins/module_utils/compat/ipaddress.py no-unicode-literals
|
|
||||||
plugins/module_utils/crypto/__init__.py empty-init
|
plugins/module_utils/crypto/__init__.py empty-init
|
||||||
|
|
|
@ -24,7 +24,6 @@ TEST_IPS = [
|
||||||
("0000:0001:0000:0000:0001:0000:0000:0001", "0:1::1:0:0:1"),
|
("0000:0001:0000:0000:0001:0000:0000:0001", "0:1::1:0:0:1"),
|
||||||
("0000:0001:0000:0001:0000:0001:0000:0001", "0:1:0:1:0:1:0:1"),
|
("0000:0001:0000:0001:0000:0001:0000:0001", "0:1:0:1:0:1:0:1"),
|
||||||
("0.0.0.0", "0.0.0.0"),
|
("0.0.0.0", "0.0.0.0"),
|
||||||
("000.001.000.000", "0.1.0.0"),
|
|
||||||
("2001:d88:ac10:fe01:0:0:0:0", "2001:d88:ac10:fe01::"),
|
("2001:d88:ac10:fe01:0:0:0:0", "2001:d88:ac10:fe01::"),
|
||||||
("0000:0000:0000:0000:0000:0000:0000:0000", "::"),
|
("0000:0000:0000:0000:0000:0000:0000:0000", "::"),
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in New Issue