New doc supporting CORS, gRCP, and general client integration
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user