Add support for GSSAPI/Kerberos to urls.py (#72113)
* Add support for GSSAPI/Kerberos to urls.py * Test out changes with the latest test container * Get remote hosts working * Fix up httptester_krb5_password reader * Fix tests for opensuse and macOS * Hopefully last lot of testing changes * Dont do CBT on macOS * Fixes from review
This commit is contained in:
parent
c4acd41d6e
commit
caba47dd3f
43 changed files with 1028 additions and 11 deletions
3
changelogs/fragments/urls-gssapi.yml
Normal file
3
changelogs/fragments/urls-gssapi.yml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
minor_changes:
|
||||||
|
- Added support for GSSAPI/Kerberos authentication with ``urls.py`` that is used by ``uri`` and ``get_url``.
|
||||||
|
- Added support for specify custom credentials for GSSAPI authentication.
|
|
@ -17,6 +17,7 @@ HTTP Testing endpoint which provides the following capabilities:
|
||||||
* nginx
|
* nginx
|
||||||
* SSL
|
* SSL
|
||||||
* SNI
|
* SNI
|
||||||
|
* Negotiate Authentication
|
||||||
|
|
||||||
|
|
||||||
Source files can be found in the `http-test-container <https://github.com/ansible/http-test-container>`_ repository.
|
Source files can be found in the `http-test-container <https://github.com/ansible/http-test-container>`_ repository.
|
||||||
|
|
|
@ -74,17 +74,17 @@ import ansible.module_utils.six.moves.urllib.error as urllib_error
|
||||||
from ansible.module_utils.common.collections import Mapping
|
from ansible.module_utils.common.collections import Mapping
|
||||||
from ansible.module_utils.six import PY3, string_types
|
from ansible.module_utils.six import PY3, string_types
|
||||||
from ansible.module_utils.six.moves import cStringIO
|
from ansible.module_utils.six.moves import cStringIO
|
||||||
from ansible.module_utils.basic import get_distribution
|
from ansible.module_utils.basic import get_distribution, missing_required_lib
|
||||||
from ansible.module_utils._text import to_bytes, to_native, to_text
|
from ansible.module_utils._text import to_bytes, to_native, to_text
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# python3
|
# python3
|
||||||
import urllib.request as urllib_request
|
import urllib.request as urllib_request
|
||||||
from urllib.request import AbstractHTTPHandler
|
from urllib.request import AbstractHTTPHandler, BaseHandler
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# python2
|
# python2
|
||||||
import urllib2 as urllib_request
|
import urllib2 as urllib_request
|
||||||
from urllib2 import AbstractHTTPHandler
|
from urllib2 import AbstractHTTPHandler, BaseHandler
|
||||||
|
|
||||||
urllib_request.HTTPRedirectHandler.http_error_308 = urllib_request.HTTPRedirectHandler.http_error_307
|
urllib_request.HTTPRedirectHandler.http_error_308 = urllib_request.HTTPRedirectHandler.http_error_307
|
||||||
|
|
||||||
|
@ -171,13 +171,105 @@ except ImportError:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_MATCH_HOSTNAME = False
|
HAS_MATCH_HOSTNAME = False
|
||||||
|
|
||||||
|
HAS_CRYPTOGRAPHY = True
|
||||||
|
try:
|
||||||
|
from cryptography import x509
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives import hashes
|
||||||
|
from cryptography.exceptions import UnsupportedAlgorithm
|
||||||
|
except ImportError:
|
||||||
|
HAS_CRYPTOGRAPHY = False
|
||||||
|
|
||||||
|
# Old import for GSSAPI authentication, this is not used in urls.py but kept for backwards compatibility.
|
||||||
try:
|
try:
|
||||||
import urllib_gssapi
|
import urllib_gssapi
|
||||||
HAS_GSSAPI = True
|
HAS_GSSAPI = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_GSSAPI = False
|
HAS_GSSAPI = False
|
||||||
|
|
||||||
|
GSSAPI_IMP_ERR = None
|
||||||
|
try:
|
||||||
|
import gssapi
|
||||||
|
|
||||||
|
class HTTPGSSAPIAuthHandler(BaseHandler):
|
||||||
|
""" Handles Negotiate/Kerberos support through the gssapi library. """
|
||||||
|
|
||||||
|
AUTH_HEADER_PATTERN = re.compile(r'(?:.*)\s*(Negotiate|Kerberos)\s*([^,]*),?', re.I)
|
||||||
|
handler_order = 480 # Handle before Digest authentication
|
||||||
|
|
||||||
|
def __init__(self, username=None, password=None):
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
self._context = None
|
||||||
|
|
||||||
|
def get_auth_value(self, headers):
|
||||||
|
auth_match = self.AUTH_HEADER_PATTERN.search(headers.get('www-authenticate', ''))
|
||||||
|
if auth_match:
|
||||||
|
return auth_match.group(1), base64.b64decode(auth_match.group(2))
|
||||||
|
|
||||||
|
def http_error_401(self, req, fp, code, msg, headers):
|
||||||
|
# If we've already attempted the auth and we've reached this again then there was a failure.
|
||||||
|
if self._context:
|
||||||
|
return
|
||||||
|
|
||||||
|
parsed = generic_urlparse(urlparse(req.get_full_url()))
|
||||||
|
|
||||||
|
auth_header = self.get_auth_value(headers)
|
||||||
|
if not auth_header:
|
||||||
|
return
|
||||||
|
auth_protocol, in_token = auth_header
|
||||||
|
|
||||||
|
username = None
|
||||||
|
if self.username:
|
||||||
|
username = gssapi.Name(self.username, name_type=gssapi.NameType.user)
|
||||||
|
|
||||||
|
if username and self.password:
|
||||||
|
if not hasattr(gssapi.raw, 'acquire_cred_with_password'):
|
||||||
|
raise NotImplementedError("Platform GSSAPI library does not support "
|
||||||
|
"gss_acquire_cred_with_password, cannot acquire GSSAPI credential with "
|
||||||
|
"explicit username and password.")
|
||||||
|
|
||||||
|
b_password = to_bytes(self.password, errors='surrogate_or_strict')
|
||||||
|
cred = gssapi.raw.acquire_cred_with_password(username, b_password, usage='initiate').creds
|
||||||
|
|
||||||
|
else:
|
||||||
|
cred = gssapi.Credentials(name=username, usage='initiate')
|
||||||
|
|
||||||
|
# Get the peer certificate for the channel binding token if possible (HTTPS). A bug on macOS causes the
|
||||||
|
# authentication to fail when the CBT is present. Just skip that platform.
|
||||||
|
cbt = None
|
||||||
|
cert = getpeercert(fp, True)
|
||||||
|
if cert and platform.system() != 'Darwin':
|
||||||
|
cert_hash = get_channel_binding_cert_hash(cert)
|
||||||
|
if cert_hash:
|
||||||
|
cbt = gssapi.raw.ChannelBindings(application_data=b"tls-server-end-point:" + cert_hash)
|
||||||
|
|
||||||
|
# TODO: We could add another option that is set to include the port in the SPN if desired in the future.
|
||||||
|
target = gssapi.Name("HTTP@%s" % parsed['hostname'], gssapi.NameType.hostbased_service)
|
||||||
|
self._context = gssapi.SecurityContext(usage="initiate", name=target, creds=cred, channel_bindings=cbt)
|
||||||
|
|
||||||
|
resp = None
|
||||||
|
while not self._context.complete:
|
||||||
|
out_token = self._context.step(in_token)
|
||||||
|
if not out_token:
|
||||||
|
break
|
||||||
|
|
||||||
|
auth_header = '%s %s' % (auth_protocol, to_native(base64.b64encode(out_token)))
|
||||||
|
req.add_unredirected_header('Authorization', auth_header)
|
||||||
|
resp = self.parent.open(req)
|
||||||
|
|
||||||
|
# The response could contain a token that the client uses to validate the server
|
||||||
|
auth_header = self.get_auth_value(resp.headers)
|
||||||
|
if not auth_header:
|
||||||
|
break
|
||||||
|
in_token = auth_header[1]
|
||||||
|
|
||||||
|
return resp
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
GSSAPI_IMP_ERR = traceback.format_exc()
|
||||||
|
HTTPGSSAPIAuthHandler = None
|
||||||
|
|
||||||
if not HAS_MATCH_HOSTNAME:
|
if not HAS_MATCH_HOSTNAME:
|
||||||
# The following block of code is under the terms and conditions of the
|
# The following block of code is under the terms and conditions of the
|
||||||
# Python Software Foundation License
|
# Python Software Foundation License
|
||||||
|
@ -408,6 +500,13 @@ class NoSSLError(SSLValidationError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MissingModuleError(Exception):
|
||||||
|
"""Failed to import 3rd party module required by the caller"""
|
||||||
|
def __init__(self, message, import_traceback):
|
||||||
|
super(MissingModuleError, self).__init__(message)
|
||||||
|
self.import_traceback = import_traceback
|
||||||
|
|
||||||
|
|
||||||
# Some environments (Google Compute Engine's CoreOS deploys) do not compile
|
# Some environments (Google Compute Engine's CoreOS deploys) do not compile
|
||||||
# against openssl and thus do not have any HTTPS support.
|
# against openssl and thus do not have any HTTPS support.
|
||||||
CustomHTTPSConnection = None
|
CustomHTTPSConnection = None
|
||||||
|
@ -1032,6 +1131,43 @@ def maybe_add_ssl_handler(url, validate_certs, ca_path=None):
|
||||||
return SSLValidationHandler(parsed.hostname, parsed.port or 443, ca_path=ca_path)
|
return SSLValidationHandler(parsed.hostname, parsed.port or 443, ca_path=ca_path)
|
||||||
|
|
||||||
|
|
||||||
|
def getpeercert(response, binary_form=False):
|
||||||
|
""" Attempt to get the peer certificate of the response from urlopen. """
|
||||||
|
# The response from urllib2.open() is different across Python 2 and 3
|
||||||
|
if PY3:
|
||||||
|
socket = response.fp.raw._sock
|
||||||
|
else:
|
||||||
|
socket = response.fp._sock.fp._sock
|
||||||
|
|
||||||
|
try:
|
||||||
|
return socket.getpeercert(binary_form)
|
||||||
|
except AttributeError:
|
||||||
|
pass # Not HTTPS
|
||||||
|
|
||||||
|
|
||||||
|
def get_channel_binding_cert_hash(certificate_der):
|
||||||
|
""" Gets the channel binding app data for a TLS connection using the peer cert. """
|
||||||
|
if not HAS_CRYPTOGRAPHY:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Logic documented in RFC 5929 section 4 https://tools.ietf.org/html/rfc5929#section-4
|
||||||
|
cert = x509.load_der_x509_certificate(certificate_der, default_backend())
|
||||||
|
|
||||||
|
hash_algorithm = None
|
||||||
|
try:
|
||||||
|
hash_algorithm = cert.signature_hash_algorithm
|
||||||
|
except UnsupportedAlgorithm:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# If the signature hash algorithm is unknown/unsupported or md5/sha1 we must use SHA256.
|
||||||
|
if not hash_algorithm or hash_algorithm.name in ['md5', 'sha1']:
|
||||||
|
hash_algorithm = hashes.SHA256()
|
||||||
|
|
||||||
|
digest = hashes.Hash(hash_algorithm, default_backend())
|
||||||
|
digest.update(certificate_der)
|
||||||
|
return digest.finalize()
|
||||||
|
|
||||||
|
|
||||||
def rfc2822_date_string(timetuple, zone='-0000'):
|
def rfc2822_date_string(timetuple, zone='-0000'):
|
||||||
"""Accepts a timetuple and optional zone which defaults to ``-0000``
|
"""Accepts a timetuple and optional zone which defaults to ``-0000``
|
||||||
and returns a date string as specified by RFC 2822, e.g.:
|
and returns a date string as specified by RFC 2822, e.g.:
|
||||||
|
@ -1176,15 +1312,13 @@ class Request:
|
||||||
ssl_handler = maybe_add_ssl_handler(url, validate_certs, ca_path=ca_path)
|
ssl_handler = maybe_add_ssl_handler(url, validate_certs, ca_path=ca_path)
|
||||||
if ssl_handler and not HAS_SSLCONTEXT:
|
if ssl_handler and not HAS_SSLCONTEXT:
|
||||||
handlers.append(ssl_handler)
|
handlers.append(ssl_handler)
|
||||||
if HAS_GSSAPI and use_gssapi:
|
|
||||||
handlers.append(urllib_gssapi.HTTPSPNEGOAuthHandler())
|
|
||||||
|
|
||||||
parsed = generic_urlparse(urlparse(url))
|
parsed = generic_urlparse(urlparse(url))
|
||||||
if parsed.scheme != 'ftp':
|
if parsed.scheme != 'ftp':
|
||||||
username = url_username
|
username = url_username
|
||||||
|
password = url_password
|
||||||
|
|
||||||
if username:
|
if username:
|
||||||
password = url_password
|
|
||||||
netloc = parsed.netloc
|
netloc = parsed.netloc
|
||||||
elif '@' in parsed.netloc:
|
elif '@' in parsed.netloc:
|
||||||
credentials, netloc = parsed.netloc.split('@', 1)
|
credentials, netloc = parsed.netloc.split('@', 1)
|
||||||
|
@ -1200,7 +1334,15 @@ class Request:
|
||||||
# reconstruct url without credentials
|
# reconstruct url without credentials
|
||||||
url = urlunparse(parsed_list)
|
url = urlunparse(parsed_list)
|
||||||
|
|
||||||
if username and not force_basic_auth:
|
if use_gssapi:
|
||||||
|
if HTTPGSSAPIAuthHandler:
|
||||||
|
handlers.append(HTTPGSSAPIAuthHandler(username, password))
|
||||||
|
else:
|
||||||
|
imp_err_msg = missing_required_lib('gssapi', reason='for use_gssapi=True',
|
||||||
|
url='https://pypi.org/project/gssapi/')
|
||||||
|
raise MissingModuleError(imp_err_msg, import_traceback=GSSAPI_IMP_ERR)
|
||||||
|
|
||||||
|
elif username and not force_basic_auth:
|
||||||
passman = urllib_request.HTTPPasswordMgrWithDefaultRealm()
|
passman = urllib_request.HTTPPasswordMgrWithDefaultRealm()
|
||||||
|
|
||||||
# this creates a password manager
|
# this creates a password manager
|
||||||
|
@ -1543,6 +1685,7 @@ def url_argument_spec():
|
||||||
force_basic_auth=dict(type='bool', default=False),
|
force_basic_auth=dict(type='bool', default=False),
|
||||||
client_cert=dict(type='path'),
|
client_cert=dict(type='path'),
|
||||||
client_key=dict(type='path'),
|
client_key=dict(type='path'),
|
||||||
|
use_gssapi=dict(type='bool', default=False),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1603,6 +1746,7 @@ def fetch_url(module, url, data=None, headers=None, method=None,
|
||||||
|
|
||||||
client_cert = module.params.get('client_cert')
|
client_cert = module.params.get('client_cert')
|
||||||
client_key = module.params.get('client_key')
|
client_key = module.params.get('client_key')
|
||||||
|
use_gssapi = module.params.get('use_gssapi', use_gssapi)
|
||||||
|
|
||||||
if not isinstance(cookies, cookiejar.CookieJar):
|
if not isinstance(cookies, cookiejar.CookieJar):
|
||||||
cookies = cookiejar.LWPCookieJar()
|
cookies = cookiejar.LWPCookieJar()
|
||||||
|
@ -1655,6 +1799,8 @@ def fetch_url(module, url, data=None, headers=None, method=None,
|
||||||
module.fail_json(msg='%s' % to_native(e), **info)
|
module.fail_json(msg='%s' % to_native(e), **info)
|
||||||
except (ConnectionError, ValueError) as e:
|
except (ConnectionError, ValueError) as e:
|
||||||
module.fail_json(msg=to_native(e), **info)
|
module.fail_json(msg=to_native(e), **info)
|
||||||
|
except MissingModuleError as e:
|
||||||
|
module.fail_json(msg=to_text(e), exception=e.import_traceback)
|
||||||
except urllib_error.HTTPError as e:
|
except urllib_error.HTTPError as e:
|
||||||
try:
|
try:
|
||||||
body = e.read()
|
body = e.read()
|
||||||
|
|
|
@ -166,6 +166,17 @@ options:
|
||||||
- Header to identify as, generally appears in web server logs.
|
- Header to identify as, generally appears in web server logs.
|
||||||
type: str
|
type: str
|
||||||
default: ansible-httpget
|
default: ansible-httpget
|
||||||
|
use_gssapi:
|
||||||
|
description:
|
||||||
|
- Use GSSAPI to perform the authentication, typically this is for Kerberos or Kerberos through Negotiate
|
||||||
|
authentication.
|
||||||
|
- Requires the Python library L(gssapi,https://github.com/pythongssapi/python-gssapi) to be installed.
|
||||||
|
- Credentials for GSSAPI can be specified with I(url_username)/I(url_password) or with the GSSAPI env var
|
||||||
|
C(KRB5CCNAME) that specified a custom Kerberos credential cache.
|
||||||
|
- NTLM authentication is C(not) supported even if the GSSAPI mech for NTLM has been installed.
|
||||||
|
type: bool
|
||||||
|
default: no
|
||||||
|
version_added: '2.11'
|
||||||
# informational: requirements for nodes
|
# informational: requirements for nodes
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- files
|
- files
|
||||||
|
|
|
@ -176,6 +176,17 @@ options:
|
||||||
- Header to identify as, generally appears in web server logs.
|
- Header to identify as, generally appears in web server logs.
|
||||||
type: str
|
type: str
|
||||||
default: ansible-httpget
|
default: ansible-httpget
|
||||||
|
use_gssapi:
|
||||||
|
description:
|
||||||
|
- Use GSSAPI to perform the authentication, typically this is for Kerberos or Kerberos through Negotiate
|
||||||
|
authentication.
|
||||||
|
- Requires the Python library L(gssapi,https://github.com/pythongssapi/python-gssapi) to be installed.
|
||||||
|
- Credentials for GSSAPI can be specified with I(url_username)/I(url_password) or with the GSSAPI env var
|
||||||
|
C(KRB5CCNAME) that specified a custom Kerberos credential cache.
|
||||||
|
- NTLM authentication is C(not) supported even if the GSSAPI mech for NTLM has been installed.
|
||||||
|
type: bool
|
||||||
|
default: no
|
||||||
|
version_added: '2.11'
|
||||||
notes:
|
notes:
|
||||||
- The dependency on httplib2 was removed in Ansible 2.1.
|
- The dependency on httplib2 was removed in Ansible 2.1.
|
||||||
- The module returns all the HTTP headers in lower-case.
|
- The module returns all the HTTP headers in lower-case.
|
||||||
|
|
|
@ -63,4 +63,15 @@ options:
|
||||||
- PEM formatted file that contains your private key to be used for SSL client authentication.
|
- PEM formatted file that contains your private key to be used for SSL client authentication.
|
||||||
- If C(client_cert) contains both the certificate and key, this option is not required.
|
- If C(client_cert) contains both the certificate and key, this option is not required.
|
||||||
type: path
|
type: path
|
||||||
|
use_gssapi:
|
||||||
|
description:
|
||||||
|
- Use GSSAPI to perform the authentication, typically this is for Kerberos or Kerberos through Negotiate
|
||||||
|
authentication.
|
||||||
|
- Requires the Python library L(gssapi,https://github.com/pythongssapi/python-gssapi) to be installed.
|
||||||
|
- Credentials for GSSAPI can be specified with I(url_username)/I(url_password) or with the GSSAPI env var
|
||||||
|
C(KRB5CCNAME) that specified a custom Kerberos credential cache.
|
||||||
|
- NTLM authentication is C(not) supported even if the GSSAPI mech for NTLM has been installed.
|
||||||
|
type: bool
|
||||||
|
default: no
|
||||||
|
version_added: '2.11'
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -99,7 +99,9 @@ options:
|
||||||
- section: url_lookup
|
- section: url_lookup
|
||||||
key: follow_redirects
|
key: follow_redirects
|
||||||
use_gssapi:
|
use_gssapi:
|
||||||
description: Use GSSAPI handler of requests
|
description:
|
||||||
|
- Use GSSAPI handler of requests
|
||||||
|
- As of Ansible 2.11, GSSAPI credentials can be specified with I(username) and I(password).
|
||||||
type: boolean
|
type: boolean
|
||||||
version_added: "2.10"
|
version_added: "2.10"
|
||||||
default: False
|
default: False
|
||||||
|
|
|
@ -534,3 +534,12 @@
|
||||||
that:
|
that:
|
||||||
- '(result.content | b64decode) == "ansible.http.tests:SUCCESS"'
|
- '(result.content | b64decode) == "ansible.http.tests:SUCCESS"'
|
||||||
when: has_httptester
|
when: has_httptester
|
||||||
|
|
||||||
|
- name: Test use_gssapi=True
|
||||||
|
include_tasks:
|
||||||
|
file: use_gssapi.yml
|
||||||
|
apply:
|
||||||
|
environment:
|
||||||
|
KRB5_CONFIG: '{{ krb5_config }}'
|
||||||
|
KRB5CCNAME: FILE:{{ remote_tmp_dir }}/krb5.cc
|
||||||
|
when: krb5_config is defined
|
||||||
|
|
60
test/integration/targets/get_url/tasks/use_gssapi.yml
Normal file
60
test/integration/targets/get_url/tasks/use_gssapi.yml
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
- name: Skip explicit auth tests on FreeBSD as Heimdal there does not have gss_acquire_cred_with_password
|
||||||
|
when: ansible_facts.os_family != 'FreeBSD'
|
||||||
|
block:
|
||||||
|
- name: test Negotiate auth over HTTP with explicit credentials
|
||||||
|
get_url:
|
||||||
|
url: http://{{ httpbin_host }}/gssapi
|
||||||
|
dest: '{{ remote_tmp_dir }}/gssapi_explicit.txt'
|
||||||
|
use_gssapi: yes
|
||||||
|
url_username: '{{ krb5_username }}'
|
||||||
|
url_password: '{{ krb5_password }}'
|
||||||
|
register: http_explicit
|
||||||
|
|
||||||
|
- name: get result of test Negotiate auth over HTTP with explicit credentials
|
||||||
|
slurp:
|
||||||
|
path: '{{ remote_tmp_dir }}/gssapi_explicit.txt'
|
||||||
|
register: http_explicit_actual
|
||||||
|
|
||||||
|
- name: assert test Negotiate auth with implicit credentials
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- http_explicit.status_code == 200
|
||||||
|
- http_explicit_actual.content | b64decode | trim == 'Microsoft Rulz'
|
||||||
|
|
||||||
|
- name: FreeBSD - verify it fails with explicit credential
|
||||||
|
get_url:
|
||||||
|
url: http://{{ httpbin_host }}/gssapi
|
||||||
|
dest: '{{ remote_tmp_dir }}/gssapi_explicit.txt'
|
||||||
|
use_gssapi: yes
|
||||||
|
url_username: '{{ krb5_username }}'
|
||||||
|
url_password: '{{ krb5_password }}'
|
||||||
|
register: explicit_failure
|
||||||
|
when: ansible_facts.os_family == 'FreeBSD'
|
||||||
|
failed_when:
|
||||||
|
- '"Platform GSSAPI library does not support gss_acquire_cred_with_password, cannot acquire GSSAPI credential with explicit username and password" not in explicit_failure.msg'
|
||||||
|
|
||||||
|
- name: skip tests on macOS, I cannot seem to get it to read a credential from a custom ccache
|
||||||
|
when: ansible_facts.distribution != 'MacOSX'
|
||||||
|
block:
|
||||||
|
- name: get Kerberos ticket for implicit auth tests
|
||||||
|
httptester_kinit:
|
||||||
|
username: '{{ krb5_username }}'
|
||||||
|
password: '{{ krb5_password }}'
|
||||||
|
|
||||||
|
- name: test Negotiate auth over HTTPS with implicit credentials
|
||||||
|
get_url:
|
||||||
|
url: https://{{ httpbin_host }}/gssapi
|
||||||
|
dest: '{{ remote_tmp_dir }}/gssapi_implicit.txt'
|
||||||
|
use_gssapi: yes
|
||||||
|
register: https_implicit
|
||||||
|
|
||||||
|
- name: get result of test Negotiate auth over HTTPS with implicit credentials
|
||||||
|
slurp:
|
||||||
|
path: '{{ remote_tmp_dir }}/gssapi_implicit.txt'
|
||||||
|
register: https_implicit_actual
|
||||||
|
|
||||||
|
- name: assert test Negotiate auth with implicit credentials
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- https_implicit.status_code == 200
|
||||||
|
- https_implicit_actual.content | b64decode | trim == 'Microsoft Rulz'
|
2
test/integration/targets/module_utils_urls/aliases
Normal file
2
test/integration/targets/module_utils_urls/aliases
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
shippable/posix/group1
|
||||||
|
needs/httptester
|
|
@ -0,0 +1,98 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
# Copyright: (c) 2020, Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
---
|
||||||
|
module: test_perrcert
|
||||||
|
short_description: Test getting the peer certificate of a HTTP response
|
||||||
|
description: Test getting the peer certificate of a HTTP response.
|
||||||
|
options:
|
||||||
|
url:
|
||||||
|
description: The endpoint to get the peer cert for
|
||||||
|
required: true
|
||||||
|
type: str
|
||||||
|
author:
|
||||||
|
- Ansible Project
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = r'''
|
||||||
|
#
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = r'''
|
||||||
|
#
|
||||||
|
'''
|
||||||
|
|
||||||
|
import base64
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.common.text.converters import to_text
|
||||||
|
from ansible.module_utils.urls import getpeercert, Request
|
||||||
|
|
||||||
|
|
||||||
|
def get_x509_shorthand(name, value):
|
||||||
|
prefix = {
|
||||||
|
'countryName': 'C',
|
||||||
|
'stateOrProvinceName': 'ST',
|
||||||
|
'localityName': 'L',
|
||||||
|
'organizationName': 'O',
|
||||||
|
'commonName': 'CN',
|
||||||
|
'organizationalUnitName': 'OU',
|
||||||
|
}[name]
|
||||||
|
|
||||||
|
return '%s=%s' % (prefix, value)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
module_args = dict(
|
||||||
|
url=dict(type='str', required=True),
|
||||||
|
)
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec=module_args,
|
||||||
|
supports_check_mode=True,
|
||||||
|
)
|
||||||
|
result = {
|
||||||
|
'changed': False,
|
||||||
|
'cert': None,
|
||||||
|
'raw_cert': None,
|
||||||
|
}
|
||||||
|
|
||||||
|
req = Request().get(module.params['url'])
|
||||||
|
try:
|
||||||
|
cert = getpeercert(req)
|
||||||
|
b_cert = getpeercert(req, binary_form=True)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
req.close()
|
||||||
|
|
||||||
|
if cert:
|
||||||
|
processed_cert = {
|
||||||
|
'issuer': '',
|
||||||
|
'not_after': cert.get('notAfter', None),
|
||||||
|
'not_before': cert.get('notBefore', None),
|
||||||
|
'serial_number': cert.get('serialNumber', None),
|
||||||
|
'subject': '',
|
||||||
|
'version': cert.get('version', None),
|
||||||
|
}
|
||||||
|
|
||||||
|
for field in ['issuer', 'subject']:
|
||||||
|
field_values = []
|
||||||
|
for x509_part in cert.get(field, []):
|
||||||
|
field_values.append(get_x509_shorthand(x509_part[0][0], x509_part[0][1]))
|
||||||
|
|
||||||
|
processed_cert[field] = ",".join(field_values)
|
||||||
|
|
||||||
|
result['cert'] = processed_cert
|
||||||
|
|
||||||
|
if b_cert:
|
||||||
|
result['raw_cert'] = to_text(base64.b64encode(b_cert))
|
||||||
|
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
3
test/integration/targets/module_utils_urls/meta/main.yml
Normal file
3
test/integration/targets/module_utils_urls/meta/main.yml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
dependencies:
|
||||||
|
- prepare_http_tests
|
||||||
|
- setup_remote_tmp_dir
|
32
test/integration/targets/module_utils_urls/tasks/main.yml
Normal file
32
test/integration/targets/module_utils_urls/tasks/main.yml
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
- name: get peercert for HTTP connection
|
||||||
|
test_peercert:
|
||||||
|
url: http://{{ httpbin_host }}/get
|
||||||
|
register: cert_http
|
||||||
|
|
||||||
|
- name: assert get peercert for HTTP connection
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cert_http.raw_cert == None
|
||||||
|
|
||||||
|
- name: get peercert for HTTPS connection
|
||||||
|
test_peercert:
|
||||||
|
url: https://{{ httpbin_host }}/get
|
||||||
|
register: cert_https
|
||||||
|
|
||||||
|
# Alpine does not have openssl, just make sure the text was actually set instead
|
||||||
|
- name: check if openssl is installed
|
||||||
|
command: which openssl
|
||||||
|
ignore_errors: yes
|
||||||
|
register: openssl
|
||||||
|
|
||||||
|
- name: get actual certificate from endpoint
|
||||||
|
shell: echo | openssl s_client -connect {{ httpbin_host }}:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'
|
||||||
|
register: cert_https_actual
|
||||||
|
changed_when: no
|
||||||
|
when: openssl is successful
|
||||||
|
|
||||||
|
- name: assert get peercert for HTTPS connection
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cert_https.raw_cert != None
|
||||||
|
- openssl is failed or cert_https.raw_cert == cert_https_actual.stdout_lines[1:-1] | join("")
|
|
@ -0,0 +1,4 @@
|
||||||
|
- name: Remove python gssapi
|
||||||
|
pip:
|
||||||
|
name: gssapi
|
||||||
|
state: absent
|
|
@ -0,0 +1,134 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
# Copyright: (c) 2020, Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
---
|
||||||
|
module: httptester_kinit
|
||||||
|
short_description: Get Kerberos ticket
|
||||||
|
description: Get Kerberos ticket using kinit non-interactively.
|
||||||
|
options:
|
||||||
|
username:
|
||||||
|
description: The username to get the ticket for.
|
||||||
|
required: true
|
||||||
|
type: str
|
||||||
|
password:
|
||||||
|
description: The password for I(username).
|
||||||
|
required; true
|
||||||
|
type: str
|
||||||
|
author:
|
||||||
|
- Ansible Project
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = r'''
|
||||||
|
#
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = r'''
|
||||||
|
#
|
||||||
|
'''
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
import errno
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
||||||
|
|
||||||
|
try:
|
||||||
|
import configparser
|
||||||
|
except ImportError:
|
||||||
|
import ConfigParser as configparser
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def env_path(name, value, default_value):
|
||||||
|
""" Adds a value to a PATH-like env var and preserve the existing value if present. """
|
||||||
|
orig_value = os.environ.get(name, None)
|
||||||
|
os.environ[name] = '%s:%s' % (value, orig_value or default_value)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if orig_value:
|
||||||
|
os.environ[name] = orig_value
|
||||||
|
|
||||||
|
else:
|
||||||
|
del os.environ[name]
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def krb5_conf(module, config):
|
||||||
|
""" Runs with a custom krb5.conf file that extends the existing config if present. """
|
||||||
|
if config:
|
||||||
|
ini_config = configparser.ConfigParser()
|
||||||
|
for section, entries in config.items():
|
||||||
|
ini_config.add_section(section)
|
||||||
|
for key, value in entries.items():
|
||||||
|
ini_config.set(section, key, value)
|
||||||
|
|
||||||
|
config_path = os.path.join(module.tmpdir, 'krb5.conf')
|
||||||
|
with open(config_path, mode='wt') as config_fd:
|
||||||
|
ini_config.write(config_fd)
|
||||||
|
|
||||||
|
with env_path('KRB5_CONFIG', config_path, '/etc/krb5.conf'):
|
||||||
|
yield
|
||||||
|
|
||||||
|
else:
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
module_args = dict(
|
||||||
|
username=dict(type='str', required=True),
|
||||||
|
password=dict(type='str', required=True, no_log=True),
|
||||||
|
)
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec=module_args,
|
||||||
|
required_together=[('username', 'password')],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Debugging purposes, get the Kerberos version. On platforms like OpenSUSE this may not be on the PATH.
|
||||||
|
try:
|
||||||
|
process = subprocess.Popen(['krb5-config', '--version'], stdout=subprocess.PIPE)
|
||||||
|
stdout, stderr = process.communicate()
|
||||||
|
version = to_text(stdout)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno != errno.ENOENT:
|
||||||
|
raise
|
||||||
|
version = 'Unknown (no krb5-config)'
|
||||||
|
|
||||||
|
# Heimdal has a few quirks that we want to paper over in this module
|
||||||
|
# 1. KRB5_TRACE does not work in any released version (<=7.7), we need to use a custom krb5.config to enable it
|
||||||
|
# 2. When reading the password it reads from the pty not stdin by default causing an issue with subprocess. We
|
||||||
|
# can control that behaviour with '--password-file=STDIN'
|
||||||
|
is_heimdal = os.uname()[0] in ['Darwin', 'FreeBSD']
|
||||||
|
|
||||||
|
kinit_args = ['kinit']
|
||||||
|
config = {}
|
||||||
|
if is_heimdal:
|
||||||
|
kinit_args.append('--password-file=STDIN')
|
||||||
|
config['logging'] = {'krb5': 'FILE:/dev/stdout'}
|
||||||
|
kinit_args.append(to_text(module.params['username'], errors='surrogate_or_strict'))
|
||||||
|
|
||||||
|
with krb5_conf(module, config):
|
||||||
|
# Weirdly setting KRB5_CONFIG in the modules environment block does not work unless we pass it in explicitly.
|
||||||
|
# Take a copy of the existing environment to make sure the process has the same env vars as ours. Also set
|
||||||
|
# KRB5_TRACE to output and debug logs helping to identify problems when calling kinit with MIT.
|
||||||
|
kinit_env = os.environ.copy()
|
||||||
|
kinit_env['KRB5_TRACE'] = '/dev/stdout'
|
||||||
|
|
||||||
|
process = subprocess.Popen(kinit_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||||
|
env=kinit_env)
|
||||||
|
stdout, stderr = process.communicate(to_bytes(module.params['password'], errors='surrogate_or_strict') + b'\n')
|
||||||
|
rc = process.returncode
|
||||||
|
|
||||||
|
module.exit_json(changed=True, stdout=to_text(stdout), stderr=to_text(stderr), rc=rc, version=version)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -1,2 +1,3 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
- setup_remote_tmp_dir
|
- setup_remote_tmp_dir
|
||||||
|
- setup_remote_constraints
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
- set_fact:
|
||||||
|
krb5_config: '{{ remote_tmp_dir }}/krb5.conf'
|
||||||
|
krb5_realm: '{{ httpbin_host.split(".")[1:] | join(".") | upper }}'
|
||||||
|
krb5_provider: '{{ (ansible_facts.os_family == "FreeBSD" or ansible_facts.distribution == "MacOSX") | ternary("Heimdal", "MIT") }}'
|
||||||
|
|
||||||
|
- set_fact:
|
||||||
|
krb5_username: admin@{{ krb5_realm }}
|
||||||
|
|
||||||
|
- name: Create krb5.conf file
|
||||||
|
template:
|
||||||
|
src: krb5.conf.j2
|
||||||
|
dest: '{{ krb5_config }}'
|
||||||
|
|
||||||
|
- name: Include distribution specific variables
|
||||||
|
include_vars: '{{ lookup("first_found", params) }}'
|
||||||
|
vars:
|
||||||
|
params:
|
||||||
|
files:
|
||||||
|
- '{{ ansible_facts.distribution }}-{{ ansible_facts.distribution_major_version }}.yml'
|
||||||
|
- '{{ ansible_facts.os_family }}-{{ ansible_facts.distribution_major_version }}.yml'
|
||||||
|
- '{{ ansible_facts.distribution }}.yml'
|
||||||
|
- '{{ ansible_facts.os_family }}.yml'
|
||||||
|
- default.yml
|
||||||
|
paths:
|
||||||
|
- '{{ role_path }}/vars'
|
||||||
|
|
||||||
|
- name: Install Kerberos sytem packages
|
||||||
|
package:
|
||||||
|
name: '{{ krb5_packages }}'
|
||||||
|
state: present
|
||||||
|
when: ansible_facts.distribution not in ['Alpine', 'MacOSX']
|
||||||
|
|
||||||
|
# apk isn't available on ansible-base so just call command
|
||||||
|
- name: Alpine - Install Kerberos system packages
|
||||||
|
command: apk add {{ krb5_packages | join(' ') }}
|
||||||
|
when: ansible_facts.distribution == 'Alpine'
|
||||||
|
|
||||||
|
- name: Install python gssapi
|
||||||
|
pip:
|
||||||
|
name:
|
||||||
|
- gssapi
|
||||||
|
- importlib ; python_version < '2.7'
|
||||||
|
state: present
|
||||||
|
extra_args: '-c {{ remote_constraints }}'
|
||||||
|
environment:
|
||||||
|
# Need this custom path for OpenSUSE as krb5-config is placed there
|
||||||
|
PATH: '{{ ansible_facts.env.PATH }}:/usr/lib/mit/bin'
|
||||||
|
notify: Remove python gssapi
|
||||||
|
|
||||||
|
- name: test the environment to make sure Kerberos is working properly
|
||||||
|
httptester_kinit:
|
||||||
|
username: '{{ krb5_username }}'
|
||||||
|
password: '{{ krb5_password }}'
|
||||||
|
environment:
|
||||||
|
KRB5_CONFIG: '{{ krb5_config }}'
|
||||||
|
KRB5CCNAME: FILE:{{ remote_tmp_dir }}/krb5.cc
|
||||||
|
|
||||||
|
- name: remove test credential cache
|
||||||
|
file:
|
||||||
|
path: '{{ remote_tmp_dir }}/krb5.cc'
|
||||||
|
state: absent
|
|
@ -22,3 +22,13 @@
|
||||||
- has_httptester|bool
|
- has_httptester|bool
|
||||||
# skip the setup if running on Windows Server 2008 as httptester is not available
|
# skip the setup if running on Windows Server 2008 as httptester is not available
|
||||||
- ansible_os_family != 'Windows' or (ansible_os_family == 'Windows' and not ansible_distribution_version.startswith("6.0."))
|
- ansible_os_family != 'Windows' or (ansible_os_family == 'Windows' and not ansible_distribution_version.startswith("6.0."))
|
||||||
|
|
||||||
|
- set_fact:
|
||||||
|
krb5_password: "{{ lookup('env', 'KRB5_PASSWORD') }}"
|
||||||
|
|
||||||
|
- name: setup Kerberos client
|
||||||
|
include_tasks: kerberos.yml
|
||||||
|
when:
|
||||||
|
- has_httptester|bool
|
||||||
|
- ansible_os_family != 'Windows'
|
||||||
|
- krb5_password != ''
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
[libdefaults]
|
||||||
|
default_realm = {{ krb5_realm | upper }}
|
||||||
|
dns_lookup_realm = false
|
||||||
|
dns_lookup_kdc = false
|
||||||
|
rdns = false
|
||||||
|
|
||||||
|
[realms]
|
||||||
|
{{ krb5_realm | upper }} = {
|
||||||
|
{% if krb5_provider == 'Heimdal' %}
|
||||||
|
{# Heimdal seems to only use UDP unless TCP is explicitly set and we must use TCP as the SSH tunnel only supports TCP. #}
|
||||||
|
{# The hostname doesn't seem to work when using the alias, just use localhost as that works. #}
|
||||||
|
kdc = tcp/127.0.0.1
|
||||||
|
admin_server = tcp/127.0.0.1
|
||||||
|
{% else %}
|
||||||
|
kdc = {{ httpbin_host }}
|
||||||
|
admin_server = {{ httpbin_host }}
|
||||||
|
{% endif %}
|
||||||
|
}
|
||||||
|
|
||||||
|
[domain_realm]
|
||||||
|
.{{ krb5_realm | lower }} = {{ krb5_realm | upper }}
|
||||||
|
{{ krb5_realm | lower }} = {{ krb5_realm | upper }}
|
||||||
|
|
||||||
|
[logging]
|
||||||
|
krb5 = FILE:/dev/stdout
|
|
@ -0,0 +1,3 @@
|
||||||
|
krb5_packages:
|
||||||
|
- krb5
|
||||||
|
- krb5-dev
|
|
@ -0,0 +1,3 @@
|
||||||
|
krb5_packages:
|
||||||
|
- krb5-user
|
||||||
|
- libkrb5-dev
|
|
@ -0,0 +1,2 @@
|
||||||
|
krb5_packages:
|
||||||
|
- heimdal
|
|
@ -0,0 +1,3 @@
|
||||||
|
krb5_packages:
|
||||||
|
- krb5-client
|
||||||
|
- krb5-devel
|
|
@ -0,0 +1,3 @@
|
||||||
|
krb5_packages:
|
||||||
|
- krb5-devel
|
||||||
|
- krb5-workstation
|
|
@ -598,3 +598,12 @@
|
||||||
|
|
||||||
- name: Check return-content
|
- name: Check return-content
|
||||||
import_tasks: return-content.yml
|
import_tasks: return-content.yml
|
||||||
|
|
||||||
|
- name: Test use_gssapi=True
|
||||||
|
include_tasks:
|
||||||
|
file: use_gssapi.yml
|
||||||
|
apply:
|
||||||
|
environment:
|
||||||
|
KRB5_CONFIG: '{{ krb5_config }}'
|
||||||
|
KRB5CCNAME: FILE:{{ remote_tmp_dir }}/krb5.cc
|
||||||
|
when: krb5_config is defined
|
||||||
|
|
76
test/integration/targets/uri/tasks/use_gssapi.yml
Normal file
76
test/integration/targets/uri/tasks/use_gssapi.yml
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
- name: test that endpoint offers Negotiate auth
|
||||||
|
uri:
|
||||||
|
url: http://{{ httpbin_host }}/gssapi
|
||||||
|
status_code: 401
|
||||||
|
register: no_auth_failure
|
||||||
|
failed_when: no_auth_failure.www_authenticate != 'Negotiate'
|
||||||
|
|
||||||
|
- name: Skip explicit auth tests on FreeBSD as Heimdal there does not have gss_acquire_cred_with_password
|
||||||
|
when: ansible_facts.os_family != 'FreeBSD'
|
||||||
|
block:
|
||||||
|
- name: test Negotiate auth over HTTP with explicit credentials
|
||||||
|
uri:
|
||||||
|
url: http://{{ httpbin_host }}/gssapi
|
||||||
|
use_gssapi: yes
|
||||||
|
url_username: '{{ krb5_username }}'
|
||||||
|
url_password: '{{ krb5_password }}'
|
||||||
|
return_content: yes
|
||||||
|
register: http_explicit
|
||||||
|
|
||||||
|
- name: test Negotiate auth over HTTPS with explicit credentials
|
||||||
|
uri:
|
||||||
|
url: https://{{ httpbin_host }}/gssapi
|
||||||
|
use_gssapi: yes
|
||||||
|
url_username: '{{ krb5_username }}'
|
||||||
|
url_password: '{{ krb5_password }}'
|
||||||
|
return_content: yes
|
||||||
|
register: https_explicit
|
||||||
|
|
||||||
|
- name: assert test Negotiate auth with implicit credentials
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- http_explicit.status == 200
|
||||||
|
- http_explicit.content | trim == 'Microsoft Rulz'
|
||||||
|
- https_explicit.status == 200
|
||||||
|
- https_explicit.content | trim == 'Microsoft Rulz'
|
||||||
|
|
||||||
|
- name: FreeBSD - verify it fails with explicit credential
|
||||||
|
uri:
|
||||||
|
url: https://{{ httpbin_host }}/gssapi
|
||||||
|
use_gssapi: yes
|
||||||
|
url_username: '{{ krb5_username }}'
|
||||||
|
url_password: '{{ krb5_password }}'
|
||||||
|
register: explicit_failure
|
||||||
|
when: ansible_facts.os_family == 'FreeBSD'
|
||||||
|
failed_when:
|
||||||
|
- '"Platform GSSAPI library does not support gss_acquire_cred_with_password, cannot acquire GSSAPI credential with explicit username and password" not in explicit_failure.msg'
|
||||||
|
|
||||||
|
- name: skip tests on macOS, I cannot seem to get it to read a credential from a custom ccache
|
||||||
|
when: ansible_facts.distribution != 'MacOSX'
|
||||||
|
block:
|
||||||
|
- name: get Kerberos ticket for implicit auth tests
|
||||||
|
httptester_kinit:
|
||||||
|
username: '{{ krb5_username }}'
|
||||||
|
password: '{{ krb5_password }}'
|
||||||
|
|
||||||
|
- name: test Negotiate auth over HTTP with implicit credentials
|
||||||
|
uri:
|
||||||
|
url: http://{{ httpbin_host }}/gssapi
|
||||||
|
use_gssapi: yes
|
||||||
|
return_content: yes
|
||||||
|
register: http_implicit
|
||||||
|
|
||||||
|
- name: test Negotiate auth over HTTPS with implicit credentials
|
||||||
|
uri:
|
||||||
|
url: https://{{ httpbin_host }}/gssapi
|
||||||
|
use_gssapi: yes
|
||||||
|
return_content: yes
|
||||||
|
register: https_implicit
|
||||||
|
|
||||||
|
- name: assert test Negotiate auth with implicit credentials
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- http_implicit.status == 200
|
||||||
|
- http_implicit.content | trim == 'Microsoft Rulz'
|
||||||
|
- https_implicit.status == 200
|
||||||
|
- https_implicit.content | trim == 'Microsoft Rulz'
|
|
@ -43,6 +43,7 @@ botocore >= 1.10.0, < 1.14 ; python_version < '2.7' # adds support for the follo
|
||||||
botocore >= 1.10.0 ; python_version >= '2.7' # adds support for the following AWS services: secretsmanager, fms, and acm-pca
|
botocore >= 1.10.0 ; python_version >= '2.7' # adds support for the following AWS services: secretsmanager, fms, and acm-pca
|
||||||
setuptools < 37 ; python_version == '2.6' # setuptools 37 and later require python 2.7 or later
|
setuptools < 37 ; python_version == '2.6' # setuptools 37 and later require python 2.7 or later
|
||||||
setuptools < 45 ; python_version == '2.7' # setuptools 45 and later require python 3.5 or later
|
setuptools < 45 ; python_version == '2.7' # setuptools 45 and later require python 3.5 or later
|
||||||
|
gssapi < 1.6.0 ; python_version <= '2.7' # gssapi 1.6.0 and later require python 3 or later
|
||||||
|
|
||||||
# freeze antsibull-changelog for consistent test results
|
# freeze antsibull-changelog for consistent test results
|
||||||
antsibull-changelog == 0.7.0
|
antsibull-changelog == 0.7.0
|
||||||
|
|
|
@ -1003,7 +1003,7 @@ def add_httptester_options(parser, argparse):
|
||||||
|
|
||||||
group.add_argument('--httptester',
|
group.add_argument('--httptester',
|
||||||
metavar='IMAGE',
|
metavar='IMAGE',
|
||||||
default='quay.io/ansible/http-test-container:1.1.0',
|
default='quay.io/ansible/http-test-container:1.3.0',
|
||||||
help='docker image to use for the httptester container')
|
help='docker image to use for the httptester container')
|
||||||
|
|
||||||
group.add_argument('--disable-httptester',
|
group.add_argument('--disable-httptester',
|
||||||
|
@ -1016,6 +1016,9 @@ def add_httptester_options(parser, argparse):
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help=argparse.SUPPRESS) # internal use only
|
help=argparse.SUPPRESS) # internal use only
|
||||||
|
|
||||||
|
parser.add_argument('--httptester-krb5-password',
|
||||||
|
help=argparse.SUPPRESS) # internal use only
|
||||||
|
|
||||||
|
|
||||||
def add_extra_docker_options(parser, integration=True):
|
def add_extra_docker_options(parser, integration=True):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -9,6 +9,7 @@ from . import types as t
|
||||||
|
|
||||||
from .util import (
|
from .util import (
|
||||||
find_python,
|
find_python,
|
||||||
|
generate_password,
|
||||||
generate_pip_command,
|
generate_pip_command,
|
||||||
ApplicationError,
|
ApplicationError,
|
||||||
)
|
)
|
||||||
|
@ -123,6 +124,8 @@ class EnvironmentConfig(CommonConfig):
|
||||||
|
|
||||||
self.inject_httptester = args.inject_httptester if 'inject_httptester' in args else False # type: bool
|
self.inject_httptester = args.inject_httptester if 'inject_httptester' in args else False # type: bool
|
||||||
self.httptester = docker_qualify_image(args.httptester if 'httptester' in args else '') # type: str
|
self.httptester = docker_qualify_image(args.httptester if 'httptester' in args else '') # type: str
|
||||||
|
krb5_password = args.httptester_krb5_password if 'httptester_krb5_password' in args else ''
|
||||||
|
self.httptester_krb5_password = krb5_password or generate_password() # type: str
|
||||||
|
|
||||||
if self.get_delegated_completion().get('httptester', 'enabled') == 'disabled':
|
if self.get_delegated_completion().get('httptester', 'enabled') == 'disabled':
|
||||||
self.httptester = False
|
self.httptester = False
|
||||||
|
|
|
@ -309,7 +309,7 @@ def delegate_docker(args, exclude, require, integration_targets):
|
||||||
test_options += ['--volume', '%s:%s' % (docker_socket, docker_socket)]
|
test_options += ['--volume', '%s:%s' % (docker_socket, docker_socket)]
|
||||||
|
|
||||||
if httptester_id:
|
if httptester_id:
|
||||||
test_options += ['--env', 'HTTPTESTER=1']
|
test_options += ['--env', 'HTTPTESTER=1', '--env', 'KRB5_PASSWORD=%s' % args.httptester_krb5_password]
|
||||||
|
|
||||||
for host in HTTPTESTER_HOSTS:
|
for host in HTTPTESTER_HOSTS:
|
||||||
test_options += ['--link', '%s:%s' % (httptester_id, host)]
|
test_options += ['--link', '%s:%s' % (httptester_id, host)]
|
||||||
|
@ -462,7 +462,7 @@ def delegate_remote(args, exclude, require, integration_targets):
|
||||||
cmd = generate_command(args, python_interpreter, os.path.join(ansible_root, 'bin'), content_root, options, exclude, require)
|
cmd = generate_command(args, python_interpreter, os.path.join(ansible_root, 'bin'), content_root, options, exclude, require)
|
||||||
|
|
||||||
if httptester_id:
|
if httptester_id:
|
||||||
cmd += ['--inject-httptester']
|
cmd += ['--inject-httptester', '--httptester-krb5-password', args.httptester_krb5_password]
|
||||||
|
|
||||||
if isinstance(args, TestConfig):
|
if isinstance(args, TestConfig):
|
||||||
if args.coverage and not args.coverage_label:
|
if args.coverage and not args.coverage_label:
|
||||||
|
|
|
@ -1247,10 +1247,18 @@ def start_httptester(args):
|
||||||
remote=8080,
|
remote=8080,
|
||||||
container=80,
|
container=80,
|
||||||
),
|
),
|
||||||
|
dict(
|
||||||
|
remote=8088,
|
||||||
|
container=88,
|
||||||
|
),
|
||||||
dict(
|
dict(
|
||||||
remote=8443,
|
remote=8443,
|
||||||
container=443,
|
container=443,
|
||||||
),
|
),
|
||||||
|
dict(
|
||||||
|
remote=8749,
|
||||||
|
container=749,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
container_id = get_docker_container_id()
|
container_id = get_docker_container_id()
|
||||||
|
@ -1287,6 +1295,7 @@ def run_httptester(args, ports=None):
|
||||||
"""
|
"""
|
||||||
options = [
|
options = [
|
||||||
'--detach',
|
'--detach',
|
||||||
|
'--env', 'KRB5_PASSWORD=%s' % args.httptester_krb5_password,
|
||||||
]
|
]
|
||||||
|
|
||||||
if ports:
|
if ports:
|
||||||
|
@ -1331,7 +1340,9 @@ def inject_httptester(args):
|
||||||
|
|
||||||
rules = '''
|
rules = '''
|
||||||
rdr pass inet proto tcp from any to any port 80 -> 127.0.0.1 port 8080
|
rdr pass inet proto tcp from any to any port 80 -> 127.0.0.1 port 8080
|
||||||
|
rdr pass inet proto tcp from any to any port 88 -> 127.0.0.1 port 8088
|
||||||
rdr pass inet proto tcp from any to any port 443 -> 127.0.0.1 port 8443
|
rdr pass inet proto tcp from any to any port 443 -> 127.0.0.1 port 8443
|
||||||
|
rdr pass inet proto tcp from any to any port 749 -> 127.0.0.1 port 8749
|
||||||
'''
|
'''
|
||||||
cmd = ['pfctl', '-ef', '-']
|
cmd = ['pfctl', '-ef', '-']
|
||||||
|
|
||||||
|
@ -1343,7 +1354,9 @@ rdr pass inet proto tcp from any to any port 443 -> 127.0.0.1 port 8443
|
||||||
elif iptables:
|
elif iptables:
|
||||||
ports = [
|
ports = [
|
||||||
(80, 8080),
|
(80, 8080),
|
||||||
|
(88, 8088),
|
||||||
(443, 8443),
|
(443, 8443),
|
||||||
|
(749, 8749),
|
||||||
]
|
]
|
||||||
|
|
||||||
for src, dst in ports:
|
for src, dst in ports:
|
||||||
|
@ -1406,6 +1419,7 @@ def integration_environment(args, target, test_dir, inventory_path, ansible_conf
|
||||||
if args.inject_httptester:
|
if args.inject_httptester:
|
||||||
env.update(dict(
|
env.update(dict(
|
||||||
HTTPTESTER='1',
|
HTTPTESTER='1',
|
||||||
|
KRB5_PASSWORD=args.httptester_krb5_password,
|
||||||
))
|
))
|
||||||
|
|
||||||
callback_plugins = ['junit'] + (env_config.callback_plugins or [] if env_config else [])
|
callback_plugins = ['junit'] + (env_config.callback_plugins or [] if env_config else [])
|
||||||
|
|
|
@ -364,6 +364,7 @@ def common_environment():
|
||||||
|
|
||||||
optional = (
|
optional = (
|
||||||
'HTTPTESTER',
|
'HTTPTESTER',
|
||||||
|
'KRB5_PASSWORD',
|
||||||
'LD_LIBRARY_PATH',
|
'LD_LIBRARY_PATH',
|
||||||
'SSH_AUTH_SOCK',
|
'SSH_AUTH_SOCK',
|
||||||
# MacOS High Sierra Compatibility
|
# MacOS High Sierra Compatibility
|
||||||
|
|
12
test/units/module_utils/urls/fixtures/cbt/ecdsa_sha256.pem
Normal file
12
test/units/module_utils/urls/fixtures/cbt/ecdsa_sha256.pem
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBjzCCATWgAwIBAgIQeNQTxkMgq4BF9tKogIGXUTAKBggqhkjOPQQ
|
||||||
|
DAjAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUzMDA4MDMxN1
|
||||||
|
oXDTE4MDUzMDA4MjMxN1owFTETMBEGA1UEAwwKU0VSVkVSMjAxNjBZM
|
||||||
|
BMGByqGSM49AgEGCCqGSM49AwEHA0IABDAfXTLOaC3ElgErlgk2tBlM
|
||||||
|
wf9XmGlGBw4vBtMJap1hAqbsdxFm6rhK3QU8PFFpv8Z/AtRG7ba3UwQ
|
||||||
|
prkssClejZzBlMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBg
|
||||||
|
EFBQcDAgYIKwYBBQUHAwEwFQYDVR0RBA4wDIIKU0VSVkVSMjAxNjAdB
|
||||||
|
gNVHQ4EFgQUnFDE8824TYAiBeX4fghEEg33UgYwCgYIKoZIzj0EAwID
|
||||||
|
SAAwRQIhAK3rXA4/0i6nm/U7bi6y618Ci2Is8++M3tYIXnEsA7zSAiA
|
||||||
|
w2s6bJoI+D7Xaey0Hp0gkks9z55y976keIEI+n3qkzw==
|
||||||
|
-----END CERTIFICATE-----
|
12
test/units/module_utils/urls/fixtures/cbt/ecdsa_sha512.pem
Normal file
12
test/units/module_utils/urls/fixtures/cbt/ecdsa_sha512.pem
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBjjCCATWgAwIBAgIQHVj2AGEwd6pOOSbcf0skQDAKBggqhkjOPQQ
|
||||||
|
DBDAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUzMDA3NTUzOV
|
||||||
|
oXDTE4MDUzMDA4MTUzOVowFTETMBEGA1UEAwwKU0VSVkVSMjAxNjBZM
|
||||||
|
BMGByqGSM49AgEGCCqGSM49AwEHA0IABL8d9S++MFpfzeH8B3vG/PjA
|
||||||
|
AWg8tGJVgsMw9nR+OfC9ltbTUwhB+yPk3JPcfW/bqsyeUgq4//LhaSp
|
||||||
|
lOWFNaNqjZzBlMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBg
|
||||||
|
EFBQcDAgYIKwYBBQUHAwEwFQYDVR0RBA4wDIIKU0VSVkVSMjAxNjAdB
|
||||||
|
gNVHQ4EFgQUKUkCgLlxoeai0EtQrZth1/BSc5kwCgYIKoZIzj0EAwQD
|
||||||
|
RwAwRAIgRrV7CLpDG7KueyFA3ZDced9dPOcv2Eydx/hgrfxYEcYCIBQ
|
||||||
|
D35JvzmqU05kSFV5eTvkhkaDObd7V55vokhm31+Li
|
||||||
|
-----END CERTIFICATE-----
|
21
test/units/module_utils/urls/fixtures/cbt/rsa-pss_sha256.pem
Normal file
21
test/units/module_utils/urls/fixtures/cbt/rsa-pss_sha256.pem
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDZDCCAhugAwIBAgIUbo9YpgkeniC5jRmOgFYX3mcVJn4wPgYJKoZIhvcNAQEK
|
||||||
|
MDGgDTALBglghkgBZQMEAgGhGjAYBgkqhkiG9w0BAQgwCwYJYIZIAWUDBAIBogQC
|
||||||
|
AgDeMBMxETAPBgNVBAMMCE9NSSBSb290MB4XDTIwMDkwNDE4NTMyNloXDTIxMDkw
|
||||||
|
NDE4NTMyNlowGDEWMBQGA1UEAwwNREMwMS5vbWkudGVzdDCCASIwDQYJKoZIhvcN
|
||||||
|
AQEBBQADggEPADCCAQoCggEBANN++3POgcKcPILMdHWIEPiVENtKoJQ8iqKKeL+/
|
||||||
|
j5oUULVuIn15H/RYMNFmStRIvj0dIL1JAq4W411wG2Tf/6niU2YSKPOAOtrVREef
|
||||||
|
gNvMZ06TYlC8UcGCLv4dBkU3q/FELV66lX9x6LcVwf2f8VWfDg4VNuwyg/eQUIgc
|
||||||
|
/yd5VV+1VXTf39QufVV+/hOtPptu+fBKOIuiuKm6FIqroqLri0Ysp6tWrSd7P6V4
|
||||||
|
6zT2yd17981vaEv5Zek2t39PoLYzJb3rvqQmumgFBIUQ1eMPLFCXX8YYYC/9ByK3
|
||||||
|
mdQaEnkD2eIOARLnojr2A228EgPpdM8phQkDzeWeYnhLiysCAwEAAaNJMEcwCQYD
|
||||||
|
VR0TBAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwGAYDVR0R
|
||||||
|
BBEwD4INREMwMS5vbWkudGVzdDA+BgkqhkiG9w0BAQowMaANMAsGCWCGSAFlAwQC
|
||||||
|
AaEaMBgGCSqGSIb3DQEBCDALBglghkgBZQMEAgGiBAICAN4DggEBAA66cbtiysjq
|
||||||
|
sCaDiYyRWCS9af2DGxJ6NAyku2aX+xgmRQzUzFAN5TihcPau+zzpk2zQKHDVMhNx
|
||||||
|
ouhTkIe6mq9KegpUuesWwkJ5KEeuBT949tIru2wAtlSVDvDcau5T9pRI/RLlnsWg
|
||||||
|
0sWaUAh/ujL+VKk6AanC4MRV69llwJcAVxlS/tYjwC74Dr8tMT1TQcVDvywB85e9
|
||||||
|
mA3uz8mGKfiMk2TKD6+6on0UwBMB8NbKSB5bcgp+CJ2ceeblmCOgOcOcV5PCGoFj
|
||||||
|
fgAppr7HjfNPYaIV5l59LfKo2Bj9kXPMqA6/D4gJ3hwoJdY/NOtuNyk8cxWMnWUe
|
||||||
|
+E2Mm6ZnB3Y=
|
||||||
|
-----END CERTIFICATE-----
|
21
test/units/module_utils/urls/fixtures/cbt/rsa-pss_sha512.pem
Normal file
21
test/units/module_utils/urls/fixtures/cbt/rsa-pss_sha512.pem
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDZDCCAhugAwIBAgIUbo9YpgkeniC5jRmOgFYX3mcVJoEwPgYJKoZIhvcNAQEK
|
||||||
|
MDGgDTALBglghkgBZQMEAgOhGjAYBgkqhkiG9w0BAQgwCwYJYIZIAWUDBAIDogQC
|
||||||
|
AgC+MBMxETAPBgNVBAMMCE9NSSBSb290MB4XDTIwMDkwNDE4NTMyN1oXDTIxMDkw
|
||||||
|
NDE4NTMyN1owGDEWMBQGA1UEAwwNREMwMS5vbWkudGVzdDCCASIwDQYJKoZIhvcN
|
||||||
|
AQEBBQADggEPADCCAQoCggEBANZMAyRDBn54RfveeVtikepqsyKVKooAc471snl5
|
||||||
|
mEEeo6ZvlOrK1VGGmo/VlF4R9iW6f5iqxG792KXk+lDtx8sbapZWk/aQa+6I9wml
|
||||||
|
p17ocW4Otl7XyQ74UTQlxmrped0rgOk+I2Wu3IC7k64gmf/ZbL9mYN/+v8TlYYyO
|
||||||
|
l8DQbO61XWOJpWt7yf18OxZtPcHH0dkoTEyIxIQcv6FDFNvPjmJzubpDgsfnly7R
|
||||||
|
C0Rc2yPU702vmAfF0SGQbd6KoXUqlfy26C85vU0Fqom1Qo22ehKrfU50vZrXdaJ2
|
||||||
|
gX14pm2kuubMjHtX/+bhNyWTxq4anCOl9/aoObZCM1D3+Y8CAwEAAaNJMEcwCQYD
|
||||||
|
VR0TBAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwGAYDVR0R
|
||||||
|
BBEwD4INREMwMS5vbWkudGVzdDA+BgkqhkiG9w0BAQowMaANMAsGCWCGSAFlAwQC
|
||||||
|
A6EaMBgGCSqGSIb3DQEBCDALBglghkgBZQMEAgOiBAICAL4DggEBAHgTDTn8onIi
|
||||||
|
XFLZ3sWJ5xCBvXypqC37dKALvXxNSo75SQZpOioG4GSdW3zjJWCiudGs7BselkFv
|
||||||
|
sHK7+5sLKTl1RvxeUoyTxnPZZmVlD3yLq8JBPxu5NScvcRwAcgm3stkq0irRnh7M
|
||||||
|
4Clw6oSKCKI7Lc3gnbvR3QLSYHeZpUcQgVCad6O/Hi+vxFMJT8PVigG0YUoTW010
|
||||||
|
pDpi5uh18RxCqRJnnEC7aDrVarxD9aAvqp1wqwWShfP4FZ9m57DH81RTGD2ZzGgP
|
||||||
|
MsZU5JHVYKkO7IKKIBKuLu+O+X2aZZ4OMlMNBt2DUIJGzEBYV41+3TST9bBPD8xt
|
||||||
|
AAIFCBcgUYY=
|
||||||
|
-----END CERTIFICATE-----
|
22
test/units/module_utils/urls/fixtures/cbt/rsa_md5.pem
Normal file
22
test/units/module_utils/urls/fixtures/cbt/rsa_md5.pem
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDGzCCAgOgAwIBAgIQJzshhViMG5hLHIJHxa+TcTANBgkqhkiG9w0
|
||||||
|
BAQQFADAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUzMDA4MD
|
||||||
|
MxNloXDTE4MDUzMDA4MjMxNlowFTETMBEGA1UEAwwKU0VSVkVSMjAxN
|
||||||
|
jCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN9N5GAzI7uq
|
||||||
|
AVlI6vUqhY5+EZWCWWGRwR3FT2DEXE5++AiJxXO0i0ZfAkLu7UggtBe
|
||||||
|
QwVNkaPD27EYzVUhy1iDo37BrFcLNpfjsjj8wVjaSmQmqvLvrvEh/BT
|
||||||
|
C5SBgDrk2+hiMh9PrpJoB3QAMDinz5aW0rEXMKitPBBiADrczyYrliF
|
||||||
|
AlEU6pTlKEKDUAeP7dKOBlDbCYvBxKnR3ddVH74I5T2SmNBq5gzkbKP
|
||||||
|
nlCXdHLZSh74USu93rKDZQF8YzdTO5dcBreJDJsntyj1o49w9WCt6M7
|
||||||
|
+pg6vKvE+tRbpCm7kXq5B9PDi42Nb6//MzNaMYf9V7v5MHapvVSv3+y
|
||||||
|
sCAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGA
|
||||||
|
QUFBwMCBggrBgEFBQcDATAVBgNVHREEDjAMggpTRVJWRVIyMDE2MB0G
|
||||||
|
A1UdDgQWBBTh4L2Clr9ber6yfY3JFS3wiECL4DANBgkqhkiG9w0BAQQ
|
||||||
|
FAAOCAQEA0JK/SL7SP9/nvqWp52vnsxVefTFehThle5DLzagmms/9gu
|
||||||
|
oSE2I9XkQIttFMprPosaIZWt7WP42uGcZmoZOzU8kFFYJMfg9Ovyca+
|
||||||
|
gnG28jDUMF1E74KrC7uynJiQJ4vPy8ne7F3XJ592LsNJmK577l42gAW
|
||||||
|
u08p3TvEJFNHy2dBk/IwZp0HIPr9+JcPf7v0uL6lK930xHJHP56XLzN
|
||||||
|
YG8vCMpJFR7wVZp3rXkJQUy3GxyHPJPjS8S43I9j+PoyioWIMEotq2+
|
||||||
|
q0IpXU/KeNFkdGV6VPCmzhykijExOMwO6doUzIUM8orv9jYLHXYC+i6
|
||||||
|
IFKSb6runxF1MAik+GCSA==
|
||||||
|
-----END CERTIFICATE-----
|
22
test/units/module_utils/urls/fixtures/cbt/rsa_sha.pem
Normal file
22
test/units/module_utils/urls/fixtures/cbt/rsa_sha.pem
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDGzCCAgOgAwIBAgIQUDHcKGevZohJV+TkIIYC1DANBgkqhkiG9w0
|
||||||
|
BAQ0FADAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUzMDA4MD
|
||||||
|
MxN1oXDTE4MDUzMDA4MjMxN1owFTETMBEGA1UEAwwKU0VSVkVSMjAxN
|
||||||
|
jCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKr9bo/XXvHt
|
||||||
|
D6Qnhb1wyLg9lDQxxe/enH49LQihtVTZMwGf2010h81QrRUe/bkHTvw
|
||||||
|
K22s2lqj3fUpGxtEbYFWLAHxv6IFnIKd+Zi1zaCPGfas9ekqCSj3vZQ
|
||||||
|
j7lCJVGUGuuqnSDvsed6g2Pz/g6mJUa+TzjxN+8wU5oj5YVUK+aing1
|
||||||
|
zPSA2MDCfx3+YzjxVwNoGixOz6Yx9ijT4pUsAYQAf1o9R+6W1/IpGgu
|
||||||
|
oax714QILT9heqIowwlHzlUZc1UAYs0/JA4CbDZaw9hlJyzMqe/aE46
|
||||||
|
efqPDOpO3vCpOSRcSyzh02WijPvEEaPejQRWg8RX93othZ615MT7dqp
|
||||||
|
ECAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGA
|
||||||
|
QUFBwMCBggrBgEFBQcDATAVBgNVHREEDjAMggpTRVJWRVIyMDE2MB0G
|
||||||
|
A1UdDgQWBBTgod3R6vejt6kOASAApA19xIG6kTANBgkqhkiG9w0BAQ0
|
||||||
|
FAAOCAQEAVfz0okK2bh3OQE8cWNbJ5PjJRSAJEqVUvYaTlS0Nqkyuaj
|
||||||
|
gicP3hb/pF8FvaVaB6r7LqgBxyW5NNL1xwdNLt60M2zaULL6Fhm1vzM
|
||||||
|
sSMc2ynkyN4++ODwii674YcQAnkUh+ZGIx+CTdZBWJfVM9dZb7QjgBT
|
||||||
|
nVukeFwN2EOOBSpiQSBpcoeJEEAq9csDVRhEfcB8Wtz7TTItgOVsilY
|
||||||
|
dQY56ON5XszjCki6UA3GwdQbBEHjWF2WERqXWrojrSSNOYDvxM5mrEx
|
||||||
|
sG1npzUTsaIr9w8ty1beh/2aToCMREvpiPFOXnVV/ovHMU1lFQTNeQ0
|
||||||
|
OI7elR0nJ0peai30eMpQQ=='
|
||||||
|
-----END CERTIFICATE-----
|
22
test/units/module_utils/urls/fixtures/cbt/rsa_sha1.pem
Normal file
22
test/units/module_utils/urls/fixtures/cbt/rsa_sha1.pem
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDGzCCAgOgAwIBAgIQJg/Mf5sR55xApJRK+kabbTANBgkqhkiG9w0
|
||||||
|
BAQUFADAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUzMDA4MD
|
||||||
|
MxNloXDTE4MDUzMDA4MjMxNlowFTETMBEGA1UEAwwKU0VSVkVSMjAxN
|
||||||
|
jCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALPKwYikjbzL
|
||||||
|
Lo6JtS6cyytdMMjSrggDoTnRUKauC5/izoYJd+2YVR5YqnluBJZpoFp
|
||||||
|
hkCgFFohUOU7qUsI1SkuGnjI8RmWTrrDsSy62BrfX+AXkoPlXo6IpHz
|
||||||
|
HaEPxjHJdUACpn8QVWTPmdAhwTwQkeUutrm3EOVnKPX4bafNYeAyj7/
|
||||||
|
AGEplgibuXT4/ehbzGKOkRN3ds/pZuf0xc4Q2+gtXn20tQIUt7t6iwh
|
||||||
|
nEWjIgopFL/hX/r5q5MpF6stc1XgIwJjEzqMp76w/HUQVqaYneU4qSG
|
||||||
|
f90ANK/TQ3aDbUNtMC/ULtIfHqHIW4POuBYXaWBsqalJL2VL3YYkKTU
|
||||||
|
sCAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGA
|
||||||
|
QUFBwMCBggrBgEFBQcDATAVBgNVHREEDjAMggpTRVJWRVIyMDE2MB0G
|
||||||
|
A1UdDgQWBBS1jgojcjPu9vqeP1uSKuiIonGwAjANBgkqhkiG9w0BAQU
|
||||||
|
FAAOCAQEAKjHL6k5Dv/Zb7dvbYEZyx0wVhjHkCTpT3xstI3+TjfAFsu
|
||||||
|
3zMmyFqFqzmr4pWZ/rHc3ObD4pEa24kP9hfB8nmr8oHMLebGmvkzh5h
|
||||||
|
0GYc4dIH7Ky1yfQN51hi7/X5iN7jnnBoCJTTlgeBVYDOEBXhfXi3cLT
|
||||||
|
u3d7nz2heyNq07gFP8iN7MfqdPZndVDYY82imLgsgar9w5d+fvnYM+k
|
||||||
|
XWItNNCUH18M26Obp4Es/Qogo/E70uqkMHost2D+tww/7woXi36X3w/
|
||||||
|
D2yBDyrJMJKZLmDgfpNIeCimncTOzi2IhzqJiOY/4XPsVN/Xqv0/dzG
|
||||||
|
TDdI11kPLq4EiwxvPanCg==
|
||||||
|
-----END CERTIFICATE-----
|
22
test/units/module_utils/urls/fixtures/cbt/rsa_sha256.pem
Normal file
22
test/units/module_utils/urls/fixtures/cbt/rsa_sha256.pem
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDGzCCAgOgAwIBAgIQWkeAtqoFg6pNWF7xC4YXhTANBgkqhkiG9w0
|
||||||
|
BAQsFADAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUyNzA5MD
|
||||||
|
I0NFoXDTE4MDUyNzA5MjI0NFowFTETMBEGA1UEAwwKU0VSVkVSMjAxN
|
||||||
|
jCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALIPKM5uykFy
|
||||||
|
NmVoLyvPSXGk15ZDqjYi3AbUxVFwCkVImqhefLATit3PkTUYFtAT+TC
|
||||||
|
AwK2E4lOu1XHM+Tmp2KIOnq2oUR8qMEvfxYThEf1MHxkctFljFssZ9N
|
||||||
|
vASDD4lzw8r0Bhl+E5PhR22Eu1Wago5bvIldojkwG+WBxPQv3ZR546L
|
||||||
|
MUZNaBXC0RhuGj5w83lbVz75qM98wvv1ekfZYAP7lrVyHxqCTPDomEU
|
||||||
|
I45tQQZHCZl5nRx1fPCyyYfcfqvFlLWD4Q3PZAbnw6mi0MiWJbGYKME
|
||||||
|
1XGicjqyn/zM9XKA1t/JzChS2bxf6rsyA9I7ibdRHUxsm1JgKry2jfW
|
||||||
|
0CAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGA
|
||||||
|
QUFBwMCBggrBgEFBQcDATAVBgNVHREEDjAMggpTRVJWRVIyMDE2MB0G
|
||||||
|
A1UdDgQWBBQabLGWg1sn7AXPwYPyfE0ER921ZDANBgkqhkiG9w0BAQs
|
||||||
|
FAAOCAQEAnRohyl6ZmOsTWCtxOJx5A8yr//NweXKwWWmFQXRmCb4bMC
|
||||||
|
xhD4zqLDf5P6RotGV0I/SHvqz+pAtJuwmr+iyAF6WTzo3164LCfnQEu
|
||||||
|
psfrrfMkf3txgDwQkA0oPAw3HEwOnR+tzprw3Yg9x6UoZEhi4XqP9AX
|
||||||
|
R49jU92KrNXJcPlz5MbkzNo5t9nr2f8q39b5HBjaiBJxzdM1hxqsbfD
|
||||||
|
KirTYbkUgPlVOo/NDmopPPb8IX8ubj/XETZG2jixD0zahgcZ1vdr/iZ
|
||||||
|
+50WSXKN2TAKBO2fwoK+2/zIWrGRxJTARfQdF+fGKuj+AERIFNh88HW
|
||||||
|
xSDYjHQAaFMcfdUpa9GGQ==
|
||||||
|
-----END CERTIFICATE-----
|
22
test/units/module_utils/urls/fixtures/cbt/rsa_sha384.pem
Normal file
22
test/units/module_utils/urls/fixtures/cbt/rsa_sha384.pem
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDGzCCAgOgAwIBAgIQEmj1prSSQYRL2zYBEjsm5jANBgkqhkiG9w0
|
||||||
|
BAQwFADAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUzMDA4MD
|
||||||
|
MxN1oXDTE4MDUzMDA4MjMxN1owFTETMBEGA1UEAwwKU0VSVkVSMjAxN
|
||||||
|
jCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKsK5NvHi4xO
|
||||||
|
081fRLMmPqKsKaHvXgPRykLA0SmKxpGJHfTAZzxojHVeVwOm87IvQj2
|
||||||
|
JUh/yrRwSi5Oqrvqx29l2IC/qQt2xkAQsO51/EWkMQ5OSJsl1MN3NXW
|
||||||
|
eRTKVoUuJzBs8XLmeraxQcBPyyLhq+WpMl/Q4ZDn1FrUEZfxV0POXgU
|
||||||
|
dI3ApuQNRtJOb6iteBIoQyMlnof0RswBUnkiWCA/+/nzR0j33j47IfL
|
||||||
|
nkmU4RtqkBlO13f6+e1GZ4lEcQVI2yZq4Zgu5VVGAFU2lQZ3aEVMTu9
|
||||||
|
8HEqD6heyNp2on5G/K/DCrGWYCBiASjnX3wiSz0BYv8f3HhCgIyVKhJ
|
||||||
|
8CAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGA
|
||||||
|
QUFBwMCBggrBgEFBQcDATAVBgNVHREEDjAMggpTRVJWRVIyMDE2MB0G
|
||||||
|
A1UdDgQWBBQS/SI61S2UE8xwSgHxbkCTpZXo4TANBgkqhkiG9w0BAQw
|
||||||
|
FAAOCAQEAMVV/WMXd9w4jtDfSrIsKaWKGtHtiMPpAJibXmSakBRwLOn
|
||||||
|
5ZGXL2bWI/Ac2J2Y7bSzs1im2ifwmEqwzzqnpVKShIkZmtij0LS0SEr
|
||||||
|
6Fw5IrK8tD6SH+lMMXUTvp4/lLQlgRCwOWxry/YhQSnuprx8IfSPvil
|
||||||
|
kwZ0Ysim4Aa+X5ojlhHpWB53edX+lFrmR1YWValBnQ5DvnDyFyLR6II
|
||||||
|
Ialp4vmkzI9e3/eOgSArksizAhpXpC9dxQBiHXdhredN0X+1BVzbgzV
|
||||||
|
hQBEwgnAIPa+B68oDILaV0V8hvxrP6jFM4IrKoGS1cq0B+Ns0zkG7ZA
|
||||||
|
2Q0W+3nVwSxIr6bd6hw7g==
|
||||||
|
-----END CERTIFICATE-----
|
22
test/units/module_utils/urls/fixtures/cbt/rsa_sha512.pem
Normal file
22
test/units/module_utils/urls/fixtures/cbt/rsa_sha512.pem
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDGzCCAgOgAwIBAgIQUDHcKGevZohJV+TkIIYC1DANBgkqhkiG9w0
|
||||||
|
BAQ0FADAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUzMDA4MD
|
||||||
|
MxN1oXDTE4MDUzMDA4MjMxN1owFTETMBEGA1UEAwwKU0VSVkVSMjAxN
|
||||||
|
jCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKr9bo/XXvHt
|
||||||
|
D6Qnhb1wyLg9lDQxxe/enH49LQihtVTZMwGf2010h81QrRUe/bkHTvw
|
||||||
|
K22s2lqj3fUpGxtEbYFWLAHxv6IFnIKd+Zi1zaCPGfas9ekqCSj3vZQ
|
||||||
|
j7lCJVGUGuuqnSDvsed6g2Pz/g6mJUa+TzjxN+8wU5oj5YVUK+aing1
|
||||||
|
zPSA2MDCfx3+YzjxVwNoGixOz6Yx9ijT4pUsAYQAf1o9R+6W1/IpGgu
|
||||||
|
oax714QILT9heqIowwlHzlUZc1UAYs0/JA4CbDZaw9hlJyzMqe/aE46
|
||||||
|
efqPDOpO3vCpOSRcSyzh02WijPvEEaPejQRWg8RX93othZ615MT7dqp
|
||||||
|
ECAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGA
|
||||||
|
QUFBwMCBggrBgEFBQcDATAVBgNVHREEDjAMggpTRVJWRVIyMDE2MB0G
|
||||||
|
A1UdDgQWBBTgod3R6vejt6kOASAApA19xIG6kTANBgkqhkiG9w0BAQ0
|
||||||
|
FAAOCAQEAVfz0okK2bh3OQE8cWNbJ5PjJRSAJEqVUvYaTlS0Nqkyuaj
|
||||||
|
gicP3hb/pF8FvaVaB6r7LqgBxyW5NNL1xwdNLt60M2zaULL6Fhm1vzM
|
||||||
|
sSMc2ynkyN4++ODwii674YcQAnkUh+ZGIx+CTdZBWJfVM9dZb7QjgBT
|
||||||
|
nVukeFwN2EOOBSpiQSBpcoeJEEAq9csDVRhEfcB8Wtz7TTItgOVsilY
|
||||||
|
dQY56ON5XszjCki6UA3GwdQbBEHjWF2WERqXWrojrSSNOYDvxM5mrEx
|
||||||
|
sG1npzUTsaIr9w8ty1beh/2aToCMREvpiPFOXnVV/ovHMU1lFQTNeQ0
|
||||||
|
OI7elR0nJ0peai30eMpQQ=='
|
||||||
|
-----END CERTIFICATE-----
|
74
test/units/module_utils/urls/test_channel_binding.py
Normal file
74
test/units/module_utils/urls/test_channel_binding.py
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# (c) 2020 Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import os.path
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import ansible.module_utils.urls as urls
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not urls.HAS_CRYPTOGRAPHY, reason='Requires cryptography to be installed')
|
||||||
|
@pytest.mark.parametrize('certificate, expected', [
|
||||||
|
('rsa_md5.pem', b'\x23\x34\xB8\x47\x6C\xBF\x4E\x6D'
|
||||||
|
b'\xFC\x76\x6A\x5D\x5A\x30\xD6\x64'
|
||||||
|
b'\x9C\x01\xBA\xE1\x66\x2A\x5C\x3A'
|
||||||
|
b'\x13\x02\xA9\x68\xD7\xC6\xB0\xF6'),
|
||||||
|
('rsa_sha1.pem', b'\x14\xCF\xE8\xE4\xB3\x32\xB2\x0A'
|
||||||
|
b'\x34\x3F\xC8\x40\xB1\x8F\x9F\x6F'
|
||||||
|
b'\x78\x92\x6A\xFE\x7E\xC3\xE7\xB8'
|
||||||
|
b'\xE2\x89\x69\x61\x9B\x1E\x8F\x3E'),
|
||||||
|
('rsa_sha256.pem', b'\x99\x6F\x3E\xEA\x81\x2C\x18\x70'
|
||||||
|
b'\xE3\x05\x49\xFF\x9B\x86\xCD\x87'
|
||||||
|
b'\xA8\x90\xB6\xD8\xDF\xDF\x4A\x81'
|
||||||
|
b'\xBE\xF9\x67\x59\x70\xDA\xDB\x26'),
|
||||||
|
('rsa_sha384.pem', b'\x34\xF3\x03\xC9\x95\x28\x6F\x4B'
|
||||||
|
b'\x21\x4A\x9B\xA6\x43\x5B\x69\xB5'
|
||||||
|
b'\x1E\xCF\x37\x58\xEA\xBC\x2A\x14'
|
||||||
|
b'\xD7\xA4\x3F\xD2\x37\xDC\x2B\x1A'
|
||||||
|
b'\x1A\xD9\x11\x1C\x5C\x96\x5E\x10'
|
||||||
|
b'\x75\x07\xCB\x41\x98\xC0\x9F\xEC'),
|
||||||
|
('rsa_sha512.pem', b'\x55\x6E\x1C\x17\x84\xE3\xB9\x57'
|
||||||
|
b'\x37\x0B\x7F\x54\x4F\x62\xC5\x33'
|
||||||
|
b'\xCB\x2C\xA5\xC1\xDA\xE0\x70\x6F'
|
||||||
|
b'\xAE\xF0\x05\x44\xE1\xAD\x2B\x76'
|
||||||
|
b'\xFF\x25\xCF\xBE\x69\xB1\xC4\xE6'
|
||||||
|
b'\x30\xC3\xBB\x02\x07\xDF\x11\x31'
|
||||||
|
b'\x4C\x67\x38\xBC\xAE\xD7\xE0\x71'
|
||||||
|
b'\xD7\xBF\xBF\x2C\x9D\xFA\xB8\x5D'),
|
||||||
|
('rsa-pss_sha256.pem', b'\xF2\x31\xE6\xFF\x3F\x9E\x16\x1B'
|
||||||
|
b'\xC2\xDC\xBB\x89\x8D\x84\x47\x4E'
|
||||||
|
b'\x58\x9C\xD7\xC2\x7A\xDB\xEF\x8B'
|
||||||
|
b'\xD9\xC0\xC0\x68\xAF\x9C\x36\x6D'),
|
||||||
|
('rsa-pss_sha512.pem', b'\x85\x85\x19\xB9\xE1\x0F\x23\xE2'
|
||||||
|
b'\x1D\x2C\xE9\xD5\x47\x2A\xAB\xCE'
|
||||||
|
b'\x42\x0F\xD1\x00\x75\x9C\x53\xA1'
|
||||||
|
b'\x7B\xB9\x79\x86\xB2\x59\x61\x27'),
|
||||||
|
('ecdsa_sha256.pem', b'\xFE\xCF\x1B\x25\x85\x44\x99\x90'
|
||||||
|
b'\xD9\xE3\xB2\xC9\x2D\x3F\x59\x7E'
|
||||||
|
b'\xC8\x35\x4E\x12\x4E\xDA\x75\x1D'
|
||||||
|
b'\x94\x83\x7C\x2C\x89\xA2\xC1\x55'),
|
||||||
|
('ecdsa_sha512.pem', b'\xE5\xCB\x68\xB2\xF8\x43\xD6\x3B'
|
||||||
|
b'\xF4\x0B\xCB\x20\x07\x60\x8F\x81'
|
||||||
|
b'\x97\x61\x83\x92\x78\x3F\x23\x30'
|
||||||
|
b'\xE5\xEF\x19\xA5\xBD\x8F\x0B\x2F'
|
||||||
|
b'\xAA\xC8\x61\x85\x5F\xBB\x63\xA2'
|
||||||
|
b'\x21\xCC\x46\xFC\x1E\x22\x6A\x07'
|
||||||
|
b'\x24\x11\xAF\x17\x5D\xDE\x47\x92'
|
||||||
|
b'\x81\xE0\x06\x87\x8B\x34\x80\x59'),
|
||||||
|
])
|
||||||
|
def test_cbt_with_cert(certificate, expected):
|
||||||
|
with open(os.path.join(os.path.dirname(__file__), 'fixtures', 'cbt', certificate)) as fd:
|
||||||
|
cert_der = base64.b64decode("".join([l.strip() for l in fd.readlines()[1:-1]]))
|
||||||
|
|
||||||
|
actual = urls.get_channel_binding_cert_hash(cert_der)
|
||||||
|
assert actual == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_cbt_no_cryptography(monkeypatch):
|
||||||
|
monkeypatch.setattr(urls, 'HAS_CRYPTOGRAPHY', False)
|
||||||
|
assert urls.get_channel_binding_cert_hash(None) is None
|
Loading…
Reference in a new issue