Redis 6.0 finally added ACL and SSL/TLS. Our multi-tenant Redis was a security risk.

Implemented ACL and encryption. Zero security incidents since. Here’s how.

Table of Contents

The Security Problem

Before Redis 6.0:

  • Single password for all clients
  • No user-level permissions
  • No encryption in transit
  • Security audit: Failed

Risks:

  • Tenant A can access Tenant B’s data
  • Compromised client = full access
  • Network sniffing possible

ACL (Access Control Lists)

# Create users with specific permissions
ACL SETUSER alice on >password123 ~user:* +get +set
ACL SETUSER bob on >password456 ~analytics:* +get
ACL SETUSER admin on >adminpass ~* +@all

# List users
ACL LIST

# Check current user
ACL WHOAMI

Real-World Setup:

# Application user (read/write specific keys)
ACL SETUSER app_user on >app_secret \
  ~cache:* ~session:* \
  +get +set +del +expire +ttl

# Analytics user (read-only)
ACL SETUSER analytics_user on >analytics_secret \
  ~* \
  +get +keys +scan +info

# Admin user (full access)
ACL SETUSER admin_user on >admin_secret \
  ~* \
  +@all

Python Client:

import redis

# Connect with ACL user
r = redis.Redis(
    host='localhost',
    port=6379,
    username='app_user',
    password='app_secret',
    decode_responses=True
)

# This works
r.set('cache:user:123', 'data')
r.get('cache:user:123')

# This fails (no permission)
try:
    r.flushdb()
except redis.exceptions.NoPermissionError:
    print("Access denied")

Multi-Tenant Isolation

# Tenant A user
ACL SETUSER tenant_a on >tenant_a_pass \
  ~tenant_a:* \
  +get +set +del +expire +ttl +incr +decr

# Tenant B user
ACL SETUSER tenant_b on >tenant_b_pass \
  ~tenant_b:* \
  +get +set +del +expire +ttl +incr +decr

Application Code:

class TenantRedisClient:
    def __init__(self, tenant_id, password):
        self.tenant_id = tenant_id
        self.client = redis.Redis(
            host='localhost',
            port=6379,
            username=f'tenant_{tenant_id}',
            password=password
        )
    
    def set(self, key, value):
        """Set with tenant prefix."""
        full_key = f"tenant_{self.tenant_id}:{key}"
        return self.client.set(full_key, value)
    
    def get(self, key):
        """Get with tenant prefix."""
        full_key = f"tenant_{self.tenant_id}:{key}"
        return self.client.get(full_key)

# Usage
tenant_a = TenantRedisClient('a', 'tenant_a_pass')
tenant_a.set('user:123', 'data')  # Stored as tenant_a:user:123

tenant_b = TenantRedisClient('b', 'tenant_b_pass')
# Cannot access tenant_a's data

SSL/TLS Encryption

Redis Configuration:

# redis.conf
port 0
tls-port 6379
tls-cert-file /path/to/redis.crt
tls-key-file /path/to/redis.key
tls-ca-cert-file /path/to/ca.crt
tls-auth-clients yes

Generate Certificates:

# Generate CA
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt

# Generate server certificate
openssl genrsa -out redis.key 4096
openssl req -new -key redis.key -out redis.csr
openssl x509 -req -days 3650 -in redis.csr -CA ca.crt -CAkey ca.key -out redis.crt

# Generate client certificate
openssl genrsa -out client.key 4096
openssl req -new -key client.key -out client.csr
openssl x509 -req -days 3650 -in client.csr -CA ca.crt -CAkey ca.key -out client.crt

Python Client with TLS:

import redis

r = redis.Redis(
    host='localhost',
    port=6379,
    username='app_user',
    password='app_secret',
    ssl=True,
    ssl_certfile='/path/to/client.crt',
    ssl_keyfile='/path/to/client.key',
    ssl_ca_certs='/path/to/ca.crt'
)

# All traffic encrypted
r.set('key', 'value')

ACL Configuration File

# users.acl
user default off
user admin on >admin_secret ~* +@all
user app_user on >app_secret ~cache:* ~session:* +get +set +del +expire
user readonly on >readonly_secret ~* +get +keys +scan

Load ACL File:

# redis.conf
aclfile /etc/redis/users.acl

Monitoring and Auditing

# Check ACL logs
ACL LOG

# Reset ACL logs
ACL LOG RESET

# Get user info
ACL GETUSER app_user

Python Monitoring:

def audit_acl_violations():
    """Monitor ACL violations."""
    r = redis.Redis(
        host='localhost',
        username='admin_user',
        password='admin_secret'
    )
    
    logs = r.acl_log()
    
    for log in logs:
        print(f"User: {log['username']}")
        print(f"Reason: {log['reason']}")
        print(f"Context: {log['context']}")
        print(f"Object: {log['object']}")
        print("---")

Results

Security:

  • Multi-tenant isolation: 100%
  • Unauthorized access attempts: 0
  • Data breaches: 0
  • Security audit: Passed

Performance:

  • TLS overhead: <5%
  • ACL check overhead: <1%
  • Overall impact: Negligible

Compliance:

  • GDPR: Compliant
  • SOC 2: Compliant
  • PCI DSS: Compliant

Migration Strategy

# Gradual migration
class RedisClientV6:
    def __init__(self, use_acl=False, use_tls=False):
        if use_acl and use_tls:
            # Full security
            self.client = redis.Redis(
                host='localhost',
                port=6379,
                username='app_user',
                password='app_secret',
                ssl=True,
                ssl_certfile='/path/to/client.crt',
                ssl_keyfile='/path/to/client.key',
                ssl_ca_certs='/path/to/ca.crt'
            )
        elif use_acl:
            # ACL only
            self.client = redis.Redis(
                host='localhost',
                port=6379,
                username='app_user',
                password='app_secret'
            )
        else:
            # Legacy
            self.client = redis.Redis(
                host='localhost',
                port=6379,
                password='old_password'
            )

Lessons Learned

  1. ACL essential for multi-tenant: Perfect isolation
  2. TLS overhead minimal: <5% performance impact
  3. Gradual migration works: No downtime
  4. Audit logs critical: Track violations
  5. Certificate management important: Automate renewal

Conclusion

Redis 6.0 ACL and TLS transformed our security posture. Zero breaches, perfect tenant isolation.

Key takeaways:

  1. ACL: Perfect multi-tenant isolation
  2. TLS: Encrypted traffic
  3. Performance impact: <5%
  4. Security audit: Passed
  5. Zero breaches since upgrade

Upgrade to Redis 6.0. Security is worth it.