Policy Hot Reload
jGuard supports zero-downtime policy updates through hot reload. This allows security policies to be updated at runtime without restarting the JVM.
Overviewβ
Hot reload monitors external policy files and atomically swaps the active policy when changes are detected.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Runtime β
β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β PolicyLoader βββββΆβ FileWatcher βββββΆβ Atomic β β
β β β β (poll-based) β β Swap β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β β β β
β βΌ βΌ βΌ β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β policies/ β β Detect β β AtomicRef β β
β β *.bin files β β Changes β β <Enforcer> β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
When to Use Hot Reloadβ
Enable Hot Reload When:β
| Scenario | Reason |
|---|---|
| Rapid incident response | Block compromised capabilities without downtime |
| A/B policy testing | Test policy changes on production traffic |
| Gradual rollouts | Phase in restrictions incrementally |
| Development/staging | Iterate on policies without restarts |
| Multi-tenant environments | Update tenant policies independently |
Disable Hot Reload When:β
| Scenario | Reason |
|---|---|
| High-security environments | Prevent runtime policy tampering |
| Immutable infrastructure | Policies baked into container images |
| Audit requirements | Policy changes require restart/approval |
| Air-gapped systems | No external policy directory needed |
| Performance-critical | Eliminate file polling overhead |
Configurationβ
Gradle Pluginβ
jguardPolicy {
// Enable hot reload
hotReload = true
// Poll interval in seconds (default: 5)
hotReloadInterval = 2
// External policy directories
externalPoliciesSourceDir = file("policies-src")
externalPoliciesOutputDir = file("policies")
}
System Propertiesβ
java -Djguard.reload=true \
-Djguard.reload.interval=5 \
-Djguard.policy.override=/etc/myapp/policies \
-javaagent:jguard-agent.jar \
-jar app.jar
| Property | Default | Description |
|---|---|---|
jguard.reload | false | Enable hot reload |
jguard.reload.interval | 5 | Poll interval in seconds |
jguard.policy.override | β | Directory for external policies |
Example: Incident Responseβ
A vulnerability is discovered in a third-party library. Block its network access immediately without restarting.
Initial Stateβ
The application is running with this external policy:
// policies-src/com.vendor.library.jguard
security module com.vendor.library {
entitle module to network.outbound("api.vendor.com", 443);
entitle module to fs.read("config", "**");
}
Incident Responseβ
- Edit the policy to deny network access:
// policies-src/com.vendor.library.jguard
security module com.vendor.library {
// INCIDENT: CVE-2024-XXXX - block all network
deny module to network.outbound;
entitle module to fs.read("config", "**");
}
- Compile the updated policy:
./gradlew compileExternalPolicies
- Within the poll interval, the agent detects the change:
[INFO] PolicyReloader - Change detected: com.vendor.library.bin
[INFO] PolicyReloader - Policy reloaded successfully
[INFO] PolicyReloader - Denied capabilities: network.outbound
- The library is now blocked from network access - no restart required.
Example: Gradual Restriction Rolloutβ
Restrict a library's capabilities incrementally to avoid breaking changes.
Week 1: Audit Modeβ
security module legacy.library {
// Audit what the library actually uses
entitle module to network.outbound;
entitle module to threads.create;
entitle module to fs.read("data", "**");
}
Week 2: Restrict Networkβ
After audit shows the library only calls api.internal.com:
security module legacy.library {
// Restrict to observed usage
entitle module to network.outbound("api.internal.com", 443);
entitle module to threads.create;
entitle module to fs.read("data", "**");
}
Week 3: Restrict Threadsβ
After confirming thread usage pattern:
security module legacy.library {
entitle module to network.outbound("api.internal.com", 443);
// Remove thread creation - not actually needed
// entitle module to threads.create;
entitle module to fs.read("data", "**");
}
Example: Multi-Tenant Policy Updatesβ
Update policies for specific tenants without affecting others.
Directory Structureβ
/etc/myapp/policies/
βββ _global.bin # Shared restrictions
βββ tenant.acme.bin # Acme Corp policies
βββ tenant.globex.bin # Globex policies
βββ tenant.initech.bin # Initech policies
Update Single Tenantβ
// policies-src/tenant.acme.jguard
security module tenant.acme {
// Acme requested additional network access
entitle module to network.outbound("*.acme.com", 443);
entitle module to network.outbound("api.partner.com", 443); // NEW
entitle module to fs.read("tenants/acme", "**");
}
Compile and the change affects only Acme's module.
Error Handlingβ
Compile-Time Validationβ
Syntax errors are caught during compilation, before they can affect the running system:
$ ./gradlew compileExternalPolicies
> Task :compileExternalPolicies FAILED
Error: policies-src/com.vendor.library.jguard:5:12
Unknown capability: network.inbound
Did you mean: network.listen?
The agent continues using the previous valid policy.
Runtime Error Handlingβ
| Scenario | Behavior |
|---|---|
Corrupted .bin file | Keep previous policy, log warning |
| Missing file | Keep previous policy, log warning |
| Invalid policy | Reject at compile time |
| File permission error | Keep previous policy, log error |
Log Outputβ
[INFO] PolicyReloader - Watching: /etc/myapp/policies
[INFO] PolicyReloader - Poll interval: 5 seconds
[INFO] PolicyReloader - Change detected: com.vendor.library.bin
[INFO] PolicyReloader - Validating new policy...
[INFO] PolicyReloader - Policy reloaded successfully
[WARN] PolicyReloader - File corrupted, keeping previous: tenant.acme.bin
Atomic Swap Guaranteeβ
Policy updates are atomic - there's no window where an incomplete policy is active:
// Internal implementation uses AtomicReference
private final AtomicReference<PolicyEnforcer> enforcer = new AtomicReference<>();
void reload(PolicyEnforcer newEnforcer) {
// Atomic swap - no partial state
enforcer.set(newEnforcer);
}
In-flight operations complete with the old policy. New operations use the new policy.
Security Considerationsβ
Protecting the Policy Directoryβ
The external policy directory is a security-sensitive location:
# Restrict write access
chmod 750 /etc/myapp/policies
chown root:security /etc/myapp/policies
# Consider SELinux/AppArmor policies
# Only allow the deployment system to write
Disabling Hot Reload in Productionβ
For high-security environments, disable hot reload entirely:
jguardPolicy {
hotReload = false
// Policies baked into signed JARs only
}
Or via system property:
java -Djguard.reload=false -javaagent:jguard-agent.jar -jar app.jar
Immutable Container Deploymentsβ
For container deployments with immutable infrastructure:
# Dockerfile
FROM eclipse-temurin:21-jre
# Bake policies into image
COPY policies/*.bin /app/policies/
# Disable hot reload - policies are immutable
ENV JAVA_OPTS="-Djguard.reload=false"
COPY app.jar /app/
ENTRYPOINT ["java", "-javaagent:jguard-agent.jar", "-jar", "/app/app.jar"]
Audit Trailβ
For compliance, log all policy changes:
# Monitor policy directory for changes
inotifywait -m -e modify,create,delete /etc/myapp/policies/ | \
while read event; do
echo "$(date) POLICY_CHANGE: $event" >> /var/log/security/policy-audit.log
done
Performance Impactβ
| Configuration | Overhead |
|---|---|
| Hot reload disabled | None |
| Hot reload enabled (5s interval) | ~1ms per interval (file stat) |
| Hot reload enabled (1s interval) | ~1ms per interval |
The overhead is minimal - just file modification time checks, not file reads.
Best Practicesβ
- Use short intervals for development (1-2 seconds)
- Use longer intervals for production (30-60 seconds) if enabled
- Disable in high-security environments where policy changes require restart
- Protect the policy directory with appropriate permissions
- Monitor policy changes for audit and alerting
- Test policy changes in staging before production hot reload
- Keep previous policy versions for rollback
Next Stepsβ
- External Policies - Grant/deny semantics
- Gradle Plugin - Build configuration
- Multi-Module Applications - Module isolation