From c6ef3ad538c49db9bac2a04eea22b228006c2ed8 Mon Sep 17 00:00:00 2001 From: Andrew Walker Date: Tue, 6 Aug 2024 10:49:51 -0700 Subject: [PATCH 1/4] Add auditing for directory services changes Generate audit trail for kerberos, AD, and LDAP changes. --- .../middlewared/plugins/activedirectory.py | 4 +- .../middlewared/plugins/kerberos.py | 37 +++++++++++++------ src/middlewared/middlewared/plugins/ldap.py | 2 +- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/middlewared/middlewared/plugins/activedirectory.py b/src/middlewared/middlewared/plugins/activedirectory.py index 63bf10d1248fc..19d21cb4ff641 100644 --- a/src/middlewared/middlewared/plugins/activedirectory.py +++ b/src/middlewared/middlewared/plugins/activedirectory.py @@ -257,7 +257,7 @@ async def common_validate(self, new, old, verrors): 'configure DNS A and AAAA records as needed for their domain.' ) - @accepts(Ref('activedirectory_update')) + @accepts(Ref('activedirectory_update'), audit='Active directory configuration update') @returns(Ref('activedirectory_update')) @job(lock="AD_start_stop") async def do_update(self, job, data): @@ -776,7 +776,7 @@ async def lookup_dc(self, domain=None): out = json.loads(lookup.stdout.decode()) return out - @accepts(Ref('kerberos_username_password'), roles=['DIRECTORY_SERVICE_WRITE']) + @accepts(Ref('kerberos_username_password'), roles=['DIRECTORY_SERVICE_WRITE'], audit='Active directory leave') @returns() @job(lock="AD_start_stop") async def leave(self, job, data): diff --git a/src/middlewared/middlewared/plugins/kerberos.py b/src/middlewared/middlewared/plugins/kerberos.py index de383dc4ca8ac..ae2b3657500c1 100644 --- a/src/middlewared/middlewared/plugins/kerberos.py +++ b/src/middlewared/middlewared/plugins/kerberos.py @@ -59,7 +59,7 @@ class Config: Str('appdefaults_aux', max_length=None), Str('libdefaults_aux', max_length=None), update=True - )) + ), audit='Kerberos config update') async def do_update(self, data): """ `appdefaults_aux` add parameters to "appdefaults" section of the krb5.conf file. @@ -641,7 +641,9 @@ async def kerberos_compress(self, data): List('admin_server'), List('kpasswd_server'), register=True - ) + ), + audit='Kerberos realm create:', + audit_extended=lambda data: data['realm'] ) async def do_create(self, data): """ @@ -677,15 +679,18 @@ async def do_create(self, data): "kerberos_realm_create", "kerberos_realm_update", ("attr", {"update": True}) - ) + ), + audit='Kerberos realm update:', + audit_callback=True ) - async def do_update(self, id_, data): + async def do_update(self, audit_callback, id_, data): """ Update a kerberos realm by id. This will be automatically populated during the domain join process in an Active Directory environment. Kerberos realm names are case-sensitive, but convention is to only use upper-case. """ old = await self.get_instance(id_) + audit_callback(old['realm']) new = old.copy() new.update(data) @@ -698,11 +703,13 @@ async def do_update(self, id_, data): await self.middleware.call('etc.generate', 'kerberos') return await self.get_instance(id_) - @accepts(Int('id')) - async def do_delete(self, id_): + @accepts(Int('id'), audit='Kerberos realm delete:', audit_callback=True) + async def do_delete(self, audit_callback, id_): """ Delete a kerberos realm by ID. """ + realm_name = (await self.get_instance(id_))['realm'] + audit_callback(realm_name) await self.middleware.call('datastore.delete', self._config.datastore, id_) await self.middleware.call('etc.generate', 'kerberos') @@ -743,7 +750,9 @@ class Config: Str('file', max_length=None), Str('name'), register=True - ) + ), + audit='Kerberos keytab create:', + audit_extended=lambda data: data['name'] ) async def do_create(self, data): """ @@ -772,13 +781,16 @@ async def do_create(self, data): Patch( 'kerberos_keytab_create', 'kerberos_keytab_update', - ) + ), + audit='Kerberos keytab update:', + audit_callback=True ) - async def do_update(self, id_, data): + async def do_update(self, audit_callback, id_, data): """ Update kerberos keytab by id. """ old = await self.get_instance(id_) + audit_callback(old['name']) new = old.copy() new.update(data) @@ -796,13 +808,14 @@ async def do_update(self, id_, data): return await self.get_instance(id_) - @accepts(Int('id')) - async def do_delete(self, id_): + @accepts(Int('id'), audit='Kerberos keytab delete:', audit_callback=True) + async def do_delete(self, audit_callback, id_): """ Delete kerberos keytab by id, and force regeneration of system keytab. """ kt = await self.get_instance(id_) + audit_callback(kt['name']) if kt['name'] == 'AD_MACHINE_ACCOUNT': ad_config = await self.middleware.call('activedirectory.config') if ad_config['enable']: @@ -830,7 +843,7 @@ async def do_delete(self, id_): @accepts(Dict( 'keytab_data', Str('name', required=True), - )) + ), audit='Kerberos keytab upload:', audit_extended=lambda name, name) @returns(Ref('kerberos_keytab_entry')) @job(lock='upload_keytab', pipes=['input'], check_pipes=True) async def upload_keytab(self, job, data): diff --git a/src/middlewared/middlewared/plugins/ldap.py b/src/middlewared/middlewared/plugins/ldap.py index 654fa60eeb77a..a24a15f18c7d8 100644 --- a/src/middlewared/middlewared/plugins/ldap.py +++ b/src/middlewared/middlewared/plugins/ldap.py @@ -566,7 +566,7 @@ async def autodetect_ldap_settings(self, data): return constants.SERVER_TYPE_GENERIC - @accepts(Ref('ldap_update')) + @accepts(Ref('ldap_update'), audit='LDAP configuration update') @job(lock="ldap_start_stop") async def do_update(self, job, data): """ From ac4bc7da4489ab3a896529e9ca99e0d2145106eb Mon Sep 17 00:00:00 2001 From: Andrew Walker Date: Tue, 6 Aug 2024 10:52:09 -0700 Subject: [PATCH 2/4] Fix audit message --- src/middlewared/middlewared/plugins/kerberos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middlewared/middlewared/plugins/kerberos.py b/src/middlewared/middlewared/plugins/kerberos.py index ae2b3657500c1..e4c634081fa06 100644 --- a/src/middlewared/middlewared/plugins/kerberos.py +++ b/src/middlewared/middlewared/plugins/kerberos.py @@ -59,7 +59,7 @@ class Config: Str('appdefaults_aux', max_length=None), Str('libdefaults_aux', max_length=None), update=True - ), audit='Kerberos config update') + ), audit='Kerberos configuration update') async def do_update(self, data): """ `appdefaults_aux` add parameters to "appdefaults" section of the krb5.conf file. From b02945c1d33ba52ee3e4e25de301c5ad29704d83 Mon Sep 17 00:00:00 2001 From: Andrew Walker Date: Tue, 6 Aug 2024 11:12:58 -0700 Subject: [PATCH 3/4] Fix --- src/middlewared/middlewared/plugins/kerberos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middlewared/middlewared/plugins/kerberos.py b/src/middlewared/middlewared/plugins/kerberos.py index e4c634081fa06..ff86b17747542 100644 --- a/src/middlewared/middlewared/plugins/kerberos.py +++ b/src/middlewared/middlewared/plugins/kerberos.py @@ -843,7 +843,7 @@ async def do_delete(self, audit_callback, id_): @accepts(Dict( 'keytab_data', Str('name', required=True), - ), audit='Kerberos keytab upload:', audit_extended=lambda name, name) + ), audit='Kerberos keytab upload:', audit_extended=lambda name: name) @returns(Ref('kerberos_keytab_entry')) @job(lock='upload_keytab', pipes=['input'], check_pipes=True) async def upload_keytab(self, job, data): From 96f7e4901a7561ef926a885decf286b737498fbc Mon Sep 17 00:00:00 2001 From: Andrew Walker Date: Wed, 7 Aug 2024 11:38:44 -0700 Subject: [PATCH 4/4] Flag keytab data as private --- src/middlewared/middlewared/plugins/kerberos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middlewared/middlewared/plugins/kerberos.py b/src/middlewared/middlewared/plugins/kerberos.py index ff86b17747542..fcbd170857ebf 100644 --- a/src/middlewared/middlewared/plugins/kerberos.py +++ b/src/middlewared/middlewared/plugins/kerberos.py @@ -747,7 +747,7 @@ class Config: @accepts( Dict( 'kerberos_keytab_create', - Str('file', max_length=None), + Str('file', max_length=None, private=True), Str('name'), register=True ),