This document outlines the production-grade security measures implemented in VelocityGate, covering authentication, data protection, and infrastructure security.
Hardcoding credentials is strictily forbidden. VelocityGate supports hierarchical configuration loading suitable for containerized and cloud-native environments.
All sensitive keys must be injected as environment variables at runtime.
| Variable | Description | Example |
|---|---|---|
DB_PASSWORD |
PostgreSQL password | super_secret_db_pass |
REDIS_PASSWORD |
Redis auth token | secure_redis_token |
JWT_SECRET |
Signing key for tokens | base64_encoded_256_bit_random |
API_KEY_PEPPER |
Server-side secret for hashing | random_pepper_string |
In Kubernetes, secrets are mounted as environment variables or files.
# deployment.yaml
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: gateway-secrets
key: db-passwordFor dynamic secret rotation, we integrate with AWS Secrets Manager using the Spring Cloud AWS starter.
// AWS Secrets Manager Integration
@Bean
public SecretsManagerClient secretsManagerClient() {
return SecretsManagerClient.builder()
.region(Region.US_EAST_1)
.build();
}User passwords are never stored in plain text. We use BCrypt with a work factor of 12.
- Algorithm: BCrypt
- Cost Factor: 12 (adjustable based on hardware)
- Salt: Randomly generated per user (handled by BCrypt)
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}API Keys are equivalent to passwords. Storing them plain-text allows an attacker with DB access to impersonate users.
- Strategy:
SHA-256(apiKey + global_pepper) - Pepper: A secret string stored in environment variables (NOT in the DB).
- Comparison: Constant-time comparison to prevent timing attacks.
public static String secureHash(String key, String pepper) {
return Hashing.sha256()
.hashString(key + pepper, StandardCharsets.UTF_8)
.toString();
}- Current:
HS256(Symmetric) - Good for monolithic/internal speed. - Production Recommendation:
RS256(Asymmetric).- Why?: The Gateway (Private Key) signs tokens. Downstream services (Public Key) verify them without needing the secret. This prevents key leakage in microservices.
Token Rotation Policy:
Access Token: 15 minutes TTL.Refresh Token: 7 days TTL, sliding window.- Revocation: Refresh tokens are stored in DB/Redis and can be revoked instantly.
Production traffic must use HTTPS.
- Redirect: HTTP -> HTTPS redirect enabled at Load Balancer or Gateway level.
- HSTS:
Strict-Transport-Securityheader enforced (max-age=31536000; includeSubDomains).
Restrict Cross-Origin Resource Sharing to known domains only.
spring:
cloud:
gateway:
globalcors:
cors-configurations:
"[/**]":
allowedOrigins: "https://app.velocitygate.com"
allowedMethods: "GET,POST,PUT,DELETE"
allowedHeaders: "Authorization,Content-Type,X-API-Key"VelocityGate injects standard security headers into every response to protect clients.
| Header | Value | Purpose |
|---|---|---|
X-Content-Type-Options |
nosniff |
Prevents MIME-type sniffing. |
X-Frame-Options |
DENY |
Prevents Clickjacking. |
X-XSS-Protection |
1; mode=block |
Enables browser XSS filters. |
Content-Security-Policy |
default-src 'self' |
Mitigates XSS/Injection attacks. |
- IP-based Limiting: (WAF Level) Blocks malicious bots/scrapers before they hit the app.
- API-Key Limiting: (Gateway Level) Enforces business quotas (e.g., 100 RPS per user).
- Fail-Closed: Ideally, if rate limiting fails, we should block traffic to protect the backend.
- Fail-Open: In some high-availability contexts, we allow traffic if Redis is down (See
RESILIENCE.md).
Loads the "Pepper" secret safely.
@Component
public class SecurityConfig {
@Value("${API_KEY_PEPPER}")
private String apiKeyPepper;
@PostConstruct
public void validate() {
if (apiKeyPepper == null || apiKeyPepper.length() < 32) {
throw new IllegalStateException("API_KEY_PEPPER must be set and >32 chars!");
}
}
}Uses SecureRandom instead of Random.
public class SecureKeyGenerator {
private static final SecureRandom random = new SecureRandom();
private static final Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding();
public static String generateKey() {
byte[] buffer = new byte[32]; // 256 bits of entropy
random.nextBytes(buffer);
return "vg_" + encoder.encodeToString(buffer); // Prefix 'vg_' for identification
}
}Prevents timing attacks when validating hashes.
public boolean validateHash(String input, String expected) {
return MessageDigest.isEqual(
input.getBytes(StandardCharsets.UTF_8),
expected.getBytes(StandardCharsets.UTF_8)
);
}