# coding: utf-8
from __future__ import unicode_literals
import os
from apps.base import BaseApp
from fabobjects.distros import shell_safe
from fabobjects.utils import server_host_manager
[docs]class RedisApp(BaseApp):
"""
A Redis class that defines a set of methods that's used by both redis server and redis client class.
"""
[docs] def redis_cli(self, command):
"""
Run redis command
:param str command: The command you want to pass to redis
:return:
"""
if self.redis_password is not None:
command = "redis-cli -a {0} {1}".format(self.redis_password, command)
else:
command = "redis-cli {0}".format(command)
return self.run(command)
[docs]class RedisServer(RedisApp):
"""
A Redis server class, this class installs and sets up a redis server on a host server.
"""
def __init__(self, *args, **kwargs):
"""
:param int service_port = Port to listen for connections, defaults to 6379.
:param str maxmemory = Max memory redis should use for storage.
:param str exposed_ip = The ip address redis should to accept connections on, defaults to `None`
:param str redis_password = Password for your redis server, defaults to `None`
:param str allowed_ip = A list or string of ip address to accept connections from, defaults to `None`
:param bool public = Set to `True` if you want redis to accept public connections,
by default this is set to `False`
"""
super(RedisServer, self).__init__(*args, **kwargs)
self.service_name = "redis-server"
self.service_port = kwargs.get("service_port", "6379")
self.maxmemory = kwargs.get("maxmemory", None)
self.exposed_ip = kwargs.get("exposed_ip", None)
self.redis_password = kwargs.get("redis_password", None)
self.allowed_ip = kwargs.get("allowed_ip", None)
self.public = kwargs.get("public", False)
[docs] def status(self):
"""
A method for restarting the application.
:return: None
"""
return self.service_status(self.service_name)
[docs] def reload(self):
"""
A method for reload the redis.
:return: None
"""
return self.service("{0} force-reload".format(self.service_name))
[docs] def start(self):
"""
A method for starting redis.
:return: None
"""
return self.service_start(self.service_name)
[docs] def stop(self):
"""
A method for stopping redis.
:return: None
"""
return self.service_stop(self.service_name)
[docs] def restart(self):
"""
A method for restarting redis.
:return: None
"""
return self.service_restart(self.service_name)
@server_host_manager
def deploy(self):
"""
Install and configure redis on a server.
:return: None
"""
self.install_package("redis-server")
if self.redis_password is not None:
self.set_password(pswd=self.redis_password)
if self.maxmemory is not None:
self.set_memory_limit(maxmemory=self.maxmemory)
if self.public:
self.make_public()
self.firewall_conf(self.allowed_ip)
@server_host_manager
def set_memory_limit(self, maxmemory=None):
"""
Set memory to be reserved for redis to use for storage
:param str maxmemory:
:return:
"""
afile = shell_safe("/etc/redis/redis.conf")
if maxmemory is None:
if self.maxmemory is None:
self.maxmemory = "256mb"
maxmemory = self.maxmemory
self.echo("maxmemory {0}".format(maxmemory), to=afile)
self.echo("maxmemory-policy allkeys-lru", to=afile)
@server_host_manager
def make_public(self, ip="0.0.0.0"):
"""
Expose redis to public traffic on given ip interface
:param ip: The ip redis will be listening on.
:return: None
"""
afile = shell_safe("/etc/redis/redis.conf")
self.sed(afile, "bind 127.0.0.1", "bind {0}".format(ip), use_sudo=True)
@server_host_manager
def change_port(self, port):
"""
Change the port which redis is listening on.
:param int port: The port number redis should listening on
:return: None
"""
try:
port = str(int(port))
except ValueError as err:
print("Value error: port must be an int".format(err))
raise err
new_port = "port {0}".format(port)
old_port = "port {0}".format(self.service_port)
afile = shell_safe("/etc/redis/redis.conf")
self.sed(afile, old_port, new_port, use_sudo=True)
self.service_port = port
@server_host_manager
def set_password(self, pswd=None):
"""
Set Up password protection for redis server
:param str pswd: A strong pass word
:return: None
"""
afile = shell_safe("/etc/redis/redis.conf")
if pswd is None:
raise RuntimeError("You did not enter password")
self.sed(
afile,
"# requirepass foobared",
"requirepass {0}".format(pswd),
use_sudo=True,
)
if self.redis_password != pswd:
self.redis_password = pswd
@server_host_manager
def firewall_conf(self):
"""
Expose ip or list of ips that can no
:return:None
"""
if self.allowed_ip is None:
return
if type(self.allowed_ip) != list:
self.allowed_ip = [self.allowed_ip]
[
self.firewall_allow_form_to(
host=host, to=self.exposed_ip, proto="tcp", port=self.service_port
)
for host in self.allowed_ip
]
[docs] def enable_ssl(
self, domain=None, country_iso=None, state=None, city=None, company_name=None
):
"""
Enable ssl connection on redis server
:param str domain: The website domain name
:param str country_iso:
:param str state:
:param str city:
:param str company_name:
:return: None
"""
# Install and configure stunnel to start on boot
self.install_package("stunnel4")
afile = shell_safe("/etc/default/stunnel4")
self.sed(afile, "ENABLED=0", "ENABLED=1", use_sudo=True)
# Create self signed ssl cert
conf_file = shell_safe("/etc/stunnel/redis-server.conf")
ssl_dir = shell_safe("/etc/stunnel/")
combined_cert = os.path.join(ssl_dir, os.path.join("certs", "private.pem"))
self.generate_self_signed_ssl(
domain=domain,
cert_dir=ssl_dir,
country_iso=country_iso,
state=state,
city=city,
company_name=company_name,
)
ssl_dir = os.path.join(ssl_dir, "certs")
self.sudo(
"cat {0}/{1}.key {0}/{1}.crt > {2}".format(ssl_dir, domain, combined_cert)
)
self.sudo("chmod 640 {0}".format(combined_cert))
# Configure stunnel to use our self signed ssl cert
self.echo("cert = {0}".format(combined_cert), to=conf_file, append=False)
self.echo("pid = /var/run/stunnel.pid", to=conf_file)
self.echo("[redis]", to=conf_file)
self.echo("accept = {0}:6379".format(self.exposed_ip), to=conf_file)
self.echo("connect = 127.0.0.1:6379", to=conf_file)
# Ensure redis is listening on localhost since stunnel is listen on the public ip
afile = shell_safe("/etc/redis/redis.conf")
self.sed(afile, "bind 0.0.0.0", "bind 127.0.0.1", use_sudo=True)
self.service_restart("redis")
self.service_start("stunnel4")
[docs]class RedisSslClient(RedisApp):
"""
A Redis ssl client, this class installs and sets up a redis client to talk to a remote redis server over an
ssl connection.
"""
def __init__(self, *args, **kwargs):
"""
Initializes the redis ssl client connection.
:param str server_ip: The ip address of the remote redis server
:param str server_cert: Path on your local file system to the server private.pem
"""
super(RedisSslClient, self).__init__(*args, **kwargs)
self.server_ip = kwargs.get("server_ip", None)
self.server_cert = kwargs.get("server_cert", None)
self.redis_password = kwargs.get("redis_password", None)
[docs] def deploy(self):
"""
Install and set up a redis client on a host server.
:return:
"""
afile = shell_safe("/etc/default/stunnel4")
combined_cert = shell_safe(
os.path.join("/etc/stunnel/", self.server_cert.split("/")[-1])
)
self.install_package("redis-tools stunnel4")
self.sed(afile, "ENABLED=0", "ENABLED=1", use_sudo=True)
self.put(local_path=self.server_cert, remote_path=shell_safe("/etc/stunnel/"))
self.sudo("chmod 640 {0}".format(combined_cert))
# Configure stunnel to use our self signed ssl cert
conf_file = shell_safe("/etc/stunnel/redis-server.conf")
self.echo("cert = {0}".format(combined_cert), to=conf_file, append=False)
self.echo("client = yes", to=conf_file)
self.echo("pid = /var/run/stunnel.pid", to=conf_file)
self.echo("[redis]", to=conf_file)
self.echo("accept = 127.0.0.1:6379", to=conf_file)
self.echo("connect = {0}:6379".format(self.server_ip), to=conf_file)
self.service_start("stunnel4")