256 lines
8.4 KiB
Markdown
256 lines
8.4 KiB
Markdown
# CORS Implementation - Security Enhancement
|
|
|
|
## Overview
|
|
|
|
This document describes the implementation of a secure CORS (Cross-Origin Resource Sharing) configuration system that replaces the previous wildcard (`*`) configuration with an explicit origin whitelist to prevent CSRF attacks.
|
|
|
|
## Implementation Summary
|
|
|
|
### 1. CORSConfig Structure (api/rest/router.go)
|
|
|
|
Created a comprehensive `CORSConfig` struct with the following fields:
|
|
|
|
```go
|
|
type CORSConfig struct {
|
|
AllowedOrigins []string // Whitelist of allowed origins
|
|
AllowedMethods []string // Allowed HTTP methods
|
|
AllowedHeaders []string // Allowed HTTP headers
|
|
AllowCredentials bool // Whether to allow credentials
|
|
MaxAge int // Cache duration for preflight responses
|
|
}
|
|
```
|
|
|
|
**Key Security Features:**
|
|
- Wildcards (`*`) are explicitly NOT supported
|
|
- Origins must be explicitly whitelisted
|
|
- Credentials can only be enabled with specific origins
|
|
|
|
### 2. CORS Middleware Implementation
|
|
|
|
The `newCORSMiddleware()` function implements:
|
|
|
|
- **Origin Validation**: Checks incoming `Origin` header against whitelist
|
|
- **Exact Match Required**: Only sets CORS headers if origin is in whitelist
|
|
- **Never Uses Wildcard**: Always returns the exact origin, never `*`
|
|
- **Preflight Handling**: Properly handles OPTIONS requests
|
|
- **Configurable Headers**: All CORS headers are configurable
|
|
|
|
**Code Location:** `api/rest/router.go:98-149`
|
|
|
|
### 3. Configuration System
|
|
|
|
#### Config Structure (internal/config/config.go)
|
|
|
|
Added `CORSConfig` to the main application configuration:
|
|
|
|
```go
|
|
type CORSConfig struct {
|
|
AllowedOrigins []string `mapstructure:"allowed_origins"`
|
|
AllowedMethods []string `mapstructure:"allowed_methods"`
|
|
AllowedHeaders []string `mapstructure:"allowed_headers"`
|
|
AllowCredentials bool `mapstructure:"allow_credentials"`
|
|
MaxAge int `mapstructure:"max_age"`
|
|
}
|
|
```
|
|
|
|
#### Default Values (internal/config/config.go:205-210)
|
|
|
|
```go
|
|
AllowedOrigins: []string{} // Empty by default
|
|
AllowedMethods: []string{"GET", "POST", "OPTIONS", "DELETE"} // Standard REST methods
|
|
AllowedHeaders: []string{"Content-Type", "Authorization"} // Common headers
|
|
AllowCredentials: false // Disabled by default
|
|
MaxAge: 3600 // 1 hour
|
|
```
|
|
|
|
### 4. Configuration Validation (internal/config/config.go:263-282)
|
|
|
|
Implemented `validateCORS()` that enforces:
|
|
|
|
1. **Wildcard Rejection**: `*` is not allowed in `allowed_origins`
|
|
2. **Origin Format Validation**: Origins must start with `http://` or `https://`
|
|
3. **Credentials Validation**: `allow_credentials` requires at least one origin
|
|
|
|
**Example Error Messages:**
|
|
- `"wildcard (*) is not allowed in CORS allowed_origins for security reasons - specify exact origins instead"`
|
|
- `"invalid origin format: example.com - origins must start with http:// or https://"`
|
|
- `"allow_credentials is enabled but no origins are allowed - this configuration is ineffective"`
|
|
|
|
### 5. Updated Server Initialization (cmd/server/main.go:288-326)
|
|
|
|
The `startRESTServer()` function now:
|
|
|
|
1. Converts config CORS to `rest.CORSConfig`
|
|
2. Logs CORS configuration on startup
|
|
3. Warns if no origins are configured
|
|
4. Passes CORS config to router
|
|
|
|
**Example Log Output:**
|
|
```
|
|
CORS enabled for origins: [http://localhost:3000 http://localhost:8080]
|
|
```
|
|
|
|
Or:
|
|
```
|
|
CORS has no allowed origins configured - all cross-origin requests will be blocked
|
|
```
|
|
|
|
### 6. Configuration Example (config.yaml:112-146)
|
|
|
|
#### Development Configuration
|
|
```yaml
|
|
cors:
|
|
allowed_origins:
|
|
- "http://localhost:3000" # React/Next.js dev port
|
|
- "http://localhost:8080" # Vue/Angular dev port
|
|
- "http://localhost:5173" # Vite dev server
|
|
allowed_methods:
|
|
- "GET"
|
|
- "POST"
|
|
- "OPTIONS"
|
|
- "DELETE"
|
|
allowed_headers:
|
|
- "Content-Type"
|
|
- "Authorization"
|
|
allow_credentials: false
|
|
max_age: 3600
|
|
```
|
|
|
|
#### Production Configuration (Example)
|
|
```yaml
|
|
cors:
|
|
allowed_origins:
|
|
- "https://app.example.com"
|
|
- "https://dashboard.example.com"
|
|
- "https://api-docs.example.com"
|
|
allowed_methods:
|
|
- "GET"
|
|
- "POST"
|
|
- "OPTIONS"
|
|
- "DELETE"
|
|
allowed_headers:
|
|
- "Content-Type"
|
|
- "Authorization"
|
|
allow_credentials: true # Enable for auth tokens
|
|
max_age: 3600
|
|
```
|
|
|
|
## Test Coverage
|
|
|
|
### 1. CORS Middleware Tests (api/rest/cors_test.go)
|
|
|
|
**Test Cases:**
|
|
- ✅ `TestCORSMiddleware_AllowedOrigin`: Verifies allowed origins are accepted
|
|
- ✅ `TestCORSMiddleware_BlockedOrigin`: Verifies non-whitelisted origins are rejected
|
|
- ✅ `TestCORSMiddleware_PreflightRequest`: Tests OPTIONS preflight handling
|
|
- ✅ `TestCORSMiddleware_Credentials`: Verifies credential header handling
|
|
- ✅ `TestCORSMiddleware_NoWildcard`: Ensures wildcard is never returned
|
|
- ✅ `TestCORSMiddleware_EmptyConfig`: Tests secure default (no origins)
|
|
- ✅ `TestCORSMiddleware_MaxAge`: Tests cache duration configuration
|
|
- ✅ `TestDefaultCORSConfig`: Verifies default configuration values
|
|
|
|
**Total: 8 test functions, 21 test cases**
|
|
|
|
### 2. CORS Validation Tests (internal/config/cors_test.go)
|
|
|
|
**Test Cases:**
|
|
- ✅ `TestValidateCORS_WildcardRejection`: Wildcard origins are rejected
|
|
- ✅ `TestValidateCORS_InvalidOriginFormat`: Invalid origin formats are rejected
|
|
- ✅ `TestValidateCORS_CredentialsWithoutOrigins`: Credentials require origins
|
|
- ✅ `TestValidateCORS_ValidConfigurations`: Valid configs are accepted
|
|
- ✅ `TestValidateCORS_MultipleOrigins`: Multiple origins with wildcard rejected
|
|
- ✅ `TestValidateCORS_EdgeCases`: Edge cases handled correctly
|
|
|
|
**Total: 6 test functions, 16 test cases**
|
|
|
|
## Security Improvements
|
|
|
|
### Before
|
|
```go
|
|
w.Header().Set("Access-Control-Allow-Origin", "*") // ❌ CSRF Vulnerability
|
|
```
|
|
|
|
### After
|
|
```go
|
|
// Only set CORS headers if origin is in whitelist
|
|
if allowed {
|
|
w.Header().Set("Access-Control-Allow-Origin", origin) // ✅ Exact origin
|
|
}
|
|
// Otherwise, no CORS headers are set (browser blocks the response)
|
|
```
|
|
|
|
## Security Guarantees
|
|
|
|
1. **No Wildcard**: The system makes it impossible to configure wildcard CORS
|
|
2. **Explicit Whitelist**: All allowed origins must be explicitly configured
|
|
3. **Validation at Startup**: Invalid configurations are rejected before the server starts
|
|
4. **Default Secure**: Empty origin list by default (most restrictive)
|
|
5. **Environment-Specific**: Different configs for dev/staging/production
|
|
|
|
## Migration Guide
|
|
|
|
### For Development
|
|
|
|
Update your `config.yaml` to include localhost origins:
|
|
|
|
```yaml
|
|
cors:
|
|
allowed_origins:
|
|
- "http://localhost:3000"
|
|
- "http://localhost:8080"
|
|
```
|
|
|
|
### For Production
|
|
|
|
Configure your production origins:
|
|
|
|
```yaml
|
|
cors:
|
|
allowed_origins:
|
|
- "https://yourdomain.com"
|
|
- "https://app.yourdomain.com"
|
|
allow_credentials: true
|
|
```
|
|
|
|
### Environment Variables
|
|
|
|
You can also configure CORS via environment variables:
|
|
|
|
```bash
|
|
export NOTIFIER_CORS_ALLOWED_ORIGINS="https://app.example.com,https://dashboard.example.com"
|
|
export NOTIFIER_CORS_ALLOW_CREDENTIALS=true
|
|
```
|
|
|
|
## Acceptance Criteria
|
|
|
|
✅ **CORS whitelist fully configurable** - via config.yaml or environment variables
|
|
✅ **Wildcard configuration is impossible** - validation rejects `*` at startup
|
|
✅ **Security headers properly set** - only for whitelisted origins
|
|
✅ **Environment-specific configs work** - examples provided for dev/prod
|
|
✅ **No CSRF vulnerability** - wildcard eliminated, exact origins only
|
|
✅ **Comprehensive tests** - 37 test cases covering all scenarios
|
|
|
|
## Files Modified
|
|
|
|
1. `api/rest/router.go` - CORS config struct and middleware
|
|
2. `api/rest/cors_test.go` - CORS middleware tests (NEW)
|
|
3. `internal/config/config.go` - CORS configuration structure
|
|
4. `internal/config/cors_test.go` - CORS validation tests (NEW)
|
|
5. `cmd/server/main.go` - Server initialization with CORS config
|
|
6. `config.yaml` - Example CORS configuration
|
|
7. `internal/auth/bootstrap.go` - Fixed unrelated string formatting bug
|
|
8. `internal/auth/keystore_hybrid.go` - Fixed unrelated rate limiter signature
|
|
|
|
## Additional Fixes
|
|
|
|
While implementing CORS, also fixed pre-existing build errors:
|
|
- String multiplication in `bootstrap.go` (changed to `strings.Repeat`)
|
|
- Rate limiter signature mismatch in `keystore_hybrid.go`
|
|
- Missing parameter in `service_retention_test.go`
|
|
|
|
## References
|
|
|
|
- **OWASP CORS**: https://owasp.org/www-community/attacks/csrf
|
|
- **MDN CORS**: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
|
- **RFC 6454**: The Web Origin Concept
|