Allow ipa tool to force specific server

Fixes: https://pagure.io/freeipa/issue/9744
Signed-off-by: Aleksandr Sharov <asharov@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
Reviewed-By: David Hanina <dhanina@redhat.com>
This commit is contained in:
Aleksandr Sharov authored and Florence Blanc-Renaud committed 2025-10-14 08:20:23 +02:00
1 parent a66df6d7b4
commit d9a2a6abcd
5 files changed
+124 -39

No files matched your search

+3
View File
@@ -55,6 +55,9 @@ Prompt for all parameters of \fICOMMAND\fR, even if they are optional.
\fB\-f\fR, \fB\-\-no\-fallback\fR
Don't fall back to other IPA servers if the default doesn't work.
.TP
\fB\-\-force\-server\fR \fIfqdn\fR
Specify the IPA server to connect to. Overrides the default server in \fI/etc/ipa/default.conf\fR, does not fall back to SRV if server doesn\'t reply. Requires fully-qualified domain name.
.TP
\fB\-v\fR, \fB\-\-verbose\fR
Produce verbose output. A second -v pretty-prints the JSON request and response. A third \-v displays the HTTP request and response.
.TP
+57 -37
View File
@@ -536,17 +536,20 @@ class Env:
1. Call `Env._bootstrap()` if it hasn't already been called.
2. Merge-in variables from the configuration file ``self.conf``
2. If server is forced, derive URIs from it by calling
`Env._derive_uris()`.
3. Merge-in variables from the configuration file ``self.conf``
(if it exists) by calling `Env._merge_from_file()`.
3. Merge-in variables from the defaults configuration file
4. Merge-in variables from the defaults configuration file
``self.conf_default`` (if it exists) by calling
`Env._merge_from_file()`.
4. Intelligently fill-in the *in_server* , *logdir*, *log*, and
*jsonrpc_uri* variables if they haven't already been set.
5. Intelligently fill-in the *in_server* , *logdir*, *log*
variables and derive URIs if they haven't already been set.
5. Merge-in the variables in ``defaults`` by calling `Env._merge()`.
6. Merge-in the variables in ``defaults`` by calling `Env._merge()`.
In normal circumstances ``defaults`` will simply be those
specified in `constants.DEFAULT_CONFIG`.
@@ -565,6 +568,13 @@ class Env:
self.__doing('_finalize_core')
self.__do_if_not_done('_bootstrap')
# If server is forced, set it up and derive URIs from it:
uris_derived = False
if 'forced_server' in self and 'server' not in self:
self.server = self["forced_server"]
self._derive_uris(**defaults)
uris_derived = True
# Merge in context config file and then default config file:
mode = self.__d.get('mode') # pylint: disable=no-member
# documented public modes: production, developer
@@ -601,38 +611,9 @@ class Env:
if 'basedn' not in self and 'domain' in self:
self.basedn = DN(*(('dc', dc) for dc in self.domain.split('.')))
# Derive xmlrpc_uri from server
# (Note that this is done before deriving jsonrpc_uri from xmlrpc_uri
# and server from jsonrpc_uri so that when only server or xmlrpc_uri
# is specified, all 3 keys have a value.)
if 'xmlrpc_uri' not in self and 'server' in self:
self.xmlrpc_uri = 'https://{}/ipa/xml'.format(self.server)
# Derive ldap_uri from server
if 'ldap_uri' not in self and 'server' in self:
self.ldap_uri = 'ldap://{}'.format(self.server)
# Derive jsonrpc_uri from xmlrpc_uri
if 'jsonrpc_uri' not in self:
if 'xmlrpc_uri' in self:
xmlrpc_uri = self.xmlrpc_uri
else:
xmlrpc_uri = defaults.get('xmlrpc_uri')
if xmlrpc_uri:
(scheme, netloc, uripath, params, query, fragment
) = urlparse(xmlrpc_uri)
uripath = uripath.replace('/xml', '/json', 1)
self.jsonrpc_uri = urlunparse((
scheme, netloc, uripath, params, query, fragment))
if 'server' not in self:
if 'jsonrpc_uri' in self:
jsonrpc_uri = self.jsonrpc_uri
else:
jsonrpc_uri = defaults.get('jsonrpc_uri')
if jsonrpc_uri:
parsed = urlparse(jsonrpc_uri)
self.server = parsed.netloc
# Derive URIs if not already done:
if not uris_derived:
self._derive_uris(**defaults)
self._merge(**defaults)
@@ -666,6 +647,45 @@ class Env:
"tls_version_min is set to a higher TLS version than "
"tls_version_max.")
def _derive_uris(self, **defaults):
"""
Intelligently derive URIs from other variables
"""
# Derive xmlrpc_uri from server
# (Note that this is done before deriving jsonrpc_uri from xmlrpc_uri
# and server from jsonrpc_uri so that when only server or xmlrpc_uri
# is specified, all 3 keys have a value.)
if 'xmlrpc_uri' not in self and 'server' in self:
self.xmlrpc_uri = 'https://{}/ipa/xml'.format(self.server)
# Derive ldap_uri from server
if 'ldap_uri' not in self and 'server' in self:
self.ldap_uri = 'ldap://{}'.format(self.server)
# Derive jsonrpc_uri from xmlrpc_uri
if 'jsonrpc_uri' not in self:
if 'xmlrpc_uri' in self:
xmlrpc_uri = self.xmlrpc_uri
else:
xmlrpc_uri = defaults.get('xmlrpc_uri')
if xmlrpc_uri:
(scheme, netloc, uripath, params, query, fragment) = urlparse(
xmlrpc_uri
)
uripath = uripath.replace("/xml", "/json", 1)
self.jsonrpc_uri = urlunparse(
(scheme, netloc, uripath, params, query, fragment)
)
if 'server' not in self:
if 'jsonrpc_uri' in self:
jsonrpc_uri = self.jsonrpc_uri
else:
jsonrpc_uri = defaults.get('jsonrpc_uri')
if jsonrpc_uri:
parsed = urlparse(jsonrpc_uri)
self.server = parsed.netloc
def _finalize(self, **lastchance):
"""
Finalize and lock environment.
+13 -1
View File
@@ -562,6 +562,15 @@ class API(ReadOnly):
callback=config_file_callback, type='string',
help='Load configuration from FILE.',
)
parser.add_option(
'--force-server',
dest='forced_server',
metavar='FQDN',
type='string',
help='Specify the IPA server to connect to. Overrides the default '
'server in /etc/ipa/default.conf, does not fall back to SRV if '
'server doesn\'t reply',
)
parser.add_option('-d', '--debug', action='store_true',
help='Produce full debuging output',
)
@@ -607,10 +616,13 @@ class API(ReadOnly):
raise errors.OptionError(_('Unable to parse option {item}'
.format(item=item)))
for key in ('conf', 'debug', 'verbose', 'prompt_all', 'interactive',
'fallback', 'delegate'):
'forced_server', 'fallback', 'delegate'):
value = getattr(options, key, None)
if value is not None:
overrides[key] = value
if key == 'forced_server':
if getattr(options, 'fallback', None) is None:
overrides['fallback'] = False
if hasattr(options, 'prod'):
overrides['webui_prod'] = options.prod
if context is not None:
+9 -1
View File
@@ -427,7 +427,15 @@ class SSLTransport(LanguageAwareTransport):
tls_version_min=api.env.tls_version_min,
tls_version_max=api.env.tls_version_max)
conn.connect()
try:
conn.connect()
except socket.error as e:
# Network error, close connection and let transport
# handle reconnect for us.
self.close()
logger.debug("HTTP connection destroyed (%s)", host)
raise errors.NetworkError(message=unicode(e))
logger.debug("New HTTP connection (%s)", host)
self._connection = host, conn
@@ -1813,6 +1813,48 @@ class TestIPACommand(IntegrationTest):
assert f"{interm_nick} {intermediate_serial}" not in certs
assert f"{interm_nick} {duplicate_serial}" in certs
def test_ipa_force_server(self):
"""
Test ipa command with --force-server option
Do ping to a replica from the master, verify that the ipa tool
connected to the replica
"""
tasks.kinit_admin(self.master)
replica = self.replicas[0]
result = self.master.run_command(
["ipa", "--force-server", replica.hostname, "--debug", "ping"],
)
assert replica.hostname in result.stderr_text
# test with invalid server
failresult = self.master.run_command(
["ipa",
"--force-server",
'1' + str(replica.hostname),
"--debug",
"ping"],
raiseonerr=False
)
assert '[Errno -2] Name or service not known' in failresult.stderr_text
# test with not running valid server
self.master.run_command(
["systemctl", "stop", "ipa"]
)
failresult = self.master.run_command(
["ipa",
"--force-server",
self.master.hostname,
"--debug",
"ping"],
raiseonerr=False
)
assert '[Errno 111] Connection refused' in failresult.stderr_text
self.master.run_command(
["systemctl", "start", "ipa"]
)
def test_expiration_date_post_2038(self, expire_password):
"""Test that expiration dates after 2038 function without
overflow.