Cloud IP Store¶
CloudIpStoreProtocol defines the pluggable backend for cached cloud-provider IP ranges. Guard-core ships two concrete implementations — InMemoryCloudIpStore and RedisCloudIpStore — and the active store can be swapped via SecurityConfig.cloud_ip_store.
CloudIpStoreProtocol¶
Location: guard_core/protocols/cloud_ip_store_protocol.py
from typing import Protocol, runtime_checkable
@runtime_checkable
class CloudIpStoreProtocol(Protocol):
async def get(self, provider: str) -> set[str] | None: ...
async def set(
self, provider: str, ranges: set[str], ttl: int | None = None
) -> None: ...
async def clear(self) -> None: ...
| Method | Description |
|---|---|
get(provider) |
Return the cached CIDR set for the provider, or None if absent. |
set(provider, ranges, ttl=) |
Cache the CIDR set for the provider. ttl is honored by stores that support it. |
clear() |
Drop all cached entries. |
A sync mirror lives at guard_core/sync/protocols/cloud_ip_store_protocol.py as SyncCloudIpStoreProtocol.
InMemoryCloudIpStore¶
Location: guard_core/handlers/cloud_ip_stores.py
class InMemoryCloudIpStore:
def __init__(self) -> None: ...
async def get(self, provider: str) -> set[str] | None: ...
async def set(self, provider: str, ranges: set[str], ttl: int | None = None) -> None: ...
async def clear(self) -> None: ...
Process-local dictionary backed store. The ttl argument is accepted for protocol compatibility but is not enforced. This is the default store when no Redis is configured.
RedisCloudIpStore¶
Location: guard_core/handlers/cloud_ip_stores.py
class RedisCloudIpStore:
def __init__(
self,
redis_handler: RedisHandlerProtocol,
key_prefix: str = "cloud_ip",
) -> None: ...
async def get(self, provider: str) -> set[str] | None: ...
async def set(self, provider: str, ranges: set[str], ttl: int | None = None) -> None: ...
async def clear(self) -> None: ...
Redis-backed store. Each provider's CIDR set is JSON-encoded as a sorted list and written under <redis_prefix><key_prefix>:<provider> (the RedisManager.set_key path already prepends config.redis_prefix, so key_prefix should not duplicate it). set() honors the optional ttl. clear() removes every key under the resolved prefix.
Backends¶
Default (auto-constructed)¶
When enable_redis=True and block_cloud_providers is set, guard-core automatically wires a RedisCloudIpStore during HandlerInitializer.initialize_redis_handlers() so cloud-IP ranges persist across worker restarts and stay shared across replicas. No cloud_ip_store= setting is required.
from guard_core.models import SecurityConfig
config = SecurityConfig(
enable_redis=True,
redis_url="redis://localhost:6379",
redis_prefix="myapp:guard:",
block_cloud_providers={"AWS", "GCP", "Azure"},
)
Cloud-IP ranges land at Redis keys like myapp:guard:cloud_ip:AWS. The redundant guard: segment in the previous key_prefix default was removed in this release (see CHANGELOG); RedisManager.set_key already prepends redis_prefix, so the key_prefix no longer duplicates it.
When enable_redis=False (or no Redis URL is reachable), the same path falls back to InMemoryCloudIpStore — fine for single-process deployments, lost on restart.
Custom prefix or implementation via callable¶
cloud_ip_store accepts a CloudIpStoreFactory callable: Callable[[RedisHandlerProtocol], CloudIpStoreProtocol]. The handler initializer invokes it with the live Redis handler once Redis is up, so user code never has to construct a throwaway RedisManager purely to feed the store.
from guard_core.handlers.cloud_ip_stores import RedisCloudIpStore
from guard_core.models import SecurityConfig
config = SecurityConfig(
enable_redis=True,
redis_url="redis://localhost:6379",
cloud_ip_store=lambda redis: RedisCloudIpStore(redis, key_prefix="cloud_ip_v2"),
)
Custom non-Redis implementation via instance¶
If the store does not need a Redis handle (in-memory, DynamoDB, SQL, etc.), pass an instance directly:
from guard_core.models import SecurityConfig
config = SecurityConfig(
cloud_ip_store=MyDynamoDBCloudIpStore(table_name="cloud_ips"),
)
The instance is wired straight into cloud_handler.set_store(...) after Redis bootstrap. Combined with lazy_init=True (the default), the first cloud-IP fetch happens in a background task — application startup does not block on it.
Redis namespace migration¶
The v2.0.0 release moved the cloud-IP cache from the legacy cloud_ranges namespace (comma-separated CSV values per provider) to cloud_ip (JSON-encoded sorted list per provider, prefixed by redis_prefix).
- Default writers —
RedisCloudIpStorewrites to<redis_prefix>cloud_ip:<provider>. - Legacy reader (dead code) — the legacy CSV path under
cloud_rangesis gated behindCloudManager._store is None. BecauseCloudManager.__new__always seeds_storewith anInMemoryCloudIpStore()and nothing resets it toNone, that branch is unreachable at runtime; the default and the Redis store both use the newcloud_ipnamespace.
Any ops tooling, dashboards, or sidecars reading those Redis keys directly must switch to the new namespace. On upgrade from a previous release, the cache invalidates once and repopulates within cloud_ip_refresh_interval.