File: //usr/lib/python2.7/site-packages/salt/tokens/rediscluster.py
# -*- coding: utf-8 -*-
'''
Provide token storage in Redis cluster.
To get started simply start a redis cluster and assign all hashslots to the connected nodes.
Add the redis hostname and port to master configs as eauth_redis_host and eauth_redis_port.
Default values for these configs are as follow:
.. code-block:: yaml
eauth_redis_host: localhost
eauth_redis_port: 6379
:depends: - redis-py-cluster Python package
'''
from __future__ import absolute_import, print_function, unicode_literals
try:
import rediscluster
HAS_REDIS = True
except ImportError:
HAS_REDIS = False
import os
import logging
import hashlib
import salt.payload
from salt.ext import six
log = logging.getLogger(__name__)
__virtualname__ = 'rediscluster'
def __virtual__():
if not HAS_REDIS:
return False, 'Could not use redis for tokens; '\
'rediscluster python client is not installed.'
return __virtualname__
def _redis_client(opts):
'''
Connect to the redis host and return a StrictRedisCluster client object.
If connection fails then return None.
'''
redis_host = opts.get("eauth_redis_host", "localhost")
redis_port = opts.get("eauth_redis_port", 6379)
try:
return rediscluster.StrictRedisCluster(host=redis_host, port=redis_port, decode_responses=True)
except rediscluster.exceptions.RedisClusterException as err:
log.warning(
'Failed to connect to redis at %s:%s - %s',
redis_host, redis_port, err
)
return None
def mk_token(opts, tdata):
'''
Mint a new token using the config option hash_type and store tdata with 'token' attribute set
to the token.
This module uses the hash of random 512 bytes as a token.
:param opts: Salt master config options
:param tdata: Token data to be stored with 'token' attirbute of this dict set to the token.
:returns: tdata with token if successful. Empty dict if failed.
'''
redis_client = _redis_client(opts)
if not redis_client:
return {}
hash_type = getattr(hashlib, opts.get('hash_type', 'md5'))
tok = six.text_type(hash_type(os.urandom(512)).hexdigest())
try:
while redis_client.get(tok) is not None:
tok = six.text_type(hash_type(os.urandom(512)).hexdigest())
except Exception as err: # pylint: disable=broad-except
log.warning(
'Authentication failure: cannot get token %s from redis: %s',
tok, err
)
return {}
tdata['token'] = tok
serial = salt.payload.Serial(opts)
try:
redis_client.set(tok, serial.dumps(tdata))
except Exception as err: # pylint: disable=broad-except
log.warning(
'Authentication failure: cannot save token %s to redis: %s',
tok, err
)
return {}
return tdata
def get_token(opts, tok):
'''
Fetch the token data from the store.
:param opts: Salt master config options
:param tok: Token value to get
:returns: Token data if successful. Empty dict if failed.
'''
redis_client = _redis_client(opts)
if not redis_client:
return {}
serial = salt.payload.Serial(opts)
try:
tdata = serial.loads(redis_client.get(tok))
return tdata
except Exception as err: # pylint: disable=broad-except
log.warning(
'Authentication failure: cannot get token %s from redis: %s',
tok, err
)
return {}
def rm_token(opts, tok):
'''
Remove token from the store.
:param opts: Salt master config options
:param tok: Token to remove
:returns: Empty dict if successful. None if failed.
'''
redis_client = _redis_client(opts)
if not redis_client:
return
try:
redis_client.delete(tok)
return {}
except Exception as err: # pylint: disable=broad-except
log.warning('Could not remove token %s: %s', tok, err)
def list_tokens(opts):
'''
List all tokens in the store.
:param opts: Salt master config options
:returns: List of dicts (token_data)
'''
ret = []
redis_client = _redis_client(opts)
if not redis_client:
return []
serial = salt.payload.Serial(opts)
try:
return [k.decode('utf8') for k in redis_client.keys()]
except Exception as err: # pylint: disable=broad-except
log.warning('Failed to list keys: %s', err)
return []