Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamically configure nsswitch.conf based on DS #13635

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/freenas/etc/nsswitch.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
# nsswitch.conf(5) - name service switch configuration file
#

group: files winbind sss
group: files
hosts: files dns
networks: files
passwd: files winbind sss
passwd: files
shells: files
services: files
protocols: files
rpc: files
sudoers: files
netgroup: files sss
netgroup: files
43 changes: 43 additions & 0 deletions src/middlewared/middlewared/etc_files/nsswitch.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<%
from middlewared.utils.nss.nss_common import NssModule
from middlewared.plugins.directoryservices import DSType, DSStatus

ds_status = middleware.call_sync('directoryservices.status')
netgroup = ''
ds = ''

# An unhealthy directory service cause significant server-wide
# issues when it is present in the nsswitch in some edge cases.

# It should only be added if we are reasonably sure we're healthy
match ds_status['directory_service']:
case DSType.LDAP.value:
if ds_status['state'] in (DSStatus.HEALTHY.value, DSStatus.JOINING.value):
ds = NssModule.SSS.name.lower()
netgroup = NssModule.SSS.name.lower()
case DSType.AD.value:
if ds_status['state'] in (DSStatus.HEALTHY.value, DSStatus.JOINING.value):
ds = NssModule.WINBIND.name.lower()
case None:
pass
case _:
# This should never happen and indicates a bug
middleware.logger.error(
'%s: unknown directory service. Please file a bug report.'
ds_status['directory_service']
)
%>\
#
# nsswitch.conf(5) - name service switch configuration file
#

group: files ${ds}
hosts: files dns
networks: files
passwd: files ${ds}
shells: files
services: files
protocols: files
rpc: files
sudoers: files
netgroup: files ${netgroup}
2 changes: 2 additions & 0 deletions src/middlewared/middlewared/plugins/activedirectory.py
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,7 @@ async def __stop(self, job, config):
await self.middleware.call('service.stop', 'cifs')
job.set_progress(40, 'Reconfiguring pam and nss.')
await self.middleware.call('etc.generate', 'pam')
await self.middleware.call('etc.generate', 'nss')
await self.set_state(DSStatus['DISABLED'].name)
job.set_progress(60, 'clearing caches.')
await self.middleware.call('service.stop', 'dscache')
Expand Down Expand Up @@ -1262,6 +1263,7 @@ async def leave(self, job, data):

job.set_progress(60, 'Regenerating configuration.')
await self.middleware.call('etc.generate', 'pam')
await self.middleware.call('etc.generate', 'nss')
await self.synchronize()
await self.middleware.call('idmap.synchronize')

Expand Down
13 changes: 5 additions & 8 deletions src/middlewared/middlewared/plugins/activedirectory_/health.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,31 +160,28 @@ async def started(self):
verrors = ValidationErrors()
config = await self.middleware.call('activedirectory.config')
if not config['enable']:
await self.middleware.call('directoryservices.set_state', {'activedirectory': DSStatus['DISABLED'].name})
await self.middleware.call('directoryservices.set_state', {'activedirectory': DSStatus.DISABLED.name})
return False

"""
Initialize state to "JOINING" until after booted.
"""
if not await self.middleware.call('system.ready'):
await self.middleware.call('directoryservices.set_state', {'activedirectory': DSStatus['JOINING'].name})
await self.middleware.call('directoryservices.set_state', {'activedirectory': DSStatus.JOINING.name})
return True

await self.middleware.call('activedirectory.common_validate', config, config, verrors)

