lmn-client/roles/lmn_printer/files/linuxmusterLinuxclient7/setup.py
2023-08-16 12:17:17 +02:00

364 lines
No EOL
11 KiB
Python

import os, re, sys, configparser, subprocess, shutil
from pathlib import Path
from linuxmusterLinuxclient7 import logging, constants, hooks, shares, config, user, templates, realm, fileHelper, printers, computer
def setup(domain=None, user=None):
"""
Sets up the client to be able to act in a linuxmuster environment
:param domain: The domain to join, defaults to the first discovered domain
:type domain: str, optional
:param user: The admin user for the join, defaults to global-admin
:type user: str, optional
:return: True on success, False otherwise
:rtype: bool
"""
logging.info('#### linuxmuster-linuxclient7 setup ####')
if not realm.clearUserCache():
return False
if not _cleanOldDomainJoins():
return False
rc, domain = _findDomain(domain)
if not rc:
return False
if user == None:
user = constants.defaultDomainAdminUser
if not _prepareNetworkConfiguration(domain):
return False
if not _deleteObsoleteFiles():
return False
if not templates.applyAll():
return False
if not _preparePam():
return False
if not _prepareServices():
return False
# Actually join domain!
print()
logging.info(f"#### Joining domain {domain} ####")
if not realm.join(domain, user):
return False
# copy server ca certificate in place
# This will also make sure that the domain join actually worked;
# mounting the sysvol will fail otherwise.
if not _installCaCertificate(domain, user):
return False
if not _adjustSssdConfiguration(domain):
return False
# run a final test
if not realm.verifyDomainJoin():
return False
print("\n\n")
logging.info(f"#### SUCCESSFULLY joined domain {domain} ####")
return True
def status():
"""
Checks the status of the client
:return: True on success, False otherwise
:rtype: bool
"""
logging.info('#### linuxmuster-linuxclient7 status ####')
if not isSetup():
logging.info("Not setup!")
return False
logging.info("Linuxmuster-linuxclient7 is setup!")
logging.info("Testing if domain is joined...")
logging.info("Checking joined domains")
rc, joinedDomains = realm.getJoinedDomains()
if not rc:
return False
print()
logging.info("Joined domains:")
for joinedDomain in joinedDomains:
logging.info(f"* {joinedDomain}")
print()
if len(joinedDomains) > 0 and not realm.verifyDomainJoin():
print()
# Give a little explination to our users :)
print("\n===============================================================================================")
print("This Computer is joined to a domain, but it was not possible to authenticate")
print("to the domain controller. There is an error with your domain join! The login WILL NOT WORK!")
print("Please try to re-join the domain using 'linuxmuster-linuxclient7 setup' and create a new image.")
print("===============================================================================================\n")
return False
elif len(joinedDomains) <= 0:
print()
logging.info('#### This client is not joined to any domain. ####')
print("#### To join a domain, run \"linuxmuster-linuxclient7 setup\" ####")
print()
logging.info('#### linuxmuster-linuxclient7 is fully setup and working! ####')
return True
def upgrade():
"""
Performs an upgrade of the linuxmuster-linuxclient7. This is executed after the package is updated.
:return: True on success, False otherwise
:rtype: bool
"""
if not isSetup():
logging.info("linuxmuster-linuxclient7 does not seem to be setup -> no upgrade is needed")
return True
logging.info('#### linuxmuster-linuxclient7 upgrade ####')
if not config.upgrade():
return False
if not _deleteObsoleteFiles():
return False
if not templates.applyAll():
return False
if not _prepareServices():
return False
rc, joinedDomains = realm.getJoinedDomains()
if not rc:
return False
for domain in joinedDomains:
_adjustSssdConfiguration(domain)
logging.info('#### linuxmuster-linuxclient7 upgrade SUCCESSFULL ####')
return True
def clean():
"""Removes all sensitive files like keys and leaves all domain joins.
"""
logging.info("#### linuxmuster-linuxclient7 clean ####")
realm.clearUserCache()
_cleanOldDomainJoins()
# clean /etc/pam.d/common-session
logging.info("Cleaning /etc/pam.d/common-session to prevent logon brick")
fileHelper.removeLinesInFileContainingString("/etc/pam.d/common-session", ["pam_mkhomedir.so", "pam_exec.so", "pam_mount.so", "linuxmuster.net", "linuxmuster-linuxclient7", "linuxmuster-client-adsso"])
logging.info('#### linuxmuster-linuxclient7 clean SUCCESSFULL ####')
def isSetup():
"""
Checks if the client is setup.
:return: True if setup, False otherwise
:rtype: bool
"""
return os.path.isfile(constants.networkConfigFilePath)
# --------------------
# - Helper functions -
# --------------------
def _cleanOldDomainJoins():
# stop sssd
logging.info("Stopping sssd")
if subprocess.call(["service", "sssd", "stop"]) != 0:
logging.error("Failed!")
return False
# Clean old domain join data
logging.info("Deleting old kerberos tickets.")
subprocess.call(["kdestroy"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if not realm.leaveAll():
return False
# delete krb5.keytab file, if existent
logging.info('Deleting krb5.keytab if it exists ... ')
if not fileHelper.deleteFile("/etc/krb5.keytab"):
return False
# delete old CA Certificate
logging.info('Deleting old CA certificate if it exists ... ')
if not fileHelper.deleteFilesWithExtension("/var/lib/samba/private/tls", ".pem"):
return False
# remove network.conf
logging.info(f"Deleting {constants.networkConfigFilePath} if exists ...")
if not fileHelper.deleteFile(constants.networkConfigFilePath):
return False
return True
def _findDomain(domain=None):
logging.info("Trying to discover available domains...")
rc, availableDomains = realm.discoverDomains()
if not rc or len(availableDomains) < 1:
logging.error("Could not discover any domain!")
return False, None
if domain == None:
domain = availableDomains[0]
logging.info(f"Using first discovered domain {domain}")
elif domain in availableDomains:
logging.info(f"Using domain {domain}")
else:
print("\n")
logging.error(f"Could not find domain {domain}!")
return False, None
return True, domain
def _prepareNetworkConfiguration(domain):
logging.info("Preparing network configuration")
rc, domainConfig = realm.getDomainConfig(domain)
if not rc:
logging.error("Could not read domain configuration")
return False
newNetworkConfig = {}
newNetworkConfig["serverHostname"] = domainConfig["domain-controller"]
newNetworkConfig["domain"] = domainConfig["domain-name"]
newNetworkConfig["realm"] = domainConfig["domain-name"].upper()
config.writeNetworkConfig(newNetworkConfig)
return True
def _preparePam():
# enable necessary pam modules
logging.info('Updating pam configuration ... ')
subprocess.call(['pam-auth-update', '--package', '--enable', 'libpam-mount', 'pwquality', 'sss', '--force'])
## mkhomedir was injected in template not using pam-auth-update
subprocess.call(['pam-auth-update', '--package', '--remove', 'krb5', 'mkhomedir', '--force'])
return True
def _prepareServices():
logging.info("Raloading systctl daemon")
subprocess.call(["systemctl", "daemon-reload"])
logging.info('Enabling services:')
services = ['linuxmuster-linuxclient7', 'smbd', 'nmbd', 'sssd']
for service in services:
logging.info('* %s' % service)
subprocess.call(['systemctl','enable', service + '.service'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
logging.info('Restarting services:')
services = ['smbd', 'nmbd', 'systemd-timesyncd']
for service in services:
logging.info('* %s' % service)
subprocess.call(['systemctl', 'restart' , service + '.service'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return True
def _installCaCertificate(domain, user):
logging.info('Installing server ca certificate ... ')
# try to mount the share
rc, sysvolMountpoint = shares.getLocalSysvolPath()
if not rc:
logging.error("Failed to mount sysvol!")
return False
cacertPath = f"{sysvolMountpoint}/{domain}/tls/cacert.pem"
cacertTargetPath = f"/var/lib/samba/private/tls/{domain}.pem"
logging.info("Copying CA certificate from server to client!")
try:
Path(Path(cacertTargetPath).parent.absolute()).mkdir(parents=True, exist_ok=True)
shutil.copyfile(cacertPath, cacertTargetPath)
except Exception as e:
logging.error("Failed!")
logging.exception(e)
return False
# make sure the file was successfully copied
if not os.path.isfile(cacertTargetPath):
logging.error('Failed to copy over CA certificate!')
return False
# unmount sysvol
shares.unmountAllSharesOfUser(computer.krbHostName())
return True
def _adjustSssdConfiguration(domain):
logging.info("Adjusting sssd.conf")
sssdConfigFilePath = '/etc/sssd/sssd.conf'
sssdConfig = configparser.ConfigParser(interpolation=None)
sssdConfig.read(sssdConfigFilePath)
# accept usernames without domain
sssdConfig[f"domain/{domain}"]["use_fully_qualified_names"] = "False"
# override homedir
sssdConfig[f"domain/{domain}"]["override_homedir"] = "/home/%u"
# Don't validate KVNO! Otherwise the Login will fail when the KVNO stored
# in /etc/krb5.keytab does not match the one in the AD (msDS-KeyVersionNumber)
sssdConfig[f"domain/{domain}"]["krb5_validate"] = "False"
sssdConfig[f"domain/{domain}"]["ad_gpo_access_control"] = "permissive"
sssdConfig[f"domain/{domain}"]["ad_gpo_ignore_unreadable"] = "True"
# Don't renew the machine password, as this will break the domain join
# See: https://github.com/linuxmuster/linuxmuster-linuxclient7/issues/27
sssdConfig[f"domain/{domain}"]["ad_maximum_machine_account_password_age"] = "0"
# Make sure usernames are not case sensitive
sssdConfig[f"domain/{domain}"]["case_sensitive"] = "False"
try:
logging.info("Writing new Configuration")
with open(sssdConfigFilePath, 'w') as sssdConfigFile:
sssdConfig.write(sssdConfigFile)
except Exception as e:
logging.error("Failed!")
logging.exception(e)
return False
logging.info("Restarting sssd")
if subprocess.call(["service", "sssd", "restart"]) != 0:
logging.error("Failed!")
return False
return True
def _deleteObsoleteFiles():
# files
logging.info("Deleting obsolete files")
for obsoleteFile in constants.obsoleteFiles:
logging.info(f"* {obsoleteFile}")
fileHelper.deleteFile(obsoleteFile)
# directories
logging.info("Deleting obsolete directories")
for obsoleteDirectory in constants.obsoleteDirectories:
logging.info(f"* {obsoleteDirectory}")
fileHelper.deleteDirectory(obsoleteDirectory)
return True