mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
redhat_subscription: use D-Bus for registration if possible (#6122)
subscription-manager currently does not have a way to get credentials (username, password, activation keys, organization ID) in a secure way: the existing command line parameters can be easily spotted when running a process listing while 'subscription-manager register' runs. There is a D-Bus service, which is used by e.g. cockpit and Anaconda to interface with RHSM (at least for registration and common queries). Try to perform the registration using D-Bus, in a way very similar to the work done in convert2rhel [1] (with my help): - try to do a simple signal test to check whether the system bus works; inspired by the login in the dconf module - pass most of the options as registration options; for the few that are not part of the registration, execute 'subscription-manager' manually - add quirks for differently working (or not) registration options for the D-Bus Register*() methods depending on the version of RHEL - 'subscription-manager register' is used only in case the signal test is not working; silent fallback in case of D-Bus errors during the registration is not done on purpose to avoid silent fallback to a less secure registration [1] https://github.com/oamg/convert2rhel/pull/540/
This commit is contained in:
parent
a49ad340af
commit
e939cd07ef
3 changed files with 226 additions and 2 deletions
|
@ -0,0 +1,4 @@
|
||||||
|
minor_changes:
|
||||||
|
- redhat_subscription - the registration is done using the D-Bus ``rhsm`` service instead of spawning a ``subscription-manager register`` command, if possible;
|
||||||
|
this avoids passing plain-text credentials as arguments to ``subscription-manager register``, which can be seen while that command runs
|
||||||
|
(https://github.com/ansible-collections/community.general/pull/6122).
|
|
@ -13,9 +13,10 @@ __metaclass__ = type
|
||||||
DOCUMENTATION = '''
|
DOCUMENTATION = '''
|
||||||
---
|
---
|
||||||
module: redhat_subscription
|
module: redhat_subscription
|
||||||
short_description: Manage registration and subscriptions to RHSM using the C(subscription-manager) command
|
short_description: Manage registration and subscriptions to RHSM using C(subscription-manager)
|
||||||
description:
|
description:
|
||||||
- Manage registration and subscription to the Red Hat Subscription Management entitlement platform using the C(subscription-manager) command
|
- Manage registration and subscription to the Red Hat Subscription Management entitlement platform using the C(subscription-manager) command,
|
||||||
|
registering using D-Bus if possible.
|
||||||
author: "Barnaby Court (@barnabycourt)"
|
author: "Barnaby Court (@barnabycourt)"
|
||||||
notes:
|
notes:
|
||||||
- In order to register a system, subscription-manager requires either a username and password, or an activationkey and an Organization ID.
|
- In order to register a system, subscription-manager requires either a username and password, or an activationkey and an Organization ID.
|
||||||
|
@ -25,6 +26,8 @@ notes:
|
||||||
config file and default to None.
|
config file and default to None.
|
||||||
requirements:
|
requirements:
|
||||||
- subscription-manager
|
- subscription-manager
|
||||||
|
- Optionally the C(dbus) Python library; this is usually included in the OS
|
||||||
|
as it is used by C(subscription-manager).
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- community.general.attributes
|
- community.general.attributes
|
||||||
attributes:
|
attributes:
|
||||||
|
@ -297,6 +300,7 @@ import json
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils.common.text.converters import to_native
|
from ansible.module_utils.common.text.converters import to_native
|
||||||
from ansible.module_utils.six.moves import configparser
|
from ansible.module_utils.six.moves import configparser
|
||||||
|
from ansible.module_utils import distro
|
||||||
|
|
||||||
|
|
||||||
SUBMAN_CMD = None
|
SUBMAN_CMD = None
|
||||||
|
@ -410,6 +414,35 @@ class Rhsm(RegistrationBase):
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _can_connect_to_dbus(self):
|
||||||
|
"""
|
||||||
|
Checks whether it is possible to connect to the system D-Bus bus.
|
||||||
|
|
||||||
|
:returns: bool -- whether it is possible to connect to the system D-Bus bus.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Technically speaking, subscription-manager uses dbus-python
|
||||||
|
# as D-Bus library, so this ought to work; better be safe than
|
||||||
|
# sorry, I guess...
|
||||||
|
import dbus
|
||||||
|
except ImportError:
|
||||||
|
self.module.debug('dbus Python module not available, will use CLI')
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
bus = dbus.SystemBus()
|
||||||
|
msg = dbus.lowlevel.SignalMessage('/', 'com.example', 'test')
|
||||||
|
bus.send_message(msg)
|
||||||
|
bus.flush()
|
||||||
|
|
||||||
|
except dbus.exceptions.DBusException as e:
|
||||||
|
self.module.debug('Failed to connect to system D-Bus bus, will use CLI: %s' % e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.module.debug('Verified system D-Bus bus as usable')
|
||||||
|
return True
|
||||||
|
|
||||||
def register(self, username, password, token, auto_attach, activationkey, org_id,
|
def register(self, username, password, token, auto_attach, activationkey, org_id,
|
||||||
consumer_type, consumer_name, consumer_id, force_register, environment,
|
consumer_type, consumer_name, consumer_id, force_register, environment,
|
||||||
release):
|
release):
|
||||||
|
@ -417,6 +450,28 @@ class Rhsm(RegistrationBase):
|
||||||
Register the current system to the provided RHSM or Red Hat Satellite
|
Register the current system to the provided RHSM or Red Hat Satellite
|
||||||
or Katello server
|
or Katello server
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
* Exception - if any error occurs during the registration
|
||||||
|
'''
|
||||||
|
# There is no support for token-based registration in the D-Bus API
|
||||||
|
# of rhsm, so always use the CLI in that case.
|
||||||
|
if not token and self._can_connect_to_dbus():
|
||||||
|
self._register_using_dbus(username, password, auto_attach,
|
||||||
|
activationkey, org_id, consumer_type,
|
||||||
|
consumer_name, consumer_id,
|
||||||
|
force_register, environment, release)
|
||||||
|
return
|
||||||
|
self._register_using_cli(username, password, token, auto_attach,
|
||||||
|
activationkey, org_id, consumer_type,
|
||||||
|
consumer_name, consumer_id,
|
||||||
|
force_register, environment, release)
|
||||||
|
|
||||||
|
def _register_using_cli(self, username, password, token, auto_attach,
|
||||||
|
activationkey, org_id, consumer_type, consumer_name,
|
||||||
|
consumer_id, force_register, environment, release):
|
||||||
|
'''
|
||||||
|
Register using the 'subscription-manager' command
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
* Exception - if error occurs while running command
|
* Exception - if error occurs while running command
|
||||||
'''
|
'''
|
||||||
|
@ -459,6 +514,169 @@ class Rhsm(RegistrationBase):
|
||||||
|
|
||||||
rc, stderr, stdout = self.module.run_command(args, check_rc=True, expand_user_and_vars=False)
|
rc, stderr, stdout = self.module.run_command(args, check_rc=True, expand_user_and_vars=False)
|
||||||
|
|
||||||
|
def _register_using_dbus(self, username, password, auto_attach,
|
||||||
|
activationkey, org_id, consumer_type, consumer_name,
|
||||||
|
consumer_id, force_register, environment, release):
|
||||||
|
'''
|
||||||
|
Register using D-Bus (connecting to the rhsm service)
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
* Exception - if error occurs during the D-Bus communication
|
||||||
|
'''
|
||||||
|
import dbus
|
||||||
|
|
||||||
|
SUBSCRIPTION_MANAGER_LOCALE = 'C'
|
||||||
|
# Seconds to wait for Registration to complete over DBus;
|
||||||
|
# 10 minutes should be a pretty generous timeout.
|
||||||
|
REGISTRATION_TIMEOUT = 600
|
||||||
|
|
||||||
|
def str2int(s, default=0):
|
||||||
|
try:
|
||||||
|
return int(s)
|
||||||
|
except ValueError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
distro_id = distro.id()
|
||||||
|
distro_version = tuple(str2int(p) for p in distro.version_parts())
|
||||||
|
|
||||||
|
# Stop the rhsm service when using systemd (which means Fedora or
|
||||||
|
# RHEL 7+): this is because the service may not use new configuration bits
|
||||||
|
# - with subscription-manager < 1.26.5-1 (in RHEL < 8.2);
|
||||||
|
# fixed later by https://github.com/candlepin/subscription-manager/pull/2175
|
||||||
|
# - sporadically: https://bugzilla.redhat.com/show_bug.cgi?id=2049296
|
||||||
|
if distro_id == 'fedora' or distro_version[0] >= 7:
|
||||||
|
cmd = ['systemctl', 'stop', 'rhsm']
|
||||||
|
self.module.run_command(cmd, check_rc=True, expand_user_and_vars=False)
|
||||||
|
|
||||||
|
# While there is a 'force' options for the registration, it is actually
|
||||||
|
# not implemented (and thus it does not work)
|
||||||
|
# - in RHEL 7 and earlier
|
||||||
|
# - in RHEL 8 before 8.8: https://bugzilla.redhat.com/show_bug.cgi?id=2118486
|
||||||
|
# - in RHEL 9 before 9.2: https://bugzilla.redhat.com/show_bug.cgi?id=2121350
|
||||||
|
# Hence, use it only when implemented, manually unregistering otherwise.
|
||||||
|
# Match it on RHEL, since we know about it; other distributions
|
||||||
|
# will need their own logic.
|
||||||
|
dbus_force_option_works = False
|
||||||
|
if (distro_id == 'rhel' and
|
||||||
|
((distro_version[0] == 8 and distro_version[1] >= 8) or
|
||||||
|
(distro_version[0] == 9 and distro_version[1] >= 2) or
|
||||||
|
distro_version[0] > 9)):
|
||||||
|
dbus_force_option_works = True
|
||||||
|
|
||||||
|
if force_register and not dbus_force_option_works:
|
||||||
|
self.unregister()
|
||||||
|
|
||||||
|
register_opts = {}
|
||||||
|
if consumer_type:
|
||||||
|
register_opts['consumer_type'] = consumer_type
|
||||||
|
if consumer_name:
|
||||||
|
register_opts['name'] = consumer_name
|
||||||
|
if consumer_id:
|
||||||
|
register_opts['consumerid'] = consumer_id
|
||||||
|
if environment:
|
||||||
|
# The option for environments used to be 'environment' in versions
|
||||||
|
# of RHEL before 8.6, and then it changed to 'environments'; since
|
||||||
|
# the Register*() D-Bus functions reject unknown options, we have
|
||||||
|
# to pass the right option depending on the version -- funky.
|
||||||
|
environment_key = 'environment'
|
||||||
|
if distro_id == 'fedora' or \
|
||||||
|
(distro_id == 'rhel' and
|
||||||
|
((distro_version[0] == 8 and distro_version[1] >= 6) or
|
||||||
|
distro_version[0] >= 9)):
|
||||||
|
environment_key = 'environments'
|
||||||
|
register_opts[environment_key] = environment
|
||||||
|
if force_register and dbus_force_option_works:
|
||||||
|
register_opts['force'] = True
|
||||||
|
# Wrap it as proper D-Bus dict
|
||||||
|
register_opts = dbus.Dictionary(register_opts, signature='sv', variant_level=1)
|
||||||
|
|
||||||
|
connection_opts = {}
|
||||||
|
# Wrap it as proper D-Bus dict
|
||||||
|
connection_opts = dbus.Dictionary(connection_opts, signature='sv', variant_level=1)
|
||||||
|
|
||||||
|
bus = dbus.SystemBus()
|
||||||
|
register_server = bus.get_object('com.redhat.RHSM1',
|
||||||
|
'/com/redhat/RHSM1/RegisterServer')
|
||||||
|
address = register_server.Start(
|
||||||
|
SUBSCRIPTION_MANAGER_LOCALE,
|
||||||
|
dbus_interface='com.redhat.RHSM1.RegisterServer',
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Use the private bus to register the system
|
||||||
|
self.module.debug('Connecting to the private DBus')
|
||||||
|
private_bus = dbus.connection.Connection(address)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if activationkey:
|
||||||
|
args = (
|
||||||
|
org_id,
|
||||||
|
[activationkey],
|
||||||
|
register_opts,
|
||||||
|
connection_opts,
|
||||||
|
SUBSCRIPTION_MANAGER_LOCALE,
|
||||||
|
)
|
||||||
|
private_bus.call_blocking(
|
||||||
|
'com.redhat.RHSM1',
|
||||||
|
'/com/redhat/RHSM1/Register',
|
||||||
|
'com.redhat.RHSM1.Register',
|
||||||
|
'RegisterWithActivationKeys',
|
||||||
|
'sasa{sv}a{sv}s',
|
||||||
|
args,
|
||||||
|
timeout=REGISTRATION_TIMEOUT,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
args = (
|
||||||
|
org_id or '',
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
register_opts,
|
||||||
|
connection_opts,
|
||||||
|
SUBSCRIPTION_MANAGER_LOCALE,
|
||||||
|
)
|
||||||
|
private_bus.call_blocking(
|
||||||
|
'com.redhat.RHSM1',
|
||||||
|
'/com/redhat/RHSM1/Register',
|
||||||
|
'com.redhat.RHSM1.Register',
|
||||||
|
'Register',
|
||||||
|
'sssa{sv}a{sv}s',
|
||||||
|
args,
|
||||||
|
timeout=REGISTRATION_TIMEOUT,
|
||||||
|
)
|
||||||
|
|
||||||
|
except dbus.exceptions.DBusException as e:
|
||||||
|
# Sometimes we get NoReply but the registration has succeeded.
|
||||||
|
# Check the registration status before deciding if this is an error.
|
||||||
|
if e.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
|
||||||
|
if not self.is_registered():
|
||||||
|
# Host is not registered so re-raise the error
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
# Host was registered so continue
|
||||||
|
finally:
|
||||||
|
# Always shut down the private bus
|
||||||
|
self.module.debug('Shutting down private DBus instance')
|
||||||
|
register_server.Stop(
|
||||||
|
SUBSCRIPTION_MANAGER_LOCALE,
|
||||||
|
dbus_interface='com.redhat.RHSM1.RegisterServer',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make sure to refresh all the local data: this will fetch all the
|
||||||
|
# certificates, update redhat.repo, etc.
|
||||||
|
self.module.run_command([SUBMAN_CMD, 'refresh'],
|
||||||
|
check_rc=True, expand_user_and_vars=False)
|
||||||
|
|
||||||
|
if auto_attach:
|
||||||
|
args = [SUBMAN_CMD, 'attach', '--auto']
|
||||||
|
self.module.run_command(args, check_rc=True, expand_user_and_vars=False)
|
||||||
|
|
||||||
|
# There is no support for setting the release via D-Bus, so invoke
|
||||||
|
# the CLI for this.
|
||||||
|
if release:
|
||||||
|
args = [SUBMAN_CMD, 'release', '--set', release]
|
||||||
|
self.module.run_command(args, check_rc=True, expand_user_and_vars=False)
|
||||||
|
|
||||||
def unsubscribe(self, serials=None):
|
def unsubscribe(self, serials=None):
|
||||||
'''
|
'''
|
||||||
Unsubscribe a system from subscribed channels
|
Unsubscribe a system from subscribed channels
|
||||||
|
|
|
@ -27,6 +27,8 @@ def patch_redhat_subscription(mocker):
|
||||||
mocker.patch('ansible_collections.community.general.plugins.modules.redhat_subscription.unlink', return_value=True)
|
mocker.patch('ansible_collections.community.general.plugins.modules.redhat_subscription.unlink', return_value=True)
|
||||||
mocker.patch('ansible_collections.community.general.plugins.modules.redhat_subscription.AnsibleModule.get_bin_path',
|
mocker.patch('ansible_collections.community.general.plugins.modules.redhat_subscription.AnsibleModule.get_bin_path',
|
||||||
return_value='/testbin/subscription-manager')
|
return_value='/testbin/subscription-manager')
|
||||||
|
mocker.patch('ansible_collections.community.general.plugins.modules.redhat_subscription.Rhsm._can_connect_to_dbus',
|
||||||
|
return_value=False)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('patch_ansible_module', [{}], indirect=['patch_ansible_module'])
|
@pytest.mark.parametrize('patch_ansible_module', [{}], indirect=['patch_ansible_module'])
|
||||||
|
|
Loading…
Reference in a new issue