Add documentation to the example master and minion configuration files.

Move minion event signing to a saner place.
Enable dropping messages when signature does not verify or when
minion is not adding the signature to its payloads.
This commit is contained in:
C. R. Oldham 2017-06-08 13:18:53 -06:00
parent e44673cdae
commit dadf4b851c
7 changed files with 83 additions and 14 deletions

View file

@ -366,6 +366,20 @@
# will cause minion to throw an exception and drop the message.
# sign_pub_messages: False
# Signature verification on messages published from minions
# This requires that minions cryptographically sign the messages they
# publish to the master. If minions are not signing, then log this information
# at loglevel 'INFO' and drop the message without acting on it.
# require_minion_sign_messages: False
# The below will drop messages when their signatures do not validate.
# Note that when this option is False but `require_minion_sign_messages` is True
# minions MUST sign their messages but the validity of their signatures
# is ignored.
# These two config options exist so a Salt infrastructure can be moved
# to signing minion messages gradually.
# drop_messages_signature_fail: False
##### Salt-SSH Configuration #####
##########################################

View file

@ -9,3 +9,18 @@ controls whether a minion can request that the master revoke its key. When True
can request a key revocation and the master will comply. If it is False, the key will not
be revoked by the msater.
New master configuration option `require_minion_sign_messages`
This requires that minions cryptographically sign the messages they
publish to the master. If minions are not signing, then log this information
at loglevel 'INFO' and drop the message without acting on it.
New master configuration option `drop_messages_signature_fail`
Drop messages from minions when their signatures do not validate.
Note that when this option is False but `require_minion_sign_messages` is True
minions MUST sign their messages but the validity of their signatures
is ignored.
New minion configuration option `minion_sign_messages`
Causes the minion to cryptographically sign the payload of messages it places
on the event bus for the master. The payloads are signed with the minion's
private key so the master can verify the signature with its public key.

View file

@ -874,6 +874,19 @@ VALID_OPTS = {
# File chunk size for salt-cp
'salt_cp_chunk_size': int,
# Require that the minion sign messages it posts to the master on the event
# bus
'minion_sign_messages': bool,
# Have master drop messages from minions for which their signatures do
# not verify
'drop_messages_signature_fail': bool,
# Require that payloads from minions have a 'sig' entry
# (in other words, require that minions have 'minion_sign_messages'
# turned on)
'require_minion_sign_messages': bool,
}
# default configurations
@ -1105,6 +1118,7 @@ DEFAULT_MINION_OPTS = {
'ssl': None,
'cache': 'localfs',
'salt_cp_chunk_size': 65536,
'minion_sign_messages': False,
}
DEFAULT_MASTER_OPTS = {
@ -1364,6 +1378,8 @@ DEFAULT_MASTER_OPTS = {
'thin_extra_mods': '',
'allow_minion_key_revoke': True,
'salt_cp_chunk_size': 98304,
'require_minion_sign_messages': False,
'drop_messages_signature_fail': False,
}

View file

@ -780,6 +780,7 @@ class RemoteFuncs(object):
# If the return data is invalid, just ignore it
if any(key not in load for key in ('return', 'jid', 'id')):
return False
if load['jid'] == 'req':
# The minion is returning a standalone job, request a jobid
prep_fstr = '{0}.prep_jid'.format(self.opts['master_job_cache'])

View file

@ -1085,14 +1085,6 @@ class AESFuncs(object):
)
return False
if 'sig' in load:
log.trace('Verifying signed event publish from minion')
sig = load.pop('sig')
this_minion_pubkey = os.path.join(self.opts['pki_dir'], 'minions/{0}'.format(load['id']))
serialized_load = salt.serializers.msgpack.serialize(load)
if not salt.crypt.verify_signature(this_minion_pubkey, serialized_load, sig):
log.info('Signature for signed event from minion published on bus did not verify')
if 'tok' in load:
load.pop('tok')
@ -1385,6 +1377,24 @@ class AESFuncs(object):
:param dict load: The minion payload
'''
if self.opts['require_minion_sign_messages'] and 'sig' not in load:
log.critical('_return: Master is requiring minions to sign their messages, but there is no signature in this payload from {0}.'.format(load['id']))
return False
if 'sig' in load:
log.trace('Verifying signed event publish from minion')
sig = load.pop('sig')
this_minion_pubkey = os.path.join(self.opts['pki_dir'], 'minions/{0}'.format(load['id']))
serialized_load = salt.serializers.msgpack.serialize(load)
if not salt.crypt.verify_signature(this_minion_pubkey, serialized_load, sig):
log.info('Failed to verify event signature from minion {0}.'.format(load['id']))
if self.opts['drop_messages_signature_fail']:
log.critical('Drop_messages_signature_fail is enabled, dropping message from {0}'.format(load['id']))
return False
else:
log.info('But \'drop_message_signature_fail\' is disabled, so message is still accepted.')
load['sig'] = sig
try:
salt.utils.job.store_job(
self.opts, load, event=self.event, mminion=self.mminion)
@ -1428,6 +1438,9 @@ class AESFuncs(object):
ret['fun_args'] = load['arg']
if 'out' in load:
ret['out'] = load['out']
if 'sig' in load:
ret['sig'] = load['sig']
self._return(ret)
def minion_runner(self, clear_load):

View file

@ -1158,11 +1158,25 @@ class Minion(MinionBase):
return functions, returners, errors, executors
def _send_req_sync(self, load, timeout):
if self.opts['minion_sign_messages']:
log.trace('Signing event to be published onto the bus.')
minion_privkey_path = os.path.join(self.opts['pki_dir'], 'minion.pem')
sig = salt.crypt.sign_message(minion_privkey_path, salt.serializers.msgpack.serialize(load))
load['sig'] = sig
channel = salt.transport.Channel.factory(self.opts)
return channel.send(load, timeout=timeout)
@tornado.gen.coroutine
def _send_req_async(self, load, timeout):
if self.opts['minion_sign_messages']:
log.trace('Signing event to be published onto the bus.')
minion_privkey_path = os.path.join(self.opts['pki_dir'], 'minion.pem')
sig = salt.crypt.sign_message(minion_privkey_path, salt.serializers.msgpack.serialize(load))
load['sig'] = sig
channel = salt.transport.client.AsyncReqChannel.factory(self.opts)
ret = yield channel.send(load, timeout=timeout)
raise tornado.gen.Return(ret)
@ -1186,12 +1200,6 @@ class Minion(MinionBase):
else:
return
if self.opts['sign_minion_messages']:
log.trace('Signing event being published onto the bus.')
minion_privkey_path = os.path.join(self.opts['pki_dir'], 'minion.pem')
sig = salt.crypt.sign_message(minion_privkey_path, salt.serializers.msgpack.serialize(load))
load['sig'] = sig
def timeout_handler(*_):
log.info('fire_master failed: master could not be contacted. Request timed out.')
return True
@ -1636,6 +1644,7 @@ class Minion(MinionBase):
log.warning(msg)
return True
if sync:
try:
ret_val = self._send_req_sync(load, timeout=timeout)

View file

@ -3,6 +3,7 @@
# Import Python libs
from __future__ import absolute_import
import logging
import os
# Import Salt libs
import salt.minion