Scheduled Rotation Policies#
The RotationScheduler runs as a background asyncio task and automatically rotates infrastructure based on configurable policies. It uses the same Terraform-based deploy pipeline as the manual infraguard rotate command.
Policy Types#
| Type | Trigger | Use Case |
|---|---|---|
schedule | Fixed time interval | Rotate every N hours regardless of burn state |
on_burn_detected | BurnDetector fires | Rotate when a domain is flagged as burned |
on_threshold | Request count | Rotate after N requests hit a domain (limits exposure) |
stagger | Time interval, one domain at a time | Roll through all domains sequentially with a delay between each |
Configuration#
rotation:
enabled: true
check_interval_seconds: 60
work_dir_base: "/opt/infraguard/deployments"
policies:
- name: "nightly-cycle"
type: "schedule"
enabled: true
interval_hours: 24
provider: "do"
region: "nyc1"
ssh_key: "/root/.ssh/id_ed25519.pub"
operator_ip: "1.2.3.4/32"
- name: "burn-response"
type: "on_burn_detected"
enabled: true
burn_cooldown_minutes: 5
provider: "aws"
ssh_key: "/root/.ssh/id_ed25519.pub"
operator_ip: "1.2.3.4/32"
- name: "request-cap"
type: "on_threshold"
enabled: true
request_threshold: 10000
threshold_window_seconds: 86400
provider: "do"
ssh_key: "/root/.ssh/id_ed25519.pub"
operator_ip: "1.2.3.4/32"
- name: "rolling-stagger"
type: "stagger"
enabled: true
interval_hours: 48
stagger_delay_minutes: 30
domains:
- "cdn.example.com"
- "static.example.com"
- "api.example.com"
provider: "do"
ssh_key: "/root/.ssh/id_ed25519.pub"
operator_ip: "1.2.3.4/32"How Each Policy Works#
Schedule#
Straightforward timer. The scheduler checks last_rotation and fires when interval_hours has elapsed. On first run, it records the current time as baseline and waits for the full interval before rotating.
On Burn Detected#
Reacts to the BurnDetector calling scheduler.notify_burn_detected(domain). The burn_cooldown_minutes setting adds a delay between detection and rotation so the operator has time to assess. If the burn alert is a false positive, the operator can clear it before rotation kicks in.
On Threshold#
Queries the tracking database for request count per domain within a rolling window. When any domain exceeds the threshold, a rotation fires. The window resets after each rotation.
Stagger#
Like schedule, but instead of rotating all domains at once, it queues them and rotates one at a time with stagger_delay_minutes between each. The first domain in the queue rotates immediately when the interval expires; subsequent domains rotate after the delay.
This avoids the blast radius of rotating everything simultaneously - if a rotation fails, only one domain is affected and the rest of the queue pauses.
CLI#
# List configured policies
infraguard schedule list
# Add a new policy
infraguard schedule add --name "every-12h" --type schedule --interval-hours 12 --provider do
# Remove a policy
infraguard schedule remove --name "every-12h"Safety#
- A minimum 120-second gap is enforced between any two rotations, regardless of policy.
- Policies that are mid-rotation are skipped on subsequent ticks (no re-entry).
- Rotation runs in a thread executor so it doesn’t block the asyncio event loop.
- Rotation events are dispatched through the plugin pipeline so Discord/Slack get notified.
Rotation History#
All rotation outcomes (success, failure, rollback) are recorded in-memory on the scheduler. The API exposes them:
curl -H "Authorization: Bearer $TOKEN" https://ig:8080/api/rotation/history