salt/returners/smtp_return.py
# -*- coding: utf-8 -*-
'''
Return salt data via email
The following fields can be set in the minion conf file. Fields are optional
unless noted otherwise.
* ``from`` (required) The name/address of the email sender.
* ``to`` (required) The names/addresses of the email recipients;
comma-delimited. For example: ``you@example.com,someoneelse@example.com``.
* ``host`` (required) The SMTP server hostname or address.
* ``port`` The SMTP server port; defaults to ``25``.
* ``username`` The username used to authenticate to the server. If specified a
password is also required. It is recommended but not required to also use
TLS with this option.
* ``password`` The password used to authenticate to the server.
* ``tls`` Whether to secure the connection using TLS; defaults to ``False``
* ``subject`` The email subject line.
* ``fields`` Which fields from the returned data to include in the subject line
of the email; comma-delimited. For example: ``id,fun``. Please note, *the
subject line is not encrypted*.
* ``gpgowner`` A user's :file:`~/.gpg` directory. This must contain a gpg
public key matching the address the mail is sent to. If left unset, no
encryption will be used. Requires :program:`python-gnupg` to be installed.
* ``template`` The path to a file to be used as a template for the email body.
* ``renderer`` A Salt renderer, or render-pipe, to use to render the email
template. Default ``jinja``.
Below is an example of the above settings in a Salt Minion configuration file:
.. code-block:: yaml
smtp.from: me@example.net
smtp.to: you@example.com
smtp.host: localhost
smtp.port: 1025
Alternative configuration values can be used by prefacing the configuration.
Any values not found in the alternative configuration will be pulled from
the default location. For example:
.. code-block:: yaml
alternative.smtp.username: saltdev
alternative.smtp.password: saltdev
alternative.smtp.tls: True
To use the SMTP returner, append '--return smtp' to the ``salt`` command.
.. code-block:: bash
salt '*' test.ping --return smtp
To use the alternative configuration, append '--return_config alternative' to the ``salt`` command.
.. versionadded:: 2015.5.0
.. code-block:: bash
salt '*' test.ping --return smtp --return_config alternative
To override individual configuration items, append --return_kwargs '{"key:": "value"}' to the
``salt`` command.
.. versionadded:: 2016.3.0
.. code-block:: bash
salt '*' test.ping --return smtp --return_kwargs '{"to": "user@domain.com"}'
An easy way to test the SMTP returner is to use the development SMTP server
built into Python. The command below will start a single-threaded SMTP server
that prints any email it receives to the console.
.. code-block:: python
python -m smtpd -n -c DebuggingServer localhost:1025
.. versionadded:: 2016.11.0
It is possible to send emails with selected Salt events by configuring ``event_return`` option
for Salt Master. For example:
.. code-block:: yaml
event_return: smtp
event_return_whitelist:
- salt/key
smtp.from: me@example.net
smtp.to: you@example.com
smtp.host: localhost
smtp.subject: 'Salt Master {{act}}ed key from Minion ID: {{id}}'
smtp.template: /srv/salt/templates/email.j2
Also you need to create additional file ``/srv/salt/templates/email.j2`` with email body template:
.. code-block:: yaml
act: {{act}}
id: {{id}}
result: {{result}}
This configuration enables Salt Master to send an email when accepting or rejecting minions keys.
'''
# Import python libs
from __future__ import absolute_import, print_function, unicode_literals
import os
import logging
import smtplib
from email.utils import formatdate
# Import Salt libs
from salt.ext import six
import salt.utils.jid
import salt.returners
import salt.loader
from salt.template import compile_template
try:
import gnupg
HAS_GNUPG = True
except ImportError:
HAS_GNUPG = False
log = logging.getLogger(__name__)
__virtualname__ = 'smtp'
def __virtual__():
return __virtualname__
def _get_options(ret=None):
'''
Get the SMTP options from salt.
'''
attrs = {'from': 'from',
'to': 'to',
'host': 'host',
'port': 'port',
'username': 'username',
'password': 'password',
'subject': 'subject',
'gpgowner': 'gpgowner',
'fields': 'fields',
'tls': 'tls',
'renderer': 'renderer',
'template': 'template'}
_options = salt.returners.get_returner_options(__virtualname__,
ret,
attrs,
__salt__=__salt__,
__opts__=__opts__)
return _options
def returner(ret):
'''
Send an email with the data
'''
_options = _get_options(ret)
from_addr = _options.get('from')
to_addrs = _options.get('to').split(',')
host = _options.get('host')
port = _options.get('port')
user = _options.get('username')
passwd = _options.get('password')
subject = _options.get('subject') or 'Email from Salt'
gpgowner = _options.get('gpgowner')
fields = _options.get('fields').split(',') if 'fields' in _options else []
smtp_tls = _options.get('tls')
renderer = _options.get('renderer') or 'jinja'
rend = salt.loader.render(__opts__, {})
blacklist = __opts__.get('renderer_blacklist')
whitelist = __opts__.get('renderer_whitelist')
if not port:
port = 25
log.debug('SMTP port has been set to %s', port)
for field in fields:
if field in ret:
subject += ' {0}'.format(ret[field])
subject = compile_template(':string:',
rend,
renderer,
blacklist,
whitelist,
input_data=subject,
**ret)
if isinstance(subject, six.moves.StringIO):
subject = subject.read()
log.debug("smtp_return: Subject is '%s'", subject)
template = _options.get('template')
if template:
content = compile_template(template, rend, renderer, blacklist, whitelist, **ret)
else:
template = ('id: {{id}}\r\n'
'function: {{fun}}\r\n'
'function args: {{fun_args}}\r\n'
'jid: {{jid}}\r\n'
'return: {{return}}\r\n')
content = compile_template(':string:',
rend,
renderer,
blacklist,
whitelist,
input_data=template,
**ret)
if gpgowner:
if HAS_GNUPG:
gpg = gnupg.GPG(gnupghome=os.path.expanduser('~{0}/.gnupg'.format(gpgowner)),
options=['--trust-model always'])
encrypted_data = gpg.encrypt(content, to_addrs)
if encrypted_data.ok:
log.debug('smtp_return: Encryption successful')
content = six.text_type(encrypted_data)
else:
log.error('smtp_return: Encryption failed, only an error message will be sent')
content = 'Encryption failed, the return data was not sent.\r\n\r\n{0}\r\n{1}'.format(
encrypted_data.status, encrypted_data.stderr)
else:
log.error("gnupg python module is required in order to user gpgowner in smtp returner ; ignoring gpgowner configuration for now")
if isinstance(content, six.moves.StringIO):
content = content.read()
message = ('From: {0}\r\n'
'To: {1}\r\n'
'Date: {2}\r\n'
'Subject: {3}\r\n'
'\r\n'
'{4}').format(from_addr,
', '.join(to_addrs),
formatdate(localtime=True),
subject,
content)
log.debug('smtp_return: Connecting to the server...')
server = smtplib.SMTP(host, int(port))
if smtp_tls is True:
server.starttls()
log.debug('smtp_return: TLS enabled')
if user and passwd:
server.login(user, passwd)
log.debug('smtp_return: Authenticated')
# enable logging SMTP session after the login credentials were passed
server.set_debuglevel(1)
server.sendmail(from_addr, to_addrs, message)
log.debug('smtp_return: Message sent.')
server.quit()
def prep_jid(nocache=False, passed_jid=None): # pylint: disable=unused-argument
'''
Do any work necessary to prepare a JID, including sending a custom id
'''
return passed_jid if passed_jid is not None else salt.utils.jid.gen_jid(__opts__)
def event_return(events):
'''
Return event data via SMTP
'''
for event in events:
ret = event.get('data', False)
if ret:
returner(ret)