identity: Add GSSAPI suport for FreeIPA authentication (#52031)

* identity: Add GSSAPI suport for FreeIPA authentication

This enables the usage of GSSAPI for authentication, instead of having
to pass the username and password as part of the playbook run.

If there is GSSAPI support, this makes the password optional, and will
be able to use the KRB5_CLIENT_KTNAME or the KRB5CCNAME environment
variables; which are standard when using kerberos authentication.

Note that this depends on the urllib_gssapi library, and will only
enable this if that library is available.

* identity: Add documentation for GSSAPI authentication for FreeIPA

This documentation describes how to use GSSAPI authentication with the
IPA identity modules.

* identity: Add changelog for GSSAPI support for IPA

This adds the changelog entry for the GSSAPI authentication feature for
the IPA identity module.
This commit is contained in:
Juan Antonio Osorio 2019-02-13 17:38:13 +02:00 committed by John R Barker
parent 7d55dc1a38
commit 9f081ca04f
6 changed files with 69 additions and 29 deletions

View file

@ -28,13 +28,15 @@
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import json
import os
import socket
import uuid
import re
from ansible.module_utils._text import to_bytes, to_native, to_text
from ansible.module_utils.six import PY3
from ansible.module_utils.six.moves.urllib.parse import quote
from ansible.module_utils.urls import fetch_url
from ansible.module_utils.urls import fetch_url, HAS_GSSAPI
from ansible.module_utils.basic import env_fallback, AnsibleFallbackNotFound
@ -60,6 +62,7 @@ class IPAClient(object):
self.module = module
self.headers = None
self.timeout = module.params.get('ipa_timeout')
self.use_gssapi = False
def get_base_url(self):
return '%s://%s/ipa' % (self.protocol, self.host)
@ -68,23 +71,38 @@ class IPAClient(object):
return '%s/session/json' % self.get_base_url()
def login(self, username, password):
url = '%s/session/login_password' % self.get_base_url()
data = 'user=%s&password=%s' % (quote(username, safe=''), quote(password, safe=''))
headers = {'referer': self.get_base_url(),
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'text/plain'}
try:
resp, info = fetch_url(module=self.module, url=url, data=to_bytes(data), headers=headers, timeout=self.timeout)
status_code = info['status']
if status_code not in [200, 201, 204]:
self._fail('login', info['msg'])
if 'KRB5CCNAME' in os.environ and HAS_GSSAPI:
self.use_gssapi = True
elif 'KRB5_CLIENT_KTNAME' in os.environ and HAS_GSSAPI:
ccache = "MEMORY:" + str(uuid.uuid4())
os.environ['KRB5CCNAME'] = ccache
self.use_gssapi = True
else:
if not password:
self._fail('login', 'Password is required if not using '
'GSSAPI. To use GSSAPI, please set the '
'KRB5_CLIENT_KTNAME or KRB5CCNAME (or both) '
' environment variables.')
url = '%s/session/login_password' % self.get_base_url()
data = 'user=%s&password=%s' % (quote(username, safe=''), quote(password, safe=''))
headers = {'referer': self.get_base_url(),
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'text/plain'}
try:
resp, info = fetch_url(module=self.module, url=url, data=to_bytes(data), headers=headers, timeout=self.timeout)
status_code = info['status']
if status_code not in [200, 201, 204]:
self._fail('login', info['msg'])
self.headers = {'referer': self.get_base_url(),
'Content-Type': 'application/json',
'Accept': 'application/json',
'Cookie': resp.info().get('Set-Cookie')}
except Exception as e:
self._fail('login', to_native(e))
self.headers = {'Cookie': resp.info().get('Set-Cookie')}
except Exception as e:
self._fail('login', to_native(e))
if not self.headers:
self.headers = dict()
self.headers.update({
'referer': self.get_base_url(),
'Content-Type': 'application/json',
'Accept': 'application/json'})
def _fail(self, msg, e):
if 'message' in e:
@ -120,7 +138,8 @@ class IPAClient(object):
data['params'] = [[name], item]
try:
resp, info = fetch_url(module=self.module, url=url, data=to_bytes(json.dumps(data)), headers=self.headers, timeout=self.timeout)
resp, info = fetch_url(module=self.module, url=url, data=to_bytes(json.dumps(data)),
headers=self.headers, timeout=self.timeout, use_gssapi=self.use_gssapi)
status_code = info['status']
if status_code not in [200, 201, 204]:
self._fail(method, info['msg'])
@ -199,7 +218,7 @@ def ipa_argument_spec():
ipa_host=dict(type='str', default='ipa.example.com', fallback=(_env_then_dns_fallback, ['IPA_HOST'])),
ipa_port=dict(type='int', default=443, fallback=(env_fallback, ['IPA_PORT'])),
ipa_user=dict(type='str', default='admin', fallback=(env_fallback, ['IPA_USER'])),
ipa_pass=dict(type='str', required=True, no_log=True, fallback=(env_fallback, ['IPA_PASS'])),
ipa_pass=dict(type='str', required=not HAS_GSSAPI, no_log=True, fallback=(env_fallback, ['IPA_PASS'])),
ipa_timeout=dict(type='int', default=10, fallback=(env_fallback, ['IPA_TIMEOUT'])),
validate_certs=dict(type='bool', default=True),
)