%PDF- %PDF-
Direktori : /lib/python2.7/site-packages/salt/beacons/ |
Current File : //lib/python2.7/site-packages/salt/beacons/btmp.py |
# -*- coding: utf-8 -*- ''' Beacon to fire events at failed login of users .. versionadded:: 2015.5.0 Example Configuration ===================== .. code-block:: yaml # Fire events on all failed logins beacons: btmp: [] # Matching on user name, using a default time range beacons: btmp: - users: gareth: - defaults: time_range: start: '8am' end: '4pm' # Matching on user name, overriding the default time range beacons: btmp: - users: gareth: time_range: start: '8am' end: '4pm' - defaults: time_range: start: '8am' end: '4pm' # Matching on group name, overriding the default time range beacons: btmp: - groups: users: time_range: start: '8am' end: '4pm' - defaults: time_range: start: '8am' end: '4pm' Use Case: Posting Failed Login Events to Slack ============================================== This can be done using the following reactor SLS: .. code-block:: jinja report-wtmp: runner.salt.cmd: - args: - fun: slack.post_message - channel: mychannel # Slack channel - from_name: someuser # Slack user - message: "Failed login from `{{ data.get('user', '') or 'unknown user' }}` on `{{ data['id'] }}`" Match the event like so in the master config file: .. code-block:: yaml reactor: - 'salt/beacon/*/btmp/': - salt://reactor/btmp.sls .. note:: This approach uses the :py:mod:`slack execution module <salt.modules.slack_notify>` directly on the master, and therefore requires that the master has a slack API key in its configuration: .. code-block:: yaml slack: api_key: xoxb-XXXXXXXXXXXX-XXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXX See the :py:mod:`slack execution module <salt.modules.slack_notify>` documentation for more information. While you can use an individual user's API key to post to Slack, a bot user is likely better suited for this. The :py:mod:`slack engine <salt.engines.slack>` documentation has information on how to set up a bot user. ''' # Import python libs from __future__ import absolute_import, unicode_literals import datetime import logging import os import struct # Import Salt Libs import salt.utils.stringutils import salt.utils.files # Import 3rd-party libs import salt.ext.six # pylint: disable=import-error from salt.ext.six.moves import map # pylint: enable=import-error __virtualname__ = 'btmp' BTMP = '/var/log/btmp' FMT = b'hi32s4s32s256shhiii4i20x' FIELDS = [ 'type', 'PID', 'line', 'inittab', 'user', 'hostname', 'exit_status', 'session', 'time', 'addr' ] SIZE = struct.calcsize(FMT) LOC_KEY = 'btmp.loc' log = logging.getLogger(__name__) # pylint: disable=import-error try: import dateutil.parser as dateutil_parser _TIME_SUPPORTED = True except ImportError: _TIME_SUPPORTED = False def __virtual__(): if os.path.isfile(BTMP): return __virtualname__ return False def _validate_time_range(trange, status, msg): ''' Check time range ''' # If trange is empty, just return the current status & msg if not trange: return status, msg if not isinstance(trange, dict): status = False msg = ('The time_range parameter for ' 'btmp beacon must ' 'be a dictionary.') if not all(k in trange for k in ('start', 'end')): status = False msg = ('The time_range parameter for ' 'btmp beacon must contain ' 'start & end options.') return status, msg def _gather_group_members(group, groups, users): ''' Gather group members ''' _group = __salt__['group.info'](group) if not _group: log.warning('Group %s does not exist, ignoring.', group) return for member in _group['members']: if member not in users: users[member] = groups[group] def _check_time_range(time_range, now): ''' Check time range ''' if _TIME_SUPPORTED: _start = dateutil_parser.parse(time_range['start']) _end = dateutil_parser.parse(time_range['end']) return bool(_start <= now <= _end) else: log.error('Dateutil is required.') return False def _get_loc(): ''' return the active file location ''' if LOC_KEY in __context__: return __context__[LOC_KEY] def validate(config): ''' Validate the beacon configuration ''' vstatus = True vmsg = 'Valid beacon configuration' # Configuration for load beacon should be a list of dicts if not isinstance(config, list): vstatus = False vmsg = ('Configuration for btmp beacon must ' 'be a list.') else: _config = {} list(map(_config.update, config)) if 'users' in _config: if not isinstance(_config['users'], dict): vstatus = False vmsg = ('User configuration for btmp beacon must ' 'be a dictionary.') else: for user in _config['users']: _time_range = _config['users'][user].get('time_range', {}) vstatus, vmsg = _validate_time_range(_time_range, vstatus, vmsg) if not vstatus: return vstatus, vmsg if 'groups' in _config: if not isinstance(_config['groups'], dict): vstatus = False vmsg = ('Group configuration for btmp beacon must ' 'be a dictionary.') else: for group in _config['groups']: _time_range = _config['groups'][group].get('time_range', {}) vstatus, vmsg = _validate_time_range(_time_range, vstatus, vmsg) if not vstatus: return vstatus, vmsg if 'defaults' in _config: if not isinstance(_config['defaults'], dict): vstatus = False vmsg = ('Defaults configuration for btmp beacon must ' 'be a dictionary.') else: _time_range = _config['defaults'].get('time_range', {}) vstatus, vmsg = _validate_time_range(_time_range, vstatus, vmsg) if not vstatus: return vstatus, vmsg return vstatus, vmsg def beacon(config): ''' Read the last btmp file and return information on the failed logins ''' ret = [] users = {} groups = {} defaults = None for config_item in config: if 'users' in config_item: users = config_item['users'] if 'groups' in config_item: groups = config_item['groups'] if 'defaults' in config_item: defaults = config_item['defaults'] with salt.utils.files.fopen(BTMP, 'rb') as fp_: loc = __context__.get(LOC_KEY, 0) if loc == 0: fp_.seek(0, 2) __context__[LOC_KEY] = fp_.tell() return ret else: fp_.seek(loc) while True: now = datetime.datetime.now() raw = fp_.read(SIZE) if len(raw) != SIZE: return ret __context__[LOC_KEY] = fp_.tell() pack = struct.unpack(FMT, raw) event = {} for ind, field in enumerate(FIELDS): event[field] = pack[ind] if isinstance(event[field], salt.ext.six.string_types): if isinstance(event[field], bytes): event[field] = salt.utils.stringutils.to_unicode(event[field]) event[field] = event[field].strip('\x00') for group in groups: _gather_group_members(group, groups, users) if users: if event['user'] in users: _user = users[event['user']] if isinstance(_user, dict) and 'time_range' in _user: if _check_time_range(_user['time_range'], now): ret.append(event) else: if defaults and 'time_range' in defaults: if _check_time_range(defaults['time_range'], now): ret.append(event) else: ret.append(event) else: if defaults and 'time_range' in defaults: if _check_time_range(defaults['time_range'], now): ret.append(event) else: ret.append(event) return ret