Merge branch 'develop' of github.com:saltstack/salt into develop

This commit is contained in:
Thomas S Hatch 2012-06-04 12:52:06 -06:00
commit d9138e19d7
12 changed files with 290 additions and 29 deletions

View file

@ -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 #####
##########################################

View file

@ -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
View 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

View file

@ -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']

View file

@ -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

View file

@ -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}

View file

@ -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

View file

@ -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

View file

@ -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
View 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')

View file

@ -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)

View file

@ -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)