diff --git a/sysinv/sysinv/sysinv/sysinv/agent/manager.py b/sysinv/sysinv/sysinv/sysinv/agent/manager.py index 04b25ddcd4..95ccad6c81 100644 --- a/sysinv/sysinv/sysinv/sysinv/agent/manager.py +++ b/sysinv/sysinv/sysinv/sysinv/agent/manager.py @@ -93,7 +93,8 @@ agent_opts = [ audit_intervals_opts = [ cfg.IntOpt('default', default=60), cfg.IntOpt('inventory_audit', default=60), - cfg.IntOpt('lldp_audit', default=300) + cfg.IntOpt('lldp_audit', default=300), + cfg.IntOpt('security_audit', default=900), ] dpdk_opts = [ @@ -1313,6 +1314,51 @@ class AgentManager(service.PeriodicService): else: self._lldp_enable_and_report(icontext, rpcapi, self._ihost_uuid) + @periodic_task.periodic_task(spacing=CONF.agent_periodic_task_intervals.security_audit) + def _security_audit(self, context): + if not self._ihost_uuid: + return + + LOG.debug("Sysinv Agent Security Audit running.") + + # get sysadmin password locally + with open("/etc/shadow", "r") as f: + user_attrs = [] + lines = f.readlines() + for line in lines: + if "sysadmin" in line: + user_attrs = line.split(":") + break + if not user_attrs: + LOG.warn("No shadow entry found for 'sysadmin' user.") + return + + icontext = mycontext.get_admin_context() + rpcapi = conductor_rpcapi.ConductorAPI(topic=conductor_rpcapi.MANAGER_TOPIC) + + # get user information from the database + try: + iuser = rpcapi.get_iuser(icontext) + except RemoteError as e: + # ignore because active controller is not yet upgraded, + # so it's current load may not implement this RPC call + if "AttributeError" in str(e): + LOG.warn("Skip security audit. Upgrade in progress.") + else: + LOG.error("Failed to get user configuration via RPC.") + return + if not iuser.passwd_hash: + LOG.warn("No password configured for 'sysadmin' in the database.") + return + + # compare sysadmin password hash with the value retrieved from the + # database and trigger user config manifest reapply if values differ + if iuser.passwd_hash != user_attrs[1]: + LOG.info("Configuration mismatch for 'sysadmin' user, attempting to reconfigure...") + rpcapi.update_user_config(icontext, [self._ihost_uuid]) + else: + LOG.debug("No divergence found within 'sysadmin' user configuration.") + @utils.synchronized(LOCK_AGENT_ACTION, external=False) def agent_audit(self, context, host_uuid, force_updates, cinder_device=None): # perform inventory audit diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py index 5516c6c52c..0e486a54a1 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/manager.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/manager.py @@ -5797,6 +5797,16 @@ class ConductorManager(service.PeriodicService): system = self.dbapi.isystem_get_one() return system + def get_iuser(self, context): + """Return iuser object + + This method returns an iuser object + + :returns: iuser object, including all field + """ + user = self.dbapi.iuser_get_one() + return user + def get_ihost_by_macs(self, context, ihost_macs): """Finds ihost db entry based upon the mac list @@ -8144,7 +8154,7 @@ class ConductorManager(service.PeriodicService): cutils.touch( self._get_oam_runtime_apply_file(standby_controller=True)) - def update_user_config(self, context): + def update_user_config(self, context, hosts_uuid=None): """Update the user configuration""" LOG.info("update_user_config") @@ -8157,6 +8167,9 @@ class ConductorManager(service.PeriodicService): "personalities": personalities, "classes": ['platform::users::runtime'] } + if hosts_uuid: + config_dict.update({"hosts_uuid": hosts_uuid}) + self._config_apply_runtime_manifest(context, config_uuid, config_dict) def update_controller_rollback_flag(self, context): diff --git a/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py b/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py index 878581ebf3..55612816d2 100644 --- a/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py +++ b/sysinv/sysinv/sysinv/sysinv/conductor/rpcapi.py @@ -190,6 +190,15 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy): """ return self.call(context, self.make_msg('get_isystem',)) + def get_iuser(self, context): + """Return iuser object + + This method returns an iuser object + + :returns: iuser object, including all field + """ + return self.call(context, self.make_msg('get_iuser',)) + def get_ihost_by_macs(self, context, ihost_macs): """Finds ihost db entry based upon the mac list @@ -752,12 +761,16 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy): """ return self.call(context, self.make_msg('update_oam_config')) - def update_user_config(self, context): + def update_user_config(self, context, hosts_uuid=None): """Synchronously, have the conductor update the user configuration. :param context: request context. + :param hosts_uuid: list of host_uuids to run user puppet manifest """ - return self.call(context, self.make_msg('update_user_config')) + return self.call( + context, + self.make_msg('update_user_config', hosts_uuid=hosts_uuid) + ) def update_controller_rollback_flag(self, context): """Synchronously, have a conductor update controller rollback flag