Skip to content

Commit

Permalink
redhat_subscription: use D-Bus for registration if possible
Browse files Browse the repository at this point in the history
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
- the CLI 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] oamg/convert2rhel#540
  • Loading branch information
ptoscano committed Mar 1, 2023
1 parent d209466 commit 34ee8da
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 2 deletions.
180 changes: 178 additions & 2 deletions plugins/modules/redhat_subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
DOCUMENTATION = '''
---
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:
- 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)"
notes:
- In order to register a system, subscription-manager requires either a username and password, or an activationkey and an Organization ID.
Expand Down Expand Up @@ -410,13 +410,68 @@ def is_registered(self):
else:
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 python-dbus
# as D-Bus library, so this ought to work; better be safe than
# sorry, I guess...
import dbus
except ImportError:
self.module.debug('dbus not available, will use CLI')
return False

try:
bus = dbus.SystemBus()
msg = dbus.lowlevel.SignalMessage('/', 'com.example', 'test')
bus.send_message(msg)
bus.flush()

self.module.debug('Verified system D-Bus bus as usable')

return True

except dbus.exceptions.DBusException:
pass

self.module.debug('Failed to connect to system D-Bus bus, will use CLI')

return False

def register(self, username, password, token, auto_attach, activationkey, org_id,
consumer_type, consumer_name, consumer_id, force_register, environment,
release):
'''
Register the current system to the provided RHSM or Red Hat Satellite
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, token, 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:
* Exception - if error occurs while running command
'''
Expand Down Expand Up @@ -459,6 +514,127 @@ def register(self, username, password, token, auto_attach, activationkey, org_id

rc, stderr, stdout = self.module.run_command(args, check_rc=True, expand_user_and_vars=False)

def _register_using_dbus(self, username, password, token, 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

if force_register:
# While there is a 'force' options for the registration,
# it actually 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
# Since it is not something that can be detected easily (short of
# checking the version, which might not be reliable), unregister
# manually first in case a force registration is requested.
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:
register_opts['environments'] = environment
# 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',
)

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):
'''
Unsubscribe a system from subscribed channels
Expand Down
1 change: 1 addition & 0 deletions tests/unit/plugins/modules/test_redhat_subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ 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.AnsibleModule.get_bin_path',
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'])
Expand Down

0 comments on commit 34ee8da

Please sign in to comment.