|
| 1 | +# TLS Tickets Support Design Document |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +This document describes the design and implementation of TLS session ticket support in the Scylla Python driver. TLS session tickets allow for quick TLS renegotiation by resuming previous TLS sessions, reducing the overhead of full handshakes when reconnecting to servers. |
| 6 | + |
| 7 | +## Background |
| 8 | + |
| 9 | +### What are TLS Session Tickets? |
| 10 | + |
| 11 | +TLS session tickets (RFC 5077 and RFC 8446 for TLS 1.3) allow clients to cache session state and reuse it for subsequent connections. This provides: |
| 12 | + |
| 13 | +- **Faster reconnections**: Reduced handshake latency by resuming previous sessions |
| 14 | +- **Less CPU usage**: Fewer cryptographic operations during reconnection |
| 15 | +- **Better performance**: Especially important for connection pools that frequently reconnect |
| 16 | + |
| 17 | +### Python SSL Support |
| 18 | + |
| 19 | +Python's `ssl` module provides built-in support for TLS session resumption: |
| 20 | + |
| 21 | +- `SSLContext.num_tickets`: Controls the number of TLS 1.3 session tickets (default: 2) |
| 22 | +- `SSLSocket.session`: Returns the current session as an `SSLSession` object |
| 23 | +- `SSLSocket.session_reused`: Boolean indicating if the session was reused |
| 24 | +- `SSLContext.wrap_socket(..., session=...)`: Allows passing a session to reuse |
| 25 | + |
| 26 | +## Current State |
| 27 | + |
| 28 | +The driver currently: |
| 29 | +1. Uses `SSLContext` for TLS connections |
| 30 | +2. Creates new TLS sessions for each connection |
| 31 | +3. Does NOT cache or reuse TLS sessions |
| 32 | +4. Does NOT track session statistics |
| 33 | + |
| 34 | +## Design |
| 35 | + |
| 36 | +### Goals |
| 37 | + |
| 38 | +1. **Enable TLS tickets by default** when SSL/TLS is enabled |
| 39 | +2. **Implement client-side session cache** to store and reuse sessions |
| 40 | +3. **Minimal API changes** - work transparently with existing SSL configuration |
| 41 | +4. **Thread-safe** session cache for concurrent connections |
| 42 | +5. **Per-endpoint session tracking** to reuse sessions for the same server |
| 43 | + |
| 44 | +### Components |
| 45 | + |
| 46 | +#### 1. TLS Session Cache |
| 47 | + |
| 48 | +A thread-safe cache that stores TLS sessions per endpoint (host:port). |
| 49 | + |
| 50 | +```python |
| 51 | +class TLSSessionCache: |
| 52 | + """Thread-safe cache for TLS sessions.""" |
| 53 | + |
| 54 | + def __init__(self, max_size=100, ttl=3600): |
| 55 | + """ |
| 56 | + Args: |
| 57 | + max_size: Maximum number of sessions to cache |
| 58 | + ttl: Time-to-live for cached sessions in seconds |
| 59 | + """ |
| 60 | + self._sessions = {} # {endpoint_key: (session, timestamp)} |
| 61 | + self._lock = threading.RLock() |
| 62 | + self._max_size = max_size |
| 63 | + self._ttl = ttl |
| 64 | + |
| 65 | + def get_session(self, endpoint_key): |
| 66 | + """Get cached session for endpoint, if valid.""" |
| 67 | + pass |
| 68 | + |
| 69 | + def set_session(self, endpoint_key, session): |
| 70 | + """Store session for endpoint.""" |
| 71 | + pass |
| 72 | + |
| 73 | + def clear_expired(self): |
| 74 | + """Remove expired sessions.""" |
| 75 | + pass |
| 76 | +``` |
| 77 | + |
| 78 | +#### 2. Integration Points |
| 79 | + |
| 80 | +##### Cluster Level |
| 81 | +- Add `tls_session_cache` attribute to `Cluster` class |
| 82 | +- Initialize cache when `ssl_context` or `ssl_options` is provided |
| 83 | +- Share cache across all connections in the cluster |
| 84 | + |
| 85 | +##### Connection Level |
| 86 | +- Modify `Connection._wrap_socket_from_context()` to: |
| 87 | + 1. Check cache for existing session for the endpoint |
| 88 | + 2. Pass cached session to `wrap_socket()` if available |
| 89 | + 3. Store new session after successful connection |
| 90 | +- Track session reuse statistics |
| 91 | + |
| 92 | +### Implementation Details |
| 93 | + |
| 94 | +#### Session Cache Key |
| 95 | + |
| 96 | +Use a tuple of `(host, port)` as the cache key to uniquely identify endpoints. |
| 97 | + |
| 98 | +#### Session Expiration |
| 99 | + |
| 100 | +- Default TTL: 1 hour (3600 seconds) |
| 101 | +- Sessions older than TTL are not reused |
| 102 | +- Periodic cleanup of expired sessions |
| 103 | + |
| 104 | +#### Cache Size Management |
| 105 | + |
| 106 | +- Default max size: 100 sessions |
| 107 | +- When cache is full, remove oldest sessions (LRU policy) |
| 108 | + |
| 109 | +#### Statistics Tracking |
| 110 | + |
| 111 | +Add connection-level attributes: |
| 112 | +- `session_reused`: Boolean indicating if current connection reused a session |
| 113 | +- Existing `SSLContext.session_stats()` can be queried for overall statistics |
| 114 | + |
| 115 | +### Configuration |
| 116 | + |
| 117 | +#### Cluster Configuration |
| 118 | + |
| 119 | +Users can configure TLS session caching via new parameters: |
| 120 | + |
| 121 | +```python |
| 122 | +cluster = Cluster( |
| 123 | + contact_points=['127.0.0.1'], |
| 124 | + ssl_context=ssl_context, |
| 125 | + tls_session_cache_enabled=True, # Default: True |
| 126 | + tls_session_cache_size=100, # Default: 100 |
| 127 | + tls_session_cache_ttl=3600 # Default: 3600 seconds |
| 128 | +) |
| 129 | +``` |
| 130 | + |
| 131 | +For backward compatibility, TLS session caching is **enabled by default** when SSL is configured. |
| 132 | + |
| 133 | +#### Disabling Session Cache |
| 134 | + |
| 135 | +Users can disable session caching by setting: |
| 136 | +```python |
| 137 | +cluster = Cluster( |
| 138 | + ..., |
| 139 | + tls_session_cache_enabled=False |
| 140 | +) |
| 141 | +``` |
| 142 | + |
| 143 | +## Implementation Plan |
| 144 | + |
| 145 | +### Phase 1: Core Implementation |
| 146 | + |
| 147 | +1. **Create TLSSessionCache class** in `cassandra/connection.py` |
| 148 | + - Thread-safe dictionary-based cache |
| 149 | + - TTL and max_size management |
| 150 | + - LRU eviction policy |
| 151 | + |
| 152 | +2. **Modify Cluster class** in `cassandra/cluster.py` |
| 153 | + - Add configuration parameters |
| 154 | + - Initialize session cache when SSL is enabled |
| 155 | + - Pass cache to connections |
| 156 | + |
| 157 | +3. **Modify Connection class** in `cassandra/connection.py` |
| 158 | + - Accept session cache in constructor |
| 159 | + - Implement session retrieval and storage |
| 160 | + - Update `_wrap_socket_from_context()` to use cached sessions |
| 161 | + |
| 162 | +### Phase 2: Testing |
| 163 | + |
| 164 | +1. **Unit tests** |
| 165 | + - Test TLSSessionCache operations |
| 166 | + - Test cache expiration and eviction |
| 167 | + - Test thread safety |
| 168 | + |
| 169 | +2. **Integration tests** |
| 170 | + - Test session reuse across connections |
| 171 | + - Test with real SSL/TLS connections |
| 172 | + - Verify performance improvements |
| 173 | + |
| 174 | +### Phase 3: Documentation |
| 175 | + |
| 176 | +1. Update API documentation |
| 177 | +2. Add usage examples |
| 178 | +3. Document configuration options |
| 179 | + |
| 180 | +## Testing Strategy |
| 181 | + |
| 182 | +### Unit Tests |
| 183 | + |
| 184 | +1. **TLSSessionCache Tests** |
| 185 | + - Test get/set operations |
| 186 | + - Test TTL expiration |
| 187 | + - Test max size and LRU eviction |
| 188 | + - Test thread safety |
| 189 | + |
| 190 | +2. **Connection Tests** |
| 191 | + - Mock SSLContext and SSLSocket |
| 192 | + - Verify session is retrieved from cache |
| 193 | + - Verify session is stored after connection |
| 194 | + - Test with cache disabled |
| 195 | + |
| 196 | +### Integration Tests |
| 197 | + |
| 198 | +1. **SSL Connection Tests** (extend existing `tests/integration/long/test_ssl.py`) |
| 199 | + - Connect to SSL-enabled cluster |
| 200 | + - Verify first connection creates new session |
| 201 | + - Verify second connection reuses session |
| 202 | + - Check `session_reused` attribute |
| 203 | + - Verify session stats from `SSLContext.session_stats()` |
| 204 | + |
| 205 | +2. **Performance Tests** |
| 206 | + - Measure connection time with/without session reuse |
| 207 | + - Verify reduced handshake latency |
| 208 | + |
| 209 | +### Test with Different SSL Configurations |
| 210 | + |
| 211 | +- Test with `ssl_context` directly provided |
| 212 | +- Test with `ssl_options` (legacy mode) |
| 213 | +- Test with cloud config |
| 214 | +- Test with twisted/eventlet reactors |
| 215 | + |
| 216 | +## Security Considerations |
| 217 | + |
| 218 | +1. **Session Security**: TLS sessions contain sensitive cryptographic material |
| 219 | + - Sessions are stored in memory only (not persisted) |
| 220 | + - Sessions expire after TTL |
| 221 | + - Sessions are not shared across different clusters |
| 222 | + |
| 223 | +2. **Host Validation**: Sessions are cached per endpoint |
| 224 | + - Sessions for host A are not used for host B |
| 225 | + - Hostname verification still occurs on each connection |
| 226 | + |
| 227 | +3. **Backward Compatibility**: |
| 228 | + - Feature is enabled by default but transparent |
| 229 | + - No breaking changes to existing API |
| 230 | + - Can be disabled if needed |
| 231 | + |
| 232 | +## Performance Impact |
| 233 | + |
| 234 | +### Expected Benefits |
| 235 | + |
| 236 | +- **Reduced connection time**: 20-50% faster reconnections |
| 237 | +- **Lower CPU usage**: Fewer cryptographic operations |
| 238 | +- **Better throughput**: Especially for workloads with frequent reconnections |
| 239 | + |
| 240 | +### Overhead |
| 241 | + |
| 242 | +- **Memory**: Minimal (~1KB per cached session) |
| 243 | +- **Cache management**: O(1) operations with occasional O(n) cleanup |
| 244 | + |
| 245 | +## Alternatives Considered |
| 246 | + |
| 247 | +### 1. Global Session Cache |
| 248 | + |
| 249 | +**Rejected**: Would share sessions across different clusters, which could be confusing and less secure. |
| 250 | + |
| 251 | +### 2. No TTL/Expiration |
| 252 | + |
| 253 | +**Rejected**: Sessions could become stale or accumulate indefinitely. |
| 254 | + |
| 255 | +### 3. Disable by Default |
| 256 | + |
| 257 | +**Rejected**: Session resumption is a standard TLS feature and should be enabled by default for better performance. |
| 258 | + |
| 259 | +## Future Enhancements |
| 260 | + |
| 261 | +1. **Configurable eviction policies**: LRU, LFU, FIFO |
| 262 | +2. **Session statistics**: Track cache hit/miss rates |
| 263 | +3. **Metrics integration**: Export session reuse metrics |
| 264 | +4. **Session serialization**: Persist sessions across driver restarts (optional) |
| 265 | + |
| 266 | +## References |
| 267 | + |
| 268 | +- [RFC 5077 - TLS Session Resumption without Server-Side State](https://tools.ietf.org/html/rfc5077) |
| 269 | +- [RFC 8446 - The Transport Layer Security (TLS) Protocol Version 1.3](https://tools.ietf.org/html/rfc8446) |
| 270 | +- [Python ssl module documentation](https://docs.python.org/3/library/ssl.html) |
0 commit comments