try:
verrors.check()
except ValidationErrors as ve:
await self.middleware.call(
'datastore.update', self._config.datastore, config['id'],
{"enable": False}, {'prefix': 'ad_'}
)
raise CallError('Automatically disabling ActiveDirectory service due to invalid configuration',
await self.middleware.call('directoryservices.set_state', {'activedirectory': DSStatus.FAULTED.name})
raise CallError(' disabling ActiveDirectory service due to invalid configuration',
errno.EINVAL, ', '.join([err[1] for err in ve]))

"""
Verify winbindd netlogon connection.
"""
await self.middleware.call('activedirectory.winbind_status')
await self.middleware.call('directoryservices.set_state', {'activedirectory': DSStatus['HEALTHY'].name})
await self.middleware.call('directoryservices.set_state', {'activedirectory': DSStatus.HEALTHY.name})
return True
60 changes: 60 additions & 0 deletions src/middlewared/middlewared/plugins/directoryservices.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,66 @@ async def get_state(self):
await self.middleware.call('cache.put', 'DS_STATE', ds_state, 60)
return ds_state

@no_authz_required
@accepts()
@returns(Dict(
'directoryservices_status',
Str('directory_service', enum=[x.value.upper() for x in DSType], null=True),
Ref('directoryservice_state', 'state')
))
async def status(self):
ds_state = await self.get_state()
enabled_service = None
state = DSStatus.DISABLED.name
for srv in DSType:
if ds_state[srv.value] != DSStatus.DISABLED.name
enabled_service = srv.value.upper()
state = ds_state[srv.value]
break

return {'service': enabled_service, 'state': state}

@private
def reset_nss(self, new):
target_ds = None

for ds, status in new.itemes():
if status != (DSStatus.DISABLED.name):
target_ds = ds
break

# avoid reading existing nsswitch unless absolutely necessary
if target_ds is None or status not in (DSStatus.HEALTHY.name, DSStatus.FAULTED.name):
return

with open('/etc/nsswitch.conf', 'r') as f:
nss_contents = f.read()

match target_ds:
case DSStatus.AD.value:
nss_module = 'winbind'
case DSStatus.LDAP.value:
nss_module = 'sss'
case _:
self.logger.error('%s: unknown directory service', target_ds)
# We don't want this to be fatal because it may trigger production
# outage
return

match status:
case DSStatus.HEALTHY.name:
must_refresh = nss_module not in nss_contents
case DSStatus.FAULTED.name:
must_refresh = nss_module in nss_contents
case _:
self.logger.error('%s: unexpected status', status)
# We don't want this to be fatal because it may trigger production
# outage
return

if must_refresh:
self.middleware.call_sync('etc.generate', 'nss')

@private
async def set_state(self, new):
ds_state = {
Expand Down
3 changes: 3 additions & 0 deletions src/middlewared/middlewared/plugins/etc.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,9 @@ class EtcService(Service):
'nscd': [
{'type': 'mako', 'path': 'nscd.conf'},
],
'nss': [
{'type': 'mako', 'path': 'nsswitch.conf'},
],
'wsd': [
{'type': 'mako', 'path': 'local/wsdd.conf', 'checkpoint': 'post_init'},
],
Expand Down
24 changes: 9 additions & 15 deletions src/middlewared/middlewared/plugins/ldap.py
Original file line number Diff line number Diff line change
Expand Up @@ -902,18 +902,15 @@ async def started(self):
try:
verrors.check()
except ValidationErrors:
await self.middleware.call(
'datastore.update', self._config.datastore, 1,
{'enable': False}, {'prefix': 'ldap_'}
)
await self.set_state(DSStatus.FAULTED)
raise CallError('Automatically disabling LDAP service due to invalid configuration.',
errno.EINVAL)

"""
Initialize state to "JOINING" until after booted.
"""
if not await self.middleware.call('system.ready'):
await self.set_state(DSStatus['JOINING'])
await self.set_state(DSStatus.JOINING)
return True

try:
Expand All @@ -927,7 +924,7 @@ async def started(self):
await self.middleware.call('etc.generate', 'ldap')
await self.middleware.call('service.start', 'sssd')

await self.set_state(DSStatus['HEALTHY'])
await self.set_state(DSStatus.HEALTHY)
return True

@private
Expand Down Expand Up @@ -971,18 +968,17 @@ async def __start(self, job):
that the service begins in a clean state.
"""
ldap_state = await self.middleware.call('ldap.get_state')
if ldap_state in ['LEAVING', 'JOINING']:
raise CallError(f'LDAP state is [{ldap_state}]. Please wait until directory service operation completes.', errno.EBUSY)

job.set_progress(0, 'Preparing to configure LDAP directory service.')
ldap = await self.config()

await self.set_state(DSStatus.JOINING)
if ldap['kerberos_realm']:
job.set_progress(5, 'Starting kerberos')
await self.middleware.call('kerberos.start')

job.set_progress(15, 'Generating configuration files')
await self.middleware.call('etc.generate', 'rc')
await self.middleware.call('etc.generate', 'nss')
await self.middleware.call('etc.generate', 'ldap')
await self.middleware.call('etc.generate', 'pam')

Expand All @@ -1006,7 +1002,7 @@ async def __start(self, job):
else:
await self.middleware.call('alert.oneshot_delete', 'DeprecatedServiceConfiguration', LDAP_DEPRECATED)

await self.set_state(DSStatus['HEALTHY'])
await self.set_state(DSStatus.HEALTHY)
job.set_progress(80, 'Restarting dependent services')
cache_job = await self.middleware.call('dscache.refresh')
await cache_job.wait()
Expand All @@ -1017,14 +1013,12 @@ async def __start(self, job):
@private
async def __stop(self, job):
ldap_state = await self.middleware.call('ldap.get_state')
if ldap_state in ['LEAVING', 'JOINING']:
raise CallError(f'LDAP state is [{ldap_state}]. Please wait until directory service operation completes.', errno.EBUSY)

job.set_progress(0, 'Preparing to stop LDAP directory service.')
await self.middleware.call('alert.oneshot_delete', 'DeprecatedServiceConfiguration', LDAP_DEPRECATED)
await self.set_state(DSStatus['LEAVING'])
await self.set_state(DSStatus.LEAVING)
job.set_progress(10, 'Rewriting configuration files.')
await self.middleware.call('etc.generate', 'rc')
await self.middleware.call('etc.generate', 'nss')
await self.middleware.call('etc.generate', 'ldap')
await self.middleware.call('etc.generate', 'pam')
await self.synchronize()
Expand All @@ -1042,7 +1036,7 @@ async def __stop(self, job):

job.set_progress(80, 'Stopping sssd service.')
await self.middleware.call('service.stop', 'sssd')
await self.set_state(DSStatus['DISABLED'])
await self.set_state(DSStatus.DISABLED)
job.set_progress(100, 'LDAP directory service stopped.')

@private
Expand Down
Loading