ipa-client-samba: a tool to configure Samba domain member on IPA client

Introduces new utility to configure Samba on an IPA domain member.

The tool sets up Samba configuration and internal databases, creates
cifs/... Kerberos service and makes sure that a keytab for this service
contains the key with the same randomly generated password that is set
in the internal Samba databases.

Samba configuration is created by querying an IPA master about details
of trust to Active Directory configuration. All known identity ranges
added to the configuration to allow Samba to properly handle them
(read-only) via idmap_sss.

Resulting configuration allows connection with both NTLMSSP and Kerberos
authentication for IPA users. Access controls for the shared content
should be set by utilizing POSIX ACLs on the file system under a
specific share.

The utility is packaged as freeipa-client-samba package to allow pulling
in all required dependencies for Samba and cifs.ko (smb3.ko) kernel
module. This allows an IPA client to become both an SMB server and an
SMB client.

Fixes: https://pagure.io/freeipa/issue/3999
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Christian Heimes <cheimes@redhat.com>
This commit is contained in:
Alexander Bokovoy
2019-05-18 14:54:48 +03:00
parent afb8305ada
commit 814592cf22
10 changed files with 1034 additions and 1 deletions

1
.gitignore vendored
View File

@@ -127,6 +127,7 @@ makeapi
client/ipa-certupdate
client/ipa-client-automount
client/ipa-client-install
client/ipa-client-samba
daemons/dnssec/ipa-dnskeysyncd
daemons/dnssec/ipa-dnskeysync-replica
daemons/dnssec/ipa-ods-exporter

View File

@@ -43,6 +43,7 @@ sbin_SCRIPTS = \
ipa-certupdate \
ipa-client-automount \
ipa-client-install \
ipa-client-samba \
$(NULL)
ipa_getkeytab_SOURCES = \
@@ -102,6 +103,7 @@ EXTRA_DIST = \
ipa-certupdate.in \
ipa-client-automount.in \
ipa-client-install.in \
ipa-client-samba.in \
$(NULL)
install-data-hook:

21
client/ipa-client-samba.in Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/python3
#
# Copyright (C) 2019 FreeIPA Contributors see COPYING for license
#
# Configure the Samba suite to operate as domain member in IPA domain
import os
import sys
from ipaclient.install import ipa_client_samba
try:
if not os.geteuid() == 0:
sys.exit("\nMust be run as root\n")
sys.exit(ipa_client_samba.run())
except SystemExit as e:
sys.exit(e)
except RuntimeError as e:
sys.exit(e)
except (KeyboardInterrupt, EOFError):
sys.exit(1)

View File

@@ -7,6 +7,7 @@ dist_man1_MANS = \
ipa-rmkeytab.1 \
ipa-client-install.1 \
ipa-client-automount.1 \
ipa-client-samba.1 \
ipa-certupdate.1 \
ipa-join.1 \
ipa.1

View File

@@ -0,0 +1,88 @@
.\" A man page for ipa-client-samba
.\" Copyright (C) 2008-2016 FreeIPA Contributors see COPYING for license
.\"
.TH "ipa-client-samba" "1" "Jun 10 2019" "FreeIPA" "FreeIPA Manual Pages"
.SH "NAME"
ipa\-client\-samba \- Configure Samba file server on an IPA client
.SH "SYNOPSIS"
ipa\-client\-samba [\fIOPTION\fR]...
.SH "DESCRIPTION"
Configures a Samba file server on the client machine to use IPA domain controller for authentication and identity services.
The tool configures Samba file server to be a domain member of IPA domain. Samba file server will use SSSD to resolve information about users and groups, and will use IPA master it is enrolled against as its domain controller.
It is not possible to reconciliate original Samba environment if that was pre-existing on the client with new configuration. Samba databases will be updated to follow IPA domain details and \fBsmb.conf\fR configuration will will be overwritten. It is recommended to enable Samba suite on a freshly deployed IPA client.
.TP
During the configuration process, the tool will perform following steps:
1. Discover details of IPA domain: realm, domain SID, domain ID range
2. Discover details of trusted Actvide Directory domains: domain name, domain SID, domain ID range
3. Create Samba configuration file using the details discovered above.
4. Create Samba Kerberos service using host credentials and fetch its keytab into /etc/samba/samba.keytab. The Kerberos service key is pre-set to a randomly generated value that is shared with Samba.
5. Populate Samba databases by setting the domain details and the randomly generated machine account password from the previous step.
6. Create a default [homes] share to allow users to log in to their home directories unless \-\-no\-homes option was specified.
.TP
The tool does not start nor does it enable Samba file services after the configuration. In order to enable and start Samba file services, one needs to enable both \fBsmb.service\fR and \fBwinbind.service\fR system services. Please check that \fB/etc/samba/smb.conf\fR contains all settings for your use case as starting Samba service will make identity mapping details written into the Samba databases. To enable and start Samba file services at the same time one can use \fBsystemctl enable \-\-now\fR command:
systemctl enable --now smb winbind
.SS "Assumptions"
The ipa\-client\-samba script assumes that the machine has alreaby been enrolled into IPA.
.SS "IPA Master Requirements"
At least one IPA master must hold a \fBTrust Controller\fR role. This can be achieved by running ipa\-adtrust\-install on the IPA master. The utility will configure IPA master to be a domain controller for IPA domain.
IPA master holding a \fBTrust Controller\fR role has also to have support for a special service command to create SMB service, \fBipa service-add-smb\fR. This command is available with FreeIPA 4.8.0 or later release.
.SH "OPTIONS"
.SS "BASIC OPTIONS"
.TP
\fB\-\-server\fR=\fISERVER\fR
Set the FQDN of the IPA server to connect to. Under normal circumstances, this option is not needed as the server to use is discovered automatically.
.TP
\fB\-\-no\-homes\fR
Do not configure a \fB[homes]\fR share by default to allow users to access their home directories.
.TP
\fB\-\-no\-nfs\fR
Do not enable SELinux booleans to allow Samba to re-share NFS shares.
.TP
\fB\-\-netbios-name\fR=\fINETBIOS_NAME\fR
NetBIOS name of this machine. If not provided then this is determined based on the leading component of the hostname.
.TP
\fB\-d\fR, \fB\-\-debug\fR
Print debugging information to stdout
.TP
\fB\-U\fR, \fB\-\-unattended\fR
Unattended installation. The user will not be prompted.
.TP
\fB\-\-uninstall\fR
Revert Samba suite configuration changes and remove SMB service principal. It is not possible to preserve original Samba configuration: while \fBsmb.conf\fR configuration file will be restored, various Samba databases would not be restored. In general, it is not possible to restore full original Samba environment.
.TP
\fB\-\-force\fR
Force through the installation steps even if they were done before
.SH "FILES"
.TP
Files that will be replaced if Samba is configured:
/etc/samba/smb.conf
.br
/etc/samba/samba.keytab
.SH "EXIT STATUS"
0 if the installation was successful
1 if an error occurred
.SH "SEE ALSO"
.BR smb.conf(5),
.BR krb5.conf(5),
.BR sssd.conf(5),
.BR systemctl(1)

View File

@@ -520,6 +520,22 @@ If your network uses IPA for authentication, this package should be
installed on every client machine.
This package provides command-line tools for IPA administrators.
%package client-samba
Summary: Tools to configure Samba on IPA client
Group: System Environment/Base
Requires: %{name}-client = %{version}-%{release}
Requires: python3-samba
Requires: samba-client
Requires: samba-winbind
Requires: samba-common-tools
Requires: samba
Requires: sssd-winbind-idmap
Requires: tdb-tools
Requires: cifs-utils
%description client-samba
This package provides command-line tools to deploy Samba domain member
on the machine enrolled into a FreeIPA environment
%package -n python3-ipaclient
Summary: Python libraries used by IPA client
@@ -1207,6 +1223,11 @@ fi
%{_mandir}/man1/ipa-certupdate.1*
%{_mandir}/man1/ipa-join.1*
%files client-samba
%doc README.md Contributors.txt
%license COPYING
%{_sbindir}/ipa-client-samba
%{_mandir}/man1/ipa-client-samba.1*
%files -n python3-ipaclient
%doc README.md Contributors.txt

View File

@@ -0,0 +1,745 @@
#
# Copyright (C) 2019 FreeIPA Contributors see COPYING for license
#
# Configure the Samba suite to operate as domain member in IPA domain
from __future__ import print_function
import logging
import os
import gssapi
from urllib.parse import urlsplit
from optparse import OptionParser # pylint: disable=deprecated-module
from contextlib import contextmanager
from ipaclient import discovery
from ipaclient.install.client import (
CLIENT_NOT_CONFIGURED,
CLIENT_ALREADY_CONFIGURED,
)
from ipalib import api, errors
from ipalib.install import sysrestore
from ipalib.util import check_client_configuration
from ipalib.request import context
from ipapython import ipautil
from ipapython.errors import SetseboolError
from ipapython.ipa_log_manager import standard_logging_setup
from ipapython.dnsutil import DNSName
from ipaplatform.tasks import tasks
from ipaplatform.paths import paths
from ipaplatform.constants import constants
from ipaplatform import services
from ipapython.admintool import ScriptError
from samba import generate_random_password
logger = logging.getLogger(os.path.basename(__file__))
logger.setLevel(logging.DEBUG)
@contextmanager
def use_api_as_principal(principal, keytab):
with ipautil.private_ccache() as ccache_file:
try:
old_principal = getattr(context, "principal", None)
name = gssapi.Name(principal, gssapi.NameType.kerberos_principal)
store = {"ccache": ccache_file, "client_keytab": keytab}
gssapi.Credentials(name=name, usage="initiate", store=store)
# Finalize API when TGT obtained using host keytab exists
if not api.isdone("finalize"):
api.finalize()
# Now we have a TGT, connect to IPA
try:
if api.Backend.rpcclient.isconnected():
api.Backend.rpcclient.disconnect()
api.Backend.rpcclient.connect()
yield
except gssapi.exceptions.GSSError as e:
raise Exception(
"Unable to bind to IPA server. Error initializing "
"principal %s in %s: %s" % (principal, keytab, str(e))
)
finally:
if api.Backend.rpcclient.isconnected():
api.Backend.rpcclient.disconnect()
setattr(context, "principal", old_principal)
def parse_options():
usage = "%prog [options]\n"
parser = OptionParser(usage=usage)
parser.add_option(
"--server",
dest="server",
help="FQDN of IPA server to connect to",
)
parser.add_option(
"--netbios-name",
dest="netbiosname",
help="NetBIOS name of this machine",
default=None,
)
parser.add_option(
"--no-homes",
dest="no_homes",
action="store_true",
default=False,
help="Do not add [homes] share to the generated Samba configuration",
)
parser.add_option(
"--no-nfs",
dest="no_nfs",
action="store_true",
default=False,
help="Do not allow NFS integration (SELinux booleans)",
)
parser.add_option(
"--force",
dest="force",
action="store_true",
default=False,
help="force installation by redoing all steps",
)
parser.add_option(
"--debug",
dest="debug",
action="store_true",
default=False,
help="print debugging information",
)
parser.add_option(
"-U",
"--unattended",
dest="unattended",
action="store_true",
default=False,
help="unattended installation never prompts the user",
)
parser.add_option(
"--uninstall",
dest="uninstall",
action="store_true",
default=False,
help="Revert configuration and remove SMB service",
)
options, args = parser.parse_args()
return options, args
domain_information_template = """
Domain name: {domain_name}
NetBIOS name: {netbios_name}
SID: {domain_sid}
ID range: {range_id_min} - {range_id_max}
"""
def pretty_print_domain_information(info):
result = []
for domain in info:
result.append(domain_information_template.format(**domain))
return "\n".join(result)
trust_keymap = {
"netbios_name": "ipantflatname",
"domain_sid": "ipantsecurityidentifier",
"domain_name": "cn",
}
def retrieve_domain_information(api):
# Pull down default domain configuration
# IPA master might be missing freeipa-server-trust-ad package
# or `ipa-adtrust-install` was never run. In such case return
# empty list to report an error
try:
tc_command = api.Command.trustconfig_show
except AttributeError:
return []
try:
result = tc_command()["result"]
except errors.PublicError:
return []
l_domain = dict()
for key in trust_keymap:
l_domain[key] = result.get(trust_keymap[key], [None])[0]
# Pull down ID range and other details of our domain
#
# TODO: make clear how to handle multiple ID ranges for ipa-local range
# In Samba only one range can belong to the same idmap domain,
# otherwise winbindd's _wbint_Sids2UnixIDs function will not be able
# to accept that a mapped Unix ID belongs to the specified domain
idrange_local = "{realm}_id_range".format(realm=api.env.realm)
result = api.Command.idrange_show(idrange_local)["result"]
l_domain["range_id_min"] = int(result["ipabaseid"][0])
l_domain["range_id_max"] = (
int(result["ipabaseid"][0]) + int(result["ipaidrangesize"][0]) - 1
)
domains = [l_domain]
# Retrieve list of trusted domains, if they exist
#
# We flatten the whole trust list because it should be non-overlapping
result = api.Command.trust_find()["result"]
for forest in result:
r = api.Command.trustdomain_find(forest["cn"][0], all=True, raw=True)[
"result"
]
# We don't need to process forest root info separately
# as trustdomain_find() returns it as well
for dom in r:
r_dom = dict()
for key in trust_keymap:
r_dom[key] = dom.get(trust_keymap[key], [None])[0]
r_idrange_name = "{realm}_id_range".format(
realm=r_dom["domain_name"].upper()
)
# TODO: support ipa-ad-trust-posix range as well
r_idrange = api.Command.idrange_show(r_idrange_name)["result"]
r_dom["range_id_min"] = int(r_idrange["ipabaseid"][0])
r_dom["range_id_max"] = (
int(r_idrange["ipabaseid"][0]) +
int(r_idrange["ipaidrangesize"][0]) - 1
)
domains.append(r_dom)
return domains
smb_conf_template = """
[global]
# Limit number of forked processes to avoid SMBLoris attack
max smbd processes = 1000
# Use dedicated Samba keytab. The key there must be synchronized
# with Samba tdb databases or nothing will work
dedicated keytab file = FILE:${samba_keytab}
kerberos method = dedicated keytab
# Set up logging per machine and Samba process
log file = /var/log/samba/log.%m
log level = 1
# We force 'member server' role to allow winbind automatically
# discover what is supported by the domain controller side
server role = member server
realm = ${realm}
netbios name = ${machine_name}
workgroup = ${netbios_name}
# Local writable range for IDs not coming from IPA or trusted domains
idmap config * : range = 0 - 0
idmap config * : backend = tdb
"""
idmap_conf_domain_snippet = """
idmap config ${netbios_name} : range = ${range_id_min} - ${range_id_max}
idmap config ${netbios_name} : backend = sss
"""
homes_conf_snippet = """
# Default homes share
[homes]
read only = no
"""
def configure_smb_conf(fstore, statestore, options, domains):
sub_dict = {
"samba_keytab": paths.SAMBA_KEYTAB,
"realm": api.env.realm,
"machine_name": options.netbiosname,
}
# First domain in the list is ours, pull our domain name from there
sub_dict["netbios_name"] = domains[0]["netbios_name"]
# Construct elements of smb.conf by pre-rendering idmap configuration
template = [smb_conf_template]
for dom in domains:
template.extend([ipautil.template_str(idmap_conf_domain_snippet, dom)])
# Add default homes share so that users can log into Samba
if not options.no_homes:
template.extend([homes_conf_snippet])
fstore.backup_file(paths.SMB_CONF)
with open(paths.SMB_CONF, "w") as f:
f.write(ipautil.template_str("\n".join(template), sub_dict))
tasks.restore_context(paths.SMB_CONF)
def generate_smb_machine_account(fstore, statestore, options, domain):
# Ideally, we should be using generate_random_machine_password()
# from samba but it uses munged UTF-16 which is not decodable
# by the code called from 'net changesecretpw -f'. Thus, we'd limit
# password to ASCII only.
return generate_random_password(128, 255)
def retrieve_service_principal(
fstore, statestore, options, domain, principal, password
):
# Use explicit encryption types. SMB service must have arcfour-hmac
# generated to allow domain member to authenticate to the domain controller
args = [
paths.IPA_GETKEYTAB,
"-p",
principal,
"-k",
paths.SAMBA_KEYTAB,
"-P",
"-e",
"aes128-cts-hmac-sha1-96,aes256-cts-hmac-sha1-96,arcfour-hmac",
]
try:
ipautil.run(args, stdin=password + "\n" + password, encoding="utf-8")
except ipautil.CalledProcessError as e:
logger.error(
"Cannot set machine account password at IPA DC. Error: %s",
e,
)
raise
# Once we fetched the keytab, we also need to set ipaNTHash attribute
# Use ipa-pwd-extop plugin to regenerate it from the Kerberos key
value = "ipaNTHash=MagicRegen"
try:
api.Command.service_mod(principal, addattr=value)
except errors.PublicError as e:
logger.error(
"Cannot update %s principal NT hash value due to an error: %s",
principal,
e,
)
raise
def populate_samba_databases(fstore, statestore, options, domain, password):
# First, set domain SID in Samba
args = [paths.NET, "setdomainsid", domain["domain_sid"]]
try:
ipautil.run(args)
except ipautil.CalledProcessError as e:
logger.error("Cannot set domain SID in Samba. Error: %s", e)
raise
# Next, make sure we can set machine account credentials
# the workaround with tdbtool is temporary until 'net' utility
# will not provide us a way to perform 'offline join' procedure
secrets_key = "SECRETS/MACHINE_LAST_CHANGE_TIME/{}".format(
domain["netbios_name"]
)
args = [paths.TDBTOOL, paths.SECRETS_TDB, "store", secrets_key, "2\\00"]
try:
ipautil.run(args)
except ipautil.CalledProcessError as e:
logger.error(
"Cannot prepare machine account creds in Samba. Error: %s", e,
)
raise
secrets_key = "SECRETS/MACHINE_PASSWORD/{}".format(domain["netbios_name"])
args = [paths.TDBTOOL, paths.SECRETS_TDB, "store", secrets_key, "2\\00"]
try:
ipautil.run(args)
except ipautil.CalledProcessError as e:
logger.error(
"Cannot prepare machine account creds in Samba. Error: %s", e,
)
raise
# Finally, set actual machine account's password
args = [paths.NET, "changesecretpw", "-f"]
try:
ipautil.run(args, stdin=password, encoding="utf-8")
except ipautil.CalledProcessError as e:
logger.error(
"Cannot set machine account creds in Samba. Error: %s", e,
)
raise
def configure_default_groupmap(fstore, statestore, options, domain):
args = [
paths.NET,
"groupmap",
"add",
"sid=S-1-5-32-546",
"unixgroup=nobody",
"type=builtin",
]
logger.info("Map BUILTIN\\Guests to a group 'nobody'")
try:
ipautil.run(args)
except ipautil.CalledProcessError as e:
if "already mapped to SID S-1-5-32-546" not in e.stdout:
logger.error(
'Cannot map BUILTIN\\Guests to a group "nobody". Error: %s',
e
)
raise
def set_selinux_booleans(booleans, statestore, backup=True):
def default_backup_func(name, value):
statestore.backup_state("selinux", name, value)
backup_func = default_backup_func if backup else None
try:
tasks.set_selinux_booleans(booleans, backup_func=backup_func)
except SetseboolError as e:
print("WARNING: " + str(e))
logger.info("WARNING: %s", e)
def harden_configuration(fstore, statestore, options, domain):
# Add default homes share so that users can log into Samba
if not options.no_homes:
set_selinux_booleans(
constants.SELINUX_BOOLEAN_SMBSERVICE["share_home_dirs"], statestore
)
# Allow Samba to access NFS-shared content
if not options.no_nfs:
set_selinux_booleans(
constants.SELINUX_BOOLEAN_SMBSERVICE["reshare_nfs_with_samba"],
statestore,
)
def uninstall(fstore, statestore, options):
# Shut down Samba services and disable them
smb = services.service("smb", api)
winbind = services.service("winbind", api)
for svc in (smb, winbind):
if svc.is_running():
svc.stop()
svc.disable()
# Restore the state of affected selinux booleans
boolean_states = {}
for usecase in constants.SELINUX_BOOLEAN_SMBSERVICE:
for name in usecase:
boolean_states[name] = statestore.restore_state("selinux", name)
if boolean_states:
set_selinux_booleans(boolean_states, statestore, backup=False)
# Remove samba's credentials cache
ipautil.remove_ccache(ccache_path=paths.KRB5CC_SAMBA)
# Remove samba's configuration file
ipautil.remove_file(paths.SMB_CONF)
fstore.restore_file(paths.SMB_CONF)
# Remove samba's persistent and temporary tdb files
tdb_files = [
tdb_file
for tdb_file in os.listdir(paths.SAMBA_DIR)
if tdb_file.endswith(".tdb")
]
for tdb_file in tdb_files:
ipautil.remove_file(tdb_file)
# Remove our keys from samba's keytab
if os.path.exists(paths.SAMBA_KEYTAB):
try:
ipautil.run(
[
paths.IPA_RMKEYTAB,
"--principal",
api.env.smb_princ,
"-k",
paths.SAMBA_KEYTAB,
]
)
except ipautil.CalledProcessError as e:
if e.returncode != 5:
logger.critical("Failed to remove old key for %s",
api.env.smb_princ)
with use_api_as_principal(api.env.host_princ, paths.KRB5_KEYTAB):
try:
api.Command.service_del(api.env.smb_princ)
except errors.VersionError as e:
print("This client is incompatible: " + str(e))
except errors.NotFound:
logger.debug("No SMB service principal exists, OK to proceed")
except errors.PublicError as e:
logger.error(
"Cannot connect to the server due to "
"a generic error: %s", e,
)
def run():
try:
check_client_configuration()
except ScriptError as e:
print(e.msg)
return e.rval
fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
statestore = sysrestore.StateFile(paths.IPA_CLIENT_SYSRESTORE)
options, _args = parse_options()
logfile = paths.IPACLIENTSAMBA_INSTALL_LOG
if options.uninstall:
logfile = paths.IPACLIENTSAMBA_UNINSTALL_LOG
standard_logging_setup(
logfile,
verbose=False,
debug=options.debug,
filemode="a",
console_format="%(message)s",
)
cfg = dict(
context="cli_installer",
confdir=paths.ETC_IPA,
in_server=False,
debug=options.debug,
verbose=0,
)
# Bootstrap API early so that env object is available
api.bootstrap(**cfg)
local_config = dict(
host_princ=str("host/%s@%s" % (api.env.host, api.env.realm)),
smb_princ=str("cifs/%s@%s" % (api.env.host, api.env.realm)),
)
# Until api.finalize() is called, we can add our own configuration
api.env._merge(**local_config)
if options.uninstall:
if statestore.has_state("domain_member"):
uninstall(fstore, statestore, options)
print(
"Samba configuration is reverted. "
"However, Samba databases were fully cleaned and "
"old configuration file will not be usable anymore."
)
else:
print("Samba domain member is not configured yet")
return 0
ca_cert_path = None
if os.path.exists(paths.IPA_CA_CRT):
ca_cert_path = paths.IPA_CA_CRT
if statestore.has_state("domain_member") and not options.force:
print("Samba domain member is already configured")
return CLIENT_ALREADY_CONFIGURED
if not os.path.exists(paths.SMBD):
print("Samba suite is not installed")
return CLIENT_NOT_CONFIGURED
autodiscover = False
ds = discovery.IPADiscovery()
if not options.server:
print("Searching for IPA server...")
ret = ds.search(ca_cert_path=ca_cert_path)
logger.debug("Executing DNS discovery")
if ret == discovery.NO_LDAP_SERVER:
logger.debug("Autodiscovery did not find LDAP server")
s = urlsplit(api.env.xmlrpc_uri)
server = [s.netloc]
logger.debug("Setting server to %s", s.netloc)
else:
autodiscover = True
if not ds.servers:
print(
"Autodiscovery was successful but didn't return a server"
)
return 1
logger.debug(
"Autodiscovery success, possible servers %s",
",".join(ds.servers),
)
server = ds.servers[0]
else:
server = options.server
logger.debug("Verifying that %s is an IPA server", server)
ldapret = ds.ipacheckldap(server, api.env.realm, ca_cert_path)
if ldapret[0] == discovery.NO_ACCESS_TO_LDAP:
print("Anonymous access to the LDAP server is disabled.")
print("Proceeding without strict verification.")
print(
"Note: This is not an error if anonymous access has been "
"explicitly restricted."
)
elif ldapret[0] == discovery.NO_TLS_LDAP:
logger.warning("Unencrypted access to LDAP is not supported.")
elif ldapret[0] != 0:
print("Unable to confirm that %s is an IPA server" % server)
return 1
if not autodiscover:
print("IPA server: %s" % server)
logger.debug("Using fixed server %s", server)
else:
print("IPA server: DNS discovery")
logger.info("Configured to use DNS discovery")
if api.env.host == server:
logger.error(
"Cannot run on IPA master. "
"Cannot configure Samba as a domain member on a domain "
"controller. Please use ipa-adtrust-install for that!"
)
return 1
if not options.netbiosname:
options.netbiosname = DNSName.from_text(api.env.host)[0].decode()
options.netbiosname = options.netbiosname.upper()
with use_api_as_principal(api.env.host_princ, paths.KRB5_KEYTAB):
try:
# Try to access 'service_add_smb' command, if it throws
# AttributeError exception, the IPA server doesn't support
# setting up Samba as a domain member.
service_add_smb = api.Command.service_add_smb
# Now try to see if SMB principal already exists
api.Command.service_show(api.env.smb_princ)
# If no exception was raised, the object exists.
# We cannot continue because we would break existing configuration
print(
"WARNING: SMB service principal %s already exists. "
"Please remove it before proceeding." % (api.env.smb_princ)
)
if not options.force:
return 1
# For --force, we should then delete cifs/.. service object
api.Command.service_del(api.env.smb_princ)
except AttributeError:
logger.error(
"Chosen IPA master %s does not have support to"
"set up Samba domain members", server,
)
return 1
except errors.VersionError as e:
print("This client is incompatible: " + str(e))
return 1
except errors.NotFound:
logger.debug("No SMB service principal exists, OK to proceed")
except errors.PublicError as e:
logger.error(
"Cannot connect to the server due to "
"a generic error: %s", e,
)
return 1
# At this point we have proper setup:
# - we connected to IPA API end-point as a host principal
# - no cifs/... principal exists so we can create it
print("Chosen IPA master: %s" % server)
print("SMB principal to be created: %s" % api.env.smb_princ)
print("NetBIOS name to be used: %s" % options.netbiosname)
logger.info("Chosen IPA master: %s", server)
logger.info("SMB principal to be created: %s", api.env.smb_princ)
logger.info("NetBIOS name to be used: %s", options.netbiosname)
# 1. Pull down ID range and other details of known domains
domains = retrieve_domain_information(api)
if len(domains) == 0:
# logger.error() produces both log file and stderr output
logger.error("No configured trust controller detected "
"on IPA masters. Use ipa-adtrust-install on an IPA "
"master to configure trust controller role.")
return 1
str_info = pretty_print_domain_information(domains)
logger.info("Discovered domains to use:\n%s", str_info)
print("Discovered domains to use:\n%s" % str_info)
if not options.unattended and not ipautil.user_input(
"Continue to configure the system with these values?", False
):
print("Installation aborted")
return 1
# 2. Create SMB service principal, if we are here, the command exists
if (
not statestore.get_state("domain_member", "service.principal") or
options.force
):
service_add_smb(api.env.host, options.netbiosname)
statestore.backup_state(
"domain_member", "service.principal", "configured"
)
# 3. Generate machine account password for reuse
password = generate_smb_machine_account(
fstore, statestore, options, domains[0]
)
# 4. Now that we have all domains retrieved, we can generate smb.conf
if (
not statestore.get_state("domain_member", "smb.conf") or
options.force
):
configure_smb_conf(fstore, statestore, options, domains)
statestore.backup_state("domain_member", "smb.conf", "configured")
# 5. Create SMB service
if statestore.get_state("domain_member",
"service.principal") == "configured":
retrieve_service_principal(
fstore, statestore, options, domains[0],
api.env.smb_princ, password
)
statestore.backup_state(
"domain_member", "service.principal", "configured"
)
# 6. Configure databases to contain proper details
if not statestore.get_state("domain_member", "tdb") or options.force:
populate_samba_databases(
fstore, statestore, options, domains[0], password
)
statestore.backup_state("domain_member", "tdb", "configured")
# 7. Configure default group mapping
if (
not statestore.get_state("domain_member", "groupmap") or
options.force
):
configure_default_groupmap(fstore, statestore, options, domains[0])
statestore.backup_state("domain_member", "groupmap", "configured")
# 8. Enable SELinux policies
if (
not statestore.get_state("domain_member", "hardening") or
options.force
):
harden_configuration(fstore, statestore, options, domains[0])
statestore.backup_state("domain_member", "hardening", "configured")
# 9. Finally, store the state of upgrade
statestore.backup_state("domain_member", "configured", True)
# Suggest service start only after validating smb.conf
print(
"Samba domain member is configured. "
"Please check configuration at %s and "
"start smb and winbind services" % paths.SMB_CONF
)
logger.info(
"Samba domain member is configured. "
"Please check configuration at %s and "
"start smb and winbind services",
paths.SMB_CONF,
)
return 0

View File

@@ -406,6 +406,8 @@ class BasePathNamespace:
SSHD = '/usr/sbin/sshd'
SSSCTL = '/usr/sbin/sssctl'
LIBARCH = "64"
TDBTOOL = '/usr/bin/tdbtool'
SECRETS_TDB = '/var/lib/samba/private/secrets.tdb'
def check_paths(self):
"""Check paths for missing files

View File

@@ -740,7 +740,7 @@ class update_host_cifs_keytabs(Updater):
def extract_key_refs(self, keytab):
host_princ = self.host_princ_template.format(
master=self.api.host, realm=self.api.realm)
master=self.api.env.host, realm=self.api.env.realm)
result = ipautil.run([paths.KLIST, "-etK", "-k", keytab],
capture_output=True, raiseonerr=False,
nolog_output=True)

View File

@@ -0,0 +1,152 @@
#
# Copyright (C) 2019 FreeIPA Contributors see COPYING for license
#
"""This module provides tests for SMB-related features like
configuring Samba file server and mounting SMB file system
"""
from __future__ import absolute_import
import time
import os
from ipatests.test_integration.base import IntegrationTest
from ipatests.pytest_ipa.integration import tasks
from ipaplatform.paths import paths
# give some time for units to stabilize
# otherwise we get transient errors
WAIT_AFTER_INSTALL = 5
WAIT_AFTER_UNINSTALL = WAIT_AFTER_INSTALL
user_password = "Secret123"
users = {
"athena": "p",
"euripides": "s"
}
class TestSMB(IntegrationTest):
num_replicas = 1
num_clients = 1
@classmethod
def fix_resolv_conf(cls, client, server):
contents = client.get_file_contents(paths.RESOLV_CONF,
encoding='utf-8')
nameserver = 'nameserver %s\n' % server.ip
if not contents.startswith(nameserver):
contents = nameserver + contents.replace(nameserver, '')
client.run_command([
'/usr/bin/cp', paths.RESOLV_CONF,
'%s.sav' % paths.RESOLV_CONF
])
client.put_file_contents(paths.RESOLV_CONF, contents)
@classmethod
def restore_resolv_conf(cls, client):
client.run_command([
'/usr/bin/cp',
'%s.sav' % paths.RESOLV_CONF,
paths.RESOLV_CONF
])
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, setup_dns=True)
tasks.install_adtrust(cls.master)
for client in cls.replicas + cls.clients:
cls.fix_resolv_conf(client, cls.master)
tasks.install_client(cls.master, client,
extra_args=['--mkhomedir'])
cls.replicas[0].collect_log('/var/log/samba/')
cls.master.collect_log('/var/log/samba/')
@classmethod
def uninstall(cls, mh):
for client in cls.clients + cls.replicas:
tasks.uninstall_client(client)
cls.restore_resolv_conf(client)
tasks.uninstall_master(cls.master)
def test_prepare_users(self):
smbsrv = self.replicas[0]
temp_pass = "t3mp!p4ss"
user_kinit = "%s\n%s\n%s\n" % (temp_pass,
user_password, user_password)
user_addpass = "%s\n%s\n" % (temp_pass, temp_pass)
for user in users:
self.master.run_command([
"ipa", "user-add",
"%s" % user, "--first", "%s" % user,
"--last", "%s" % users[user],
'--password'], stdin_text=user_addpass
)
self.master.run_command(['kdestroy', '-A'])
self.master.run_command(
['kinit', user], stdin_text=user_kinit
)
# Force creation of home directories on the SMB server
smbsrv.run_command(['su', '-l', '-', user, '-c', 'stat .'])
# Switch back to admin
self.master.run_command(['kdestroy', '-A'])
tasks.kinit_admin(self.master)
def test_install_samba(self):
smbsrv = self.replicas[0]
smbsrv.run_command([
"ipa-client-samba", "-U"
])
smbsrv.run_command([
"systemctl", "enable", "--now", "smb", "winbind"
])
time.sleep(WAIT_AFTER_INSTALL)
smbsrv.run_command(['smbstatus'])
def test_access_homes_smbclient(self):
"""Access user home directory via smb3.ko and smbclient
Test checks that both kernel SMB3 driver and userspace
smbclient utility work against IPA-enrolled Samba server
"""
smbsrv = self.replicas[0]
smbclt = self.clients[0]
remote_uri = '//{smbsrv}/homes'.format(smbsrv=smbsrv.hostname)
for user in users:
smbclt.run_command(['kinit', user], stdin_text=user_password)
mntpoint = '/mnt/{user}'.format(user=user)
userfile = '{user}.dat'.format(user=user)
smbclt.run_command(['mkdir', '-p', mntpoint])
smbclt.run_command(['mount', '-t', 'cifs',
remote_uri, mntpoint, '-o',
'user={user},sec=krb5i'.format(user=user)])
smbclt.run_command(['dd', 'count=1024', 'bs=1K', 'if=/dev/zero',
'of={path}'.format(
path=os.path.join(mntpoint, userfile))])
smbclt.run_command(['findmnt', '-t', 'cifs'])
smbclt.run_command(['ls', '-laZ',
os.path.join(mntpoint, userfile)])
smbsrv.run_command(['smbstatus'])
smbclt.run_command(['umount', '-a', '-t', 'cifs'])
smbclt.run_command(['smbclient', '-k', remote_uri,
'-c', 'allinfo {path}'.format(path=userfile)])
smbclt.run_command(['kdestroy', '-A'])
def test_uninstall_samba(self):
for user in users:
self.master.run_command(['ipa', 'user-del', user])
smbsrv = self.replicas[0]
smbsrv.run_command(['ipa-client-samba', '--uninstall', '-U'])