mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
ipa-backup: Make sure all roles are installed on the current master.
ipa-backup does not check whether the IPA master it is running on has all used roles installed. This can lead into situations where backups are done on a CAless or KRAless host while these roles are used in the IPA cluster. These backups cannot be used to restore a complete cluster. With this change, ipa-backup refuses to execute if the roles installed on the current host do not match the list of roles used in the cluster. A --disable-role-check knob is provided to restore the previous behavior. Fixes: https://pagure.io/freeipa/issue/8217 Signed-off-by: François Cami <fcami@redhat.com> Reviewed-By: Rob Crittenden <rcritten@redhat.com> Reviewed-By: Mohammad Rizwan Yusuf <myusuf@redhat.com>
This commit is contained in:
parent
9324bba6b7
commit
3665ba928b
@ -51,6 +51,9 @@ Include the IPA service log files in the backup.
|
||||
\fB\-\-online\fR
|
||||
Perform the backup on\-line. Requires the \-\-data option.
|
||||
.TP
|
||||
\fB\-\-disable\-role\-check\fR
|
||||
Perform the backup even if this host does not have all the roles in use in the cluster. This is not recommended.
|
||||
.TP
|
||||
\fB\-\-v\fR, \fB\-\-verbose\fR
|
||||
Print debugging information
|
||||
.TP
|
||||
@ -85,4 +88,4 @@ The log file for backups
|
||||
.PP
|
||||
.SH "SEE ALSO"
|
||||
.BR ipa\-restore(1)
|
||||
.BR gpg2(1)
|
||||
.BR gpg2(1)
|
||||
|
@ -247,12 +247,16 @@ class Backup(admintool.AdminTool):
|
||||
"--online", dest="online", action="store_true",
|
||||
default=False,
|
||||
help="Perform the LDAP backups online, for data only.")
|
||||
|
||||
parser.add_option(
|
||||
"--disable-role-check", dest="rolecheck", action="store_false",
|
||||
default=True,
|
||||
help="Perform the backup even if this host does not have all "
|
||||
"the roles used in the cluster. This is not recommended."
|
||||
)
|
||||
|
||||
def setup_logging(self, log_file_mode='a'):
|
||||
super(Backup, self).setup_logging(log_file_mode='a')
|
||||
|
||||
|
||||
def validate_options(self):
|
||||
options = self.options
|
||||
super(Backup, self).validate_options(needs_root=True)
|
||||
@ -279,7 +283,6 @@ class Backup(admintool.AdminTool):
|
||||
self.option_parser.error("You cannot specify --data "
|
||||
"with --logs")
|
||||
|
||||
|
||||
def run(self):
|
||||
options = self.options
|
||||
super(Backup, self).run()
|
||||
@ -312,6 +315,8 @@ class Backup(admintool.AdminTool):
|
||||
|
||||
self.get_connection()
|
||||
|
||||
self.check_roles(raiseonerr=options.rolecheck)
|
||||
|
||||
self.create_header(options.data_only)
|
||||
if options.data_only:
|
||||
if not options.online:
|
||||
@ -358,6 +363,79 @@ class Backup(admintool.AdminTool):
|
||||
logger.error('Cannot change directory to %s: %s', cwd, e)
|
||||
shutil.rmtree(self.top_dir)
|
||||
|
||||
def check_roles(self, raiseonerr=True):
|
||||
"""Check that locally-installed roles match the globally used ones.
|
||||
|
||||
Specifically: make sure no role used in the cluster is absent
|
||||
from the local replica ipa-backup is running on.
|
||||
"""
|
||||
|
||||
locally_installed_roles = set()
|
||||
globally_used_roles = set()
|
||||
|
||||
# We need to cover the following roles:
|
||||
# * DNS: filter="(|(cn=DNS)(cn=DNSKeySync))"
|
||||
# * CA: filter="(cn=CA)"
|
||||
# * KRA: filter="(cn=KRA)"
|
||||
# * AD Trust Controller: filter="(cn=ADTRUST)"
|
||||
# Note:
|
||||
# We do not need to worry about AD Trust Agents as Trust
|
||||
# Controllers are Trust Agents themselves and contain extra,
|
||||
# necessary Samba configuration. So either the cluster has no
|
||||
# AD Trust bits installed, or it should be backuped on a Trust
|
||||
# Controller, not a Trust Agent.
|
||||
role_names = {
|
||||
'CA', 'DNS', 'DNSKeySync', 'KRA', 'ADTRUST'
|
||||
}
|
||||
|
||||
search_base = DN(api.env.container_masters, api.env.basedn)
|
||||
attrs_list = ['ipaconfigstring', 'cn']
|
||||
|
||||
for role in role_names:
|
||||
search_filter = '(cn=%s)' % role
|
||||
try:
|
||||
masters = dict()
|
||||
result = self._conn.get_entries(
|
||||
search_base,
|
||||
filter=search_filter,
|
||||
attrs_list=attrs_list,
|
||||
scope=self._conn.SCOPE_SUBTREE
|
||||
)
|
||||
masters[role] = {e.dn[1]['cn'] for e in result}
|
||||
|
||||
if api.env.host in masters[role]:
|
||||
locally_installed_roles.add(role)
|
||||
if masters[role] is not None:
|
||||
globally_used_roles.add(role)
|
||||
except errors.EmptyResult:
|
||||
pass
|
||||
|
||||
if locally_installed_roles == globally_used_roles:
|
||||
logger.info(
|
||||
"Local roles match globally used roles, proceeding."
|
||||
)
|
||||
else:
|
||||
if raiseonerr:
|
||||
raise admintool.ScriptError(
|
||||
'Error: Local roles %s do not match globally used '
|
||||
'roles %s. A backup done on this host would not be '
|
||||
'complete enough to restore a fully functional, '
|
||||
'identical cluster.' % (
|
||||
', '.join(sorted(locally_installed_roles)),
|
||||
', '.join(sorted(globally_used_roles))
|
||||
)
|
||||
)
|
||||
else:
|
||||
msg = (
|
||||
'Warning: Local roles %s do not match globally used roles '
|
||||
'%s. A backup done on this host would not be complete '
|
||||
'enough to restore a fully functional, identical cluster. '
|
||||
'Proceeding as role check was explicitly disabled.' % (
|
||||
', '.join(sorted(locally_installed_roles)),
|
||||
', '.join(sorted(globally_used_roles))
|
||||
)
|
||||
)
|
||||
logger.info(msg)
|
||||
|
||||
def add_instance_specific_data(self):
|
||||
'''
|
||||
@ -387,7 +465,6 @@ class Backup(admintool.AdminTool):
|
||||
|
||||
self.logs.append(paths.VAR_LOG_DIRSRV_INSTANCE_TEMPLATE % serverid)
|
||||
|
||||
|
||||
def get_connection(self):
|
||||
'''
|
||||
Create an ldapi connection and bind to it using autobind as root.
|
||||
@ -405,7 +482,6 @@ class Backup(admintool.AdminTool):
|
||||
|
||||
return self._conn
|
||||
|
||||
|
||||
def db2ldif(self, instance, backend, online=True):
|
||||
'''
|
||||
Create a LDIF backup of the data in this instance.
|
||||
@ -481,7 +557,6 @@ class Backup(admintool.AdminTool):
|
||||
'Unexpected error: %s' % e
|
||||
)
|
||||
|
||||
|
||||
def db2bak(self, instance, online=True):
|
||||
'''
|
||||
Create a BAK backup of the data and changelog in this instance.
|
||||
@ -545,7 +620,6 @@ class Backup(admintool.AdminTool):
|
||||
'Unexpected error: %s' % e
|
||||
)
|
||||
|
||||
|
||||
def file_backup(self, options):
|
||||
|
||||
def verify_directories(dirs):
|
||||
@ -611,7 +685,6 @@ class Backup(admintool.AdminTool):
|
||||
# Rename the archive back to files.tar to preserve compatibility
|
||||
os.rename(os.path.join(self.dir, 'files.tar.gz'), self.tarfile)
|
||||
|
||||
|
||||
def create_header(self, data_only):
|
||||
'''
|
||||
Create the backup file header that contains the meta data about
|
||||
@ -649,7 +722,6 @@ class Backup(admintool.AdminTool):
|
||||
with open(self.header, 'w') as fd:
|
||||
config.write(fd)
|
||||
|
||||
|
||||
def finalize_backup(self, data_only=False, encrypt=False, keyring=None):
|
||||
'''
|
||||
Create the final location of the backup files and move the files
|
||||
|
Loading…
Reference in New Issue
Block a user