mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge branch 'develop' of github.com:saltstack/salt into develop
This commit is contained in:
commit
d9138e19d7
12 changed files with 290 additions and 29 deletions
|
@ -180,6 +180,24 @@
|
|||
# This is not recommended, since it would allow anyone who gets root on any
|
||||
# single minion to instantly have root on all of the minions!
|
||||
#
|
||||
# Minions can also be allowed to execute runners from the salt master.
|
||||
# Since executing a runner from the minion could be considered a security risk,
|
||||
# it needs to be enabled. This setting functions just like the peer seeting
|
||||
# except that it opens up runners instead of module functions.
|
||||
#
|
||||
# All peer runner support is turned off by default and must be enabled before
|
||||
# using. This will enable all peer runners for all minions:
|
||||
#
|
||||
# peer_run:
|
||||
# .*:
|
||||
# - .*
|
||||
#
|
||||
# To enable just the manage.up runner for the minion foo.example.com:
|
||||
#
|
||||
# peer_run:
|
||||
# foo.example.com:
|
||||
# - manage.up
|
||||
#
|
||||
|
||||
##### Cluster settings #####
|
||||
##########################################
|
||||
|
|
|
@ -471,6 +471,27 @@ This will allow all minions to execute all commands:
|
|||
This is not recommended, since it would allow anyone who gets root on any
|
||||
single minion to instantly have root on all of the minions!
|
||||
|
||||
.. conf_master:: peer_run
|
||||
|
||||
``peer_run``
|
||||
------------
|
||||
|
||||
Default: ``{}``
|
||||
|
||||
The peer_run option is used to open up runners on the master to access from the
|
||||
minions. The peer_run configuration matches the format of the peer
|
||||
configuration.
|
||||
|
||||
The following example would allow foo.example.com to execute the manage.up
|
||||
runner:
|
||||
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
peer_run:
|
||||
foo.example.com:
|
||||
- manage.up
|
||||
|
||||
Node Groups
|
||||
-----------
|
||||
|
||||
|
|
19
doc/topics/nonroot.rst
Normal file
19
doc/topics/nonroot.rst
Normal file
|
@ -0,0 +1,19 @@
|
|||
============================================
|
||||
Running the Salt Master as Unprivileged User
|
||||
============================================
|
||||
|
||||
While the default setup runs the Salt Master as the root user, it is generally
|
||||
wise to run servers as an unprivileged user. In Salt 0.9.10 the management
|
||||
of the running user was greatly improved, the only change needed is to alter
|
||||
the option ``user`` in the master configuration file and all salt system
|
||||
components will be updated to function under the new user when the master
|
||||
is started.
|
||||
|
||||
If running a version older that 0.9.10 then a number of files need to be
|
||||
owned by the user intended to run the master:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# chown -R <user> /var/cache/salt
|
||||
# chown -R <user> /var/log/salt
|
||||
# chown -R <user> /etc/salt/pki
|
|
@ -82,13 +82,16 @@ class Master(object):
|
|||
'''
|
||||
Run the sequence to start a salt master server
|
||||
'''
|
||||
verify_env([os.path.join(self.opts['pki_dir'], 'minions'),
|
||||
verify_env([self.opts['pki_dir'],
|
||||
os.path.join(self.opts['pki_dir'], 'minions'),
|
||||
os.path.join(self.opts['pki_dir'], 'minions_pre'),
|
||||
os.path.join(self.opts['pki_dir'], 'minions_rejected'),
|
||||
self.opts['cachedir'],
|
||||
os.path.join(self.opts['cachedir'], 'jobs'),
|
||||
os.path.dirname(self.opts['log_file']),
|
||||
self.opts['sock_dir'],
|
||||
])
|
||||
],
|
||||
self.opts['user'])
|
||||
import salt.log
|
||||
salt.log.setup_logfile_logger(
|
||||
self.opts['log_file'], self.opts['log_level']
|
||||
|
@ -176,7 +179,8 @@ class Minion(object):
|
|||
self.opts['cachedir'],
|
||||
self.opts['extension_modules'],
|
||||
os.path.dirname(self.opts['log_file']),
|
||||
])
|
||||
],
|
||||
self.opts['user'])
|
||||
import salt.log
|
||||
salt.log.setup_logfile_logger(
|
||||
self.opts['log_file'], self.opts['log_level']
|
||||
|
@ -295,7 +299,8 @@ class Syndic(object):
|
|||
'''
|
||||
verify_env([self.opts['pki_dir'], self.opts['cachedir'],
|
||||
os.path.dirname(self.opts['log_file']),
|
||||
])
|
||||
],
|
||||
self.opts['user'])
|
||||
import salt.log
|
||||
salt.log.setup_logfile_logger(
|
||||
self.opts['log_file'], self.opts['log_level']
|
||||
|
|
|
@ -663,7 +663,8 @@ class SaltKey(object):
|
|||
os.path.join(self.opts['pki_dir'], 'minions_pre'),
|
||||
os.path.join(self.opts['pki_dir'], 'minions_rejected'),
|
||||
os.path.dirname(self.opts['log_file']),
|
||||
])
|
||||
],
|
||||
self.opts['user'])
|
||||
import salt.log
|
||||
salt.log.setup_logfile_logger(self.opts['key_logfile'],
|
||||
self.opts['loglevel'])
|
||||
|
@ -767,7 +768,8 @@ class SaltCall(object):
|
|||
verify_env([opts['pki_dir'],
|
||||
opts['cachedir'],
|
||||
os.path.dirname(opts['log_file']),
|
||||
])
|
||||
],
|
||||
self.opts['user'])
|
||||
|
||||
return opts
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ import zmq
|
|||
import salt.config
|
||||
import salt.payload
|
||||
import salt.utils
|
||||
import salt.utils.event
|
||||
from salt.exceptions import SaltClientError, SaltInvocationError
|
||||
|
||||
# Try to import range from https://github.com/ytoolshed/range
|
||||
|
@ -74,6 +75,7 @@ class LocalClient(object):
|
|||
self.serial = salt.payload.Serial(self.opts)
|
||||
self.key = self.__read_master_key()
|
||||
self.salt_user = self.__get_user()
|
||||
self.event = salt.utils.event.MasterEvent(self.opts['sock_dir'])
|
||||
|
||||
def __read_master_key(self):
|
||||
'''
|
||||
|
@ -619,6 +621,13 @@ class LocalClient(object):
|
|||
return ret
|
||||
time.sleep(0.02)
|
||||
|
||||
def get_event_returns(self, jid, minions, timeout=None):
|
||||
'''
|
||||
Gather the return data from the event system
|
||||
'''
|
||||
if timeout is None:
|
||||
timeout = self.opts['timeout']
|
||||
|
||||
def find_cmd(self, cmd):
|
||||
'''
|
||||
Hunt through the old salt calls for when cmd was run, return a dict:
|
||||
|
@ -691,6 +700,15 @@ class LocalClient(object):
|
|||
minions:
|
||||
A set, the targets that the tgt passed should match.
|
||||
'''
|
||||
# Make sure the publisher is running by checking the unix socket
|
||||
if not os.path.exists(
|
||||
os.path.join(
|
||||
self.opts['sock_dir'],
|
||||
'publish_pull.ipc'
|
||||
)
|
||||
):
|
||||
return {'jid': '0', 'minions': []}
|
||||
|
||||
if expr_form == 'nodegroup':
|
||||
if tgt not in self.opts['nodegroups']:
|
||||
conf_file = self.opts.get('conf_file', 'the master config file')
|
||||
|
@ -754,19 +772,7 @@ class LocalClient(object):
|
|||
)
|
||||
)
|
||||
socket.send(package)
|
||||
payload = None
|
||||
for ind in range(100):
|
||||
try:
|
||||
payload = self.serial.loads(
|
||||
socket.recv(
|
||||
zmq.NOBLOCK
|
||||
)
|
||||
)
|
||||
break
|
||||
except zmq.core.error.ZMQError:
|
||||
time.sleep(0.01)
|
||||
if not payload:
|
||||
return {'jid': '0', 'minions': []}
|
||||
payload = self.serial.loads(socket.recv())
|
||||
return {'jid': payload['load']['jid'],
|
||||
'minions': minions}
|
||||
|
||||
|
|
|
@ -510,7 +510,7 @@ class Loader(object):
|
|||
try:
|
||||
ret = fun()
|
||||
except Exception as exc:
|
||||
log.critical(('Failed to load grains definded in grain file '
|
||||
log.critical(('Failed to load grains defined in grain file '
|
||||
'{0} in function {1}, error: {2}').format(
|
||||
key, fun, exc))
|
||||
continue
|
||||
|
|
|
@ -173,6 +173,8 @@ class Master(SMaster):
|
|||
aes_funcs,
|
||||
clear_funcs)
|
||||
reqserv.start_publisher()
|
||||
# Uncomment this when the master side event system is ready
|
||||
#reqserv.start_event_publisher()
|
||||
|
||||
def sigterm_clean(signum, frame):
|
||||
'''
|
||||
|
@ -199,6 +201,63 @@ class Master(SMaster):
|
|||
raise SystemExit('\nExiting on Ctrl-c')
|
||||
|
||||
|
||||
class EventPublisher(multiprocessing.Process):
|
||||
'''
|
||||
The interface that takes master events and republishes them out to anyone
|
||||
who wants to listen
|
||||
'''
|
||||
def __init__(self, opts):
|
||||
super(EventPublisher, self).__init__()
|
||||
self.opts = opts
|
||||
|
||||
def run(self):
|
||||
'''
|
||||
Bind the pub and pull sockets for events
|
||||
'''
|
||||
# Set up the context
|
||||
context = zmq.Context(1)
|
||||
# Prepare the master event publisher
|
||||
epub_sock = context.socket(zmq.PUB)
|
||||
epub_uri = 'ipc://{0}'.format(
|
||||
os.path.join(self.opts['sock_dir'], 'master_event_pub.ipc')
|
||||
)
|
||||
# Prepare master event pull socket
|
||||
epull_sock = context.socket(zmq.PULL)
|
||||
epull_uri = 'ipc://{0}'.format(
|
||||
os.path.join(self.opts['sock_dir'], 'master_event_pull.ipc')
|
||||
)
|
||||
# Start the master event publisher
|
||||
log.info('Starting the Salt Event Publisher on {0}'.format(epub_uri))
|
||||
epub_sock.bind(epub_uri)
|
||||
epull_sock.bind(epull_uri)
|
||||
# Restrict access to the sockets
|
||||
os.chmod(
|
||||
os.path.join(self.opts['sock_dir'],
|
||||
'master_event_pub.ipc'),
|
||||
448
|
||||
)
|
||||
os.chmod(
|
||||
os.path.join(self.opts['sock_dir'],
|
||||
'master_event_pull.ipc'),
|
||||
448
|
||||
)
|
||||
|
||||
try:
|
||||
while True:
|
||||
# Catch and handle EINTR from when this process is sent
|
||||
# SIGUSR1 gracefully so we don't choke and die horribly
|
||||
try:
|
||||
package = epull_sock.recv()
|
||||
epub_sock.send(package)
|
||||
except zmq.ZMQError as exc:
|
||||
if exc.errno == errno.EINTR:
|
||||
continue
|
||||
raise exc
|
||||
except KeyboardInterrupt:
|
||||
epub_sock.close()
|
||||
epull_sock.close()
|
||||
|
||||
|
||||
class Publisher(multiprocessing.Process):
|
||||
'''
|
||||
The publishing interface, a simple zeromq publisher that sends out the
|
||||
|
@ -212,17 +271,27 @@ class Publisher(multiprocessing.Process):
|
|||
'''
|
||||
Bind to the interface specified in the configuration file
|
||||
'''
|
||||
# Set up the context
|
||||
context = zmq.Context(1)
|
||||
# Prepare minion publish socket
|
||||
pub_sock = context.socket(zmq.PUB)
|
||||
pub_sock.setsockopt(zmq.HWM, 1)
|
||||
pull_sock = context.socket(zmq.PULL)
|
||||
pub_uri = 'tcp://{0[interface]}:{0[publish_port]}'.format(self.opts)
|
||||
# Prepare minion pull socket
|
||||
pull_sock = context.socket(zmq.PULL)
|
||||
pull_uri = 'ipc://{0}'.format(
|
||||
os.path.join(self.opts['sock_dir'], 'publish_pull.ipc')
|
||||
)
|
||||
os.path.join(self.opts['sock_dir'], 'publish_pull.ipc')
|
||||
)
|
||||
# Start the minion command publisher
|
||||
log.info('Starting the Salt Publisher on {0}'.format(pub_uri))
|
||||
pub_sock.bind(pub_uri)
|
||||
pull_sock.bind(pull_uri)
|
||||
# Restrict access to the socket
|
||||
os.chmod(
|
||||
os.path.join(self.opts['sock_dir'],
|
||||
'publish_pull.ipc'),
|
||||
448
|
||||
)
|
||||
|
||||
try:
|
||||
while True:
|
||||
|
@ -300,6 +369,15 @@ class ReqServer(object):
|
|||
self.publisher = Publisher(self.opts)
|
||||
self.publisher.start()
|
||||
|
||||
|
||||
def start_event_publisher(self):
|
||||
'''
|
||||
Start the salt publisher interface
|
||||
'''
|
||||
# Start the publisher
|
||||
self.eventpublisher = EventPublisher(self.opts)
|
||||
self.eventpublisher.start()
|
||||
|
||||
def run(self):
|
||||
'''
|
||||
Start up the ReqServer
|
||||
|
|
|
@ -104,9 +104,9 @@ def load(mod):
|
|||
|
||||
salt '*' kmod.load kvm
|
||||
'''
|
||||
pre_mods = kldstat()
|
||||
pre_mods = lsmod()
|
||||
data = __salt__['cmd.run_all']('kldload {0}'.format(mod))
|
||||
post_mods = kldstat()
|
||||
post_mods = lsmod()
|
||||
return _new_mods(pre_mods, post_mods)
|
||||
|
||||
|
||||
|
@ -118,7 +118,7 @@ def remove(mod):
|
|||
|
||||
salt '*' kmod.remove kvm
|
||||
'''
|
||||
pre_mods = kldstat()
|
||||
pre_mods = lsmod()
|
||||
data = __salt__['cmd.run_all']('kldunload {0}'.format(mod))
|
||||
post_mods = kldstat()
|
||||
post_mods = lsmod()
|
||||
return _rm_mods(pre_mods, post_mods)
|
||||
|
|
78
salt/utils/event.py
Normal file
78
salt/utils/event.py
Normal file
|
@ -0,0 +1,78 @@
|
|||
'''
|
||||
Manage event listeners.
|
||||
'''
|
||||
|
||||
# Import Python libs
|
||||
import os
|
||||
|
||||
# Import Third Party libs
|
||||
import zmq
|
||||
|
||||
|
||||
class SaltEvent(object):
|
||||
'''
|
||||
The base class used to manage salt events
|
||||
'''
|
||||
def __init__(self, sock_dir, node):
|
||||
self.poller = zmq.Poller()
|
||||
self.cpub = False
|
||||
if node == 'master':
|
||||
self.puburi = os.path.join(
|
||||
sock_dir,
|
||||
'master_event_pub.ipc'
|
||||
)
|
||||
self.pulluri = os.path.join(
|
||||
sock_dir,
|
||||
'master_event_pull.ipc'
|
||||
)
|
||||
else:
|
||||
self.puburi = os.path.join(
|
||||
sock_dir,
|
||||
'minion_event_pub.ipc'
|
||||
)
|
||||
self.pulluri = os.path.join(
|
||||
sock_dir,
|
||||
'minion_event_pull.ipc'
|
||||
)
|
||||
|
||||
def connect_pub(self):
|
||||
'''
|
||||
Establish the publish connection
|
||||
'''
|
||||
self.context = zmq.context()
|
||||
self.sub = context.socket(zmq.SUB)
|
||||
self.sub.connect(self.puburi)
|
||||
self.poller.register(self.pub, zmq.POLLIN)
|
||||
self.cpub = True
|
||||
|
||||
def get_event(self, wait=5, tag=''):
|
||||
'''
|
||||
Get a single publication
|
||||
'''
|
||||
if not self.cpub:
|
||||
self.connect_pub()
|
||||
self.sub.setsockopt(zmq.SUBSCRIBE, tag)
|
||||
start = time.time()
|
||||
while True:
|
||||
socks = dict(self.poller.poll())
|
||||
if self.pub in socks and socks[self.pub] == zmq.POLLIN:
|
||||
return self.sub.recv()
|
||||
if (time.time() - start) > wait:
|
||||
return None
|
||||
|
||||
|
||||
class MasterEvent(SaltEvent):
|
||||
'''
|
||||
Create a master event management object
|
||||
'''
|
||||
def __init__(self, sock_dir):
|
||||
super(MasterEvent, self).__init__(sock_dir, 'master')
|
||||
self.connect_pub()
|
||||
|
||||
|
||||
class MinionEvent(SaltEvent):
|
||||
'''
|
||||
Create a master event management object
|
||||
'''
|
||||
def __init__(self, sock_dir):
|
||||
super(MinionEvent, self).__init__(sock_dir, 'minion')
|
|
@ -4,6 +4,7 @@ A few checks to make sure the environment is sane
|
|||
# Original Author: Jeff Schroeder <jeffschroeder@computer.org>
|
||||
import os
|
||||
import re
|
||||
import pwd
|
||||
import sys
|
||||
import stat
|
||||
import getpass
|
||||
|
@ -60,21 +61,52 @@ def zmq_version():
|
|||
return False
|
||||
|
||||
|
||||
def verify_env(dirs):
|
||||
def verify_env(dirs, user):
|
||||
'''
|
||||
Verify that the named directories are in place and that the environment
|
||||
can shake the salt
|
||||
'''
|
||||
try:
|
||||
pwnam = pwd.getpwnam(user)
|
||||
uid = pwnam[2]
|
||||
gid = pwnam[3]
|
||||
except KeyError:
|
||||
err = ('Failed to prepare the Salt environment for user '
|
||||
'{0}. The user is not available.\n').format(user)
|
||||
sys.stderr.write(err)
|
||||
sys.exit(2)
|
||||
for dir_ in dirs:
|
||||
if not os.path.isdir(dir_):
|
||||
try:
|
||||
cumask = os.umask(63) # 077
|
||||
os.makedirs(dir_)
|
||||
# If starting the process as root, chown the new dirs
|
||||
if os.getuid() == 0:
|
||||
os.chown(dir_, uid, gid)
|
||||
os.umask(cumask)
|
||||
except OSError as e:
|
||||
sys.stderr.write('Failed to create directory path "{0}" - {1}\n'.format(dir_, e))
|
||||
|
||||
mode = os.stat(dir_)
|
||||
# If starting the process as root, chown the new dirs
|
||||
if os.getuid() == 0:
|
||||
fmode = os.stat(dir_)
|
||||
if not fmode.st_uid == uid or not fmode.st_gid == gid:
|
||||
# chown the file for the new user
|
||||
os.chown(dir_, uid, gid)
|
||||
for root, dirs, files in os.walk(dir_):
|
||||
for name in files:
|
||||
path = os.path.join(root, name)
|
||||
fmode = os.stat(path)
|
||||
if not fmode.st_uid == uid or not fmode.st_gid == gid:
|
||||
# chown the file for the new user
|
||||
os.chown(path, uid, gid)
|
||||
for name in dirs:
|
||||
path = os.path.join(root, name)
|
||||
fmode = os.stat(path)
|
||||
if not fmode.st_uid == uid or not fmode.st_gid == gid:
|
||||
# chown the file for the new user
|
||||
os.chown(path, uid, gid)
|
||||
# Allow the pki dir to be 700 or 750, but nothing else.
|
||||
# This prevents other users from writing out keys, while
|
||||
# allowing the use-case of 3rd-party software (like django)
|
||||
|
|
|
@ -5,6 +5,7 @@ Set up the Salt integration test suite
|
|||
# Import Python libs
|
||||
import multiprocessing
|
||||
import os
|
||||
import pwd
|
||||
import sys
|
||||
import shutil
|
||||
import signal
|
||||
|
@ -85,7 +86,8 @@ class TestDaemon(object):
|
|||
self.sub_minion_opts['pki_dir'],
|
||||
self.master_opts['sock_dir'],
|
||||
self.smaster_opts['sock_dir'],
|
||||
])
|
||||
],
|
||||
pwd.getpwuid(os.getuid())[0])
|
||||
|
||||
master = salt.master.Master(self.master_opts)
|
||||
self.master_process = multiprocessing.Process(target=master.start)
|
||||
|
|
Loading…
Add table
Reference in a new issue