File: //opt/saltstack/salt/lib/python3.10/site-packages/salt/returners/smtp_return.py
"""
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 io
import logging
import os
import smtplib
from email.utils import formatdate
import salt.loader
import salt.returners
import salt.utils.jid
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 += f" {ret[field]}"
subject = compile_template(
":string:", rend, renderer, blacklist, whitelist, input_data=subject, **ret
)
if isinstance(subject, io.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(f"~{gpgowner}/.gnupg"),
options=["--trust-model always"],
)
encrypted_data = gpg.encrypt(content, to_addrs)
if encrypted_data.ok:
log.debug("smtp_return: Encryption successful")
content = str(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{}\r\n{}".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, io.StringIO):
content = content.read()
message = "From: {}\r\nTo: {}\r\nDate: {}\r\nSubject: {}\r\n\r\n{}".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)