Skip to content

Commit

Permalink
implement Kerberos auth
Browse files Browse the repository at this point in the history
  • Loading branch information
evgeni committed Feb 17, 2025
1 parent 8a54a28 commit 6d07044
Show file tree
Hide file tree
Showing 8 changed files with 51 additions and 18 deletions.
10 changes: 8 additions & 2 deletions plugins/doc_fragments/foreman.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,26 @@ class ModuleDocFragment(object):
description:
- Username accessing the Foreman server.
- If the value is not specified in the task, the value of environment variable C(FOREMAN_USERNAME) will be used instead.
required: true
required: false
type: str
password:
description:
- Password of the user accessing the Foreman server.
- If the value is not specified in the task, the value of environment variable C(FOREMAN_PASSWORD) will be used instead.
required: true
required: false
type: str
validate_certs:
description:
- Whether or not to verify the TLS certificates of the Foreman server.
- If the value is not specified in the task, the value of environment variable C(FOREMAN_VALIDATE_CERTS) will be used instead.
default: true
type: bool
use_gssapi:
description:
- Use GSSAPI to perform the authentication, typically this is for Kerberos or Kerberos through Negotiate authentication.
- Requires the Python library L(requests-gssapi,https://github.com/pythongssapi/requests-gssapi) to be installed.
default: false
type: bool
attributes:
check_mode:
description: Can run in check_mode and return changed status prediction without modifying the entity
Expand Down
24 changes: 23 additions & 1 deletion plugins/module_utils/_apypie.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,14 @@ def _prepare_route_params(self, input_dict):
except ImportError:
pass

try:
from requests_gssapi import HTTPKerberosAuth # type: ignore
except ImportError:
try:
from requests_kerberos import HTTPKerberosAuth # type: ignore
except ImportError:
HTTPKerberosAuth = None

NO_CONTENT = 204


Expand All @@ -264,7 +272,10 @@ class Api(object):
:param uri: base URL of the server
:param username: username to access the API
:param password: username to access the API
:param password: password to access the API
:param client_cert: client cert to access the API
:param client_key: client key to access the API
:param kerberos: use Kerberos/GSSAPI for authentication with the API. Requires either `requests-gssapi` or `requests-kerberos`.
:param api_version: version of the API. Defaults to `1`
:param language: prefered locale for the API description
:param apidoc_cache_base_dir: base directory for building apidoc_cache_dir. Defaults to `~/.cache/apipie_bindings`.
Expand Down Expand Up @@ -307,6 +318,15 @@ def __init__(self, **kwargs):
if kwargs.get('username') and kwargs.get('password'):
self._session.auth = (kwargs['username'], kwargs['password'])

if kwargs.get('client_cert') and kwargs.get('client_key'):
self._session.cert = (kwargs['client_cert'], kwargs['client_key'])

if kwargs.get('kerberos'):
if HTTPKerberosAuth is not None:
self._session.auth = HTTPKerberosAuth()
else:
raise ValueError('Kerberos authentication requested, but neither requests-gssapi nor requests-kerberos found.')

self._apidoc = None

@property
Expand Down Expand Up @@ -664,6 +684,8 @@ def __init__(self, **kwargs):
self.task_poll = 4
kwargs['api_version'] = 2
super().__init__(**kwargs)
if kwargs.get('kerberos'):
self.call('users', 'extlogin')

def _resource(self, resource: str) -> 'Resource':
if resource not in self.resources:
Expand Down
15 changes: 10 additions & 5 deletions plugins/module_utils/foreman_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,27 +368,31 @@ def __init__(self, **kwargs):
self.foreman_spec, gen_args = _foreman_spec_helper(kwargs.pop('foreman_spec', {}))
argument_spec = dict(
server_url=dict(required=True, fallback=(env_fallback, ['FOREMAN_SERVER_URL', 'FOREMAN_SERVER', 'FOREMAN_URL'])),
username=dict(required=True, fallback=(env_fallback, ['FOREMAN_USERNAME', 'FOREMAN_USER'])),
password=dict(required=True, no_log=True, fallback=(env_fallback, ['FOREMAN_PASSWORD'])),
username=dict(required=False, fallback=(env_fallback, ['FOREMAN_USERNAME', 'FOREMAN_USER'])),
password=dict(required=False, no_log=True, fallback=(env_fallback, ['FOREMAN_PASSWORD'])),
validate_certs=dict(type='bool', default=True, fallback=(env_fallback, ['FOREMAN_VALIDATE_CERTS'])),
use_gssapi=dict(type='bool', default=False, fallback=(env_fallback, ['FOREMAN_USE_GSSAPI'])),
)
argument_spec.update(gen_args)
argument_spec.update(kwargs.pop('argument_spec', {}))
supports_check_mode = kwargs.pop('supports_check_mode', True)
required_if = kwargs.pop('required_if', [])
required_if.append(('use_gssapi', False, ('username', 'password')))

self.required_plugins = kwargs.pop('required_plugins', [])

super(ForemanAnsibleModule, self).__init__(argument_spec=argument_spec, supports_check_mode=supports_check_mode, **kwargs)
super(ForemanAnsibleModule, self).__init__(argument_spec=argument_spec, supports_check_mode=supports_check_mode, required_if=required_if, **kwargs)

aliases = {alias for arg in argument_spec.values() for alias in arg.get('aliases', [])}
self.foreman_params = _recursive_dict_without_none(self.params, aliases)

self.check_requirements()

self._foremanapi_server_url = self.foreman_params.pop('server_url')
self._foremanapi_username = self.foreman_params.pop('username')
self._foremanapi_password = self.foreman_params.pop('password')
self._foremanapi_username = self.foreman_params.pop('username', None)
self._foremanapi_password = self.foreman_params.pop('password', None)
self._foremanapi_validate_certs = self.foreman_params.pop('validate_certs')
self._foremanapi_use_gssapi = self.foreman_params.pop('use_gssapi')

if self._foremanapi_server_url.lower().startswith('http://'):
self.warn("You have configured a plain HTTP server URL. All communication will happen unencrypted.")
Expand Down Expand Up @@ -611,6 +615,7 @@ def connect(self):
username=to_bytes(self._foremanapi_username),
password=to_bytes(self._foremanapi_password),
verify_ssl=self._foremanapi_validate_certs,
kerberos=self._foremanapi_use_gssapi,
task_timeout=self.task_timeout,
)

Expand Down
4 changes: 2 additions & 2 deletions plugins/modules/bookmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,10 @@ def main():
argument_spec=dict(
state=dict(default='present', choices=['present_with_defaults', 'present', 'absent']),
),
required_if=(
required_if=[
['state', 'present', ['query']],
['state', 'present_with_defaults', ['query']],
),
],
)

with module.api_connection():
Expand Down
4 changes: 2 additions & 2 deletions plugins/modules/compute_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,9 +496,9 @@ def main():
),
state=dict(type='str', default='present', choices=['present', 'absent', 'present_with_defaults']),
),
required_if=(
required_if=[
['state', 'present_with_defaults', ['provider', 'provider_params']],
),
],
)

if not module.desired_absent:
Expand Down
4 changes: 2 additions & 2 deletions plugins/modules/global_parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,10 @@ def main():
state=dict(default='present', choices=['present_with_defaults', 'present', 'absent']),
updated_name=dict(),
),
required_if=(
required_if=[
['state', 'present_with_defaults', ['value']],
['state', 'present', ['value']],
),
],
)

with module.api_connection():
Expand Down
4 changes: 2 additions & 2 deletions plugins/modules/job_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,11 +357,11 @@ def main():
template_inputs=dict(
type='nested_list',
foreman_spec=template_input_foreman_spec,
required_if=(
required_if=[
['input_type', 'fact', ('fact_name',)],
['input_type', 'variable', ('variable_name',)],
['input_type', 'puppet_parameter', ('puppet_class_name', 'puppet_parameter_name')],
),
],
),
),
argument_spec=dict(
Expand Down
4 changes: 2 additions & 2 deletions plugins/modules/os_default_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,10 @@ def main():
template_kind=dict(required=True, choices=TEMPLATE_KIND_LIST, type='entity'),
provisioning_template=dict(type='entity', thin=False),
),
required_if=(
required_if=[
['state', 'present', ['provisioning_template']],
['state', 'present_with_defaults', ['provisioning_template']],
),
],
entity_opts={'scope': ['operatingsystem']},
)

Expand Down

0 comments on commit 6d07044

Please sign in to comment.