mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-06-05 23:03:48 +00:00
cedee416a8
* General improvements and bug fixes. * Improve tests coverage. * fixup! Improve tests coverage. * Update README.md with latest changes. * Fix the uint32 * Resolve issue with race condition for logging. * fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * Fix the test of the rate limiter * Add default ratelimit.json file * Update dependencies. * Significant refactor. * fixup! Significant refactor. * fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! fixup! fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! fixup! fixup! fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! fixup! fixup! fixup! fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025 * fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! Merge remote-tracking branch 'origin/main' into improvements-mid-apr-2025
675 lines
19 KiB
Go
675 lines
19 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
libpack_cache "github.com/lukaszraczylo/graphql-monitoring-proxy/cache"
|
|
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
|
"github.com/stretchr/testify/suite"
|
|
)
|
|
|
|
type IntegrationSecurityTestSuite struct {
|
|
suite.Suite
|
|
proxyApp *fiber.App
|
|
apiApp *fiber.App
|
|
logger *libpack_logger.Logger
|
|
tempDir string
|
|
validAPIKey string
|
|
}
|
|
|
|
func TestIntegrationSecurityTestSuite(t *testing.T) {
|
|
suite.Run(t, new(IntegrationSecurityTestSuite))
|
|
}
|
|
|
|
func (suite *IntegrationSecurityTestSuite) SetupTest() {
|
|
// Create temporary directory for test files
|
|
var err error
|
|
suite.tempDir, err = os.MkdirTemp("", "security_integration_test")
|
|
suite.NoError(err)
|
|
|
|
// Setup configuration
|
|
cfg = &config{}
|
|
cfg.Logger = libpack_logger.New()
|
|
suite.logger = cfg.Logger
|
|
|
|
// Configure security settings
|
|
suite.validAPIKey = "integration-test-api-key-secure-12345"
|
|
os.Setenv("GMP_ADMIN_API_KEY", suite.validAPIKey)
|
|
|
|
// Setup cache for testing
|
|
cacheConfig := &libpack_cache.CacheConfig{
|
|
Logger: cfg.Logger,
|
|
TTL: 60,
|
|
}
|
|
cacheConfig.Memory.MaxMemorySize = 10 * 1024 * 1024 // 10MB
|
|
cacheConfig.Memory.MaxEntries = 1000
|
|
libpack_cache.EnableCache(cacheConfig)
|
|
|
|
// Setup banned users file in temp directory
|
|
cfg.Api.BannedUsersFile = filepath.Join(suite.tempDir, "banned_users.json")
|
|
|
|
// Create test apps
|
|
suite.setupTestApps()
|
|
}
|
|
|
|
func (suite *IntegrationSecurityTestSuite) TearDownTest() {
|
|
// Clean up environment
|
|
os.Unsetenv("GMP_ADMIN_API_KEY")
|
|
os.Unsetenv("ADMIN_API_KEY")
|
|
|
|
// Clean up temporary directory
|
|
os.RemoveAll(suite.tempDir)
|
|
}
|
|
|
|
// tempDirShouldBeAllowed checks if the temp directory is in an allowed location
|
|
func (suite *IntegrationSecurityTestSuite) tempDirShouldBeAllowed() bool {
|
|
absPath, err := filepath.Abs(suite.tempDir)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
// Check if temp directory is in allowed locations
|
|
allowedPrefixes := []string{"/tmp/", "/var/tmp/"}
|
|
for _, prefix := range allowedPrefixes {
|
|
if strings.HasPrefix(absPath, prefix) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// Check if it's in the working directory
|
|
workDir, err := os.Getwd()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
cleanedWorkDir := filepath.Clean(workDir)
|
|
return strings.HasPrefix(absPath, cleanedWorkDir+string(filepath.Separator))
|
|
}
|
|
|
|
func (suite *IntegrationSecurityTestSuite) setupTestApps() {
|
|
// Setup proxy app (simplified for testing)
|
|
suite.proxyApp = fiber.New(fiber.Config{
|
|
DisableStartupMessage: true,
|
|
})
|
|
|
|
// Add proxy routes with security middleware
|
|
suite.proxyApp.Use(func(c *fiber.Ctx) error {
|
|
// Add request UUID for tracking
|
|
c.Locals("request_uuid", fmt.Sprintf("test-uuid-%d", time.Now().UnixNano()))
|
|
return c.Next()
|
|
})
|
|
|
|
suite.proxyApp.Post("/graphql", func(c *fiber.Ctx) error {
|
|
// Simulate GraphQL proxy behavior with logging
|
|
if cfg.LogLevel == "DEBUG" {
|
|
logDebugRequest(c)
|
|
}
|
|
|
|
// Mock GraphQL response
|
|
response := map[string]interface{}{
|
|
"data": map[string]interface{}{
|
|
"user": map[string]interface{}{
|
|
"id": "12345",
|
|
"name": "Test User",
|
|
"email": "test@example.com",
|
|
},
|
|
},
|
|
}
|
|
|
|
c.Set("Content-Type", "application/json")
|
|
if cfg.LogLevel == "DEBUG" {
|
|
logDebugResponse(c)
|
|
}
|
|
|
|
return c.JSON(response)
|
|
})
|
|
|
|
// Setup API app
|
|
suite.apiApp = fiber.New(fiber.Config{
|
|
DisableStartupMessage: true,
|
|
})
|
|
|
|
api := suite.apiApp.Group("/api")
|
|
api.Use(authMiddleware)
|
|
api.Post("/user-ban", apiBanUser)
|
|
api.Post("/user-unban", apiUnbanUser)
|
|
api.Post("/cache-clear", apiClearCache)
|
|
api.Get("/cache-stats", apiCacheStats)
|
|
}
|
|
|
|
// TestEndToEndSecurity tests complete request flow with security checks
|
|
func (suite *IntegrationSecurityTestSuite) TestEndToEndSecurity() {
|
|
suite.Run("GraphQL request with sensitive data logging", func() {
|
|
// Set debug mode to test logging sanitization
|
|
originalLogLevel := cfg.LogLevel
|
|
cfg.LogLevel = "DEBUG"
|
|
defer func() { cfg.LogLevel = originalLogLevel }()
|
|
|
|
// Create GraphQL request with sensitive data
|
|
graphqlQuery := map[string]interface{}{
|
|
"query": `
|
|
mutation LoginUser($input: LoginInput!) {
|
|
login(input: $input) {
|
|
user { id name }
|
|
token
|
|
}
|
|
}
|
|
`,
|
|
"variables": map[string]interface{}{
|
|
"input": map[string]interface{}{
|
|
"email": "user@example.com",
|
|
"password": "secret123password",
|
|
"api_key": "sk-sensitive-key-123",
|
|
},
|
|
},
|
|
}
|
|
|
|
requestBody, err := json.Marshal(graphqlQuery)
|
|
suite.NoError(err)
|
|
|
|
req, err := http.NewRequest("POST", "/graphql", bytes.NewBuffer(requestBody))
|
|
suite.NoError(err)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", "Bearer sensitive-token-123")
|
|
|
|
resp, err := suite.proxyApp.Test(req)
|
|
suite.NoError(err)
|
|
suite.Equal(200, resp.StatusCode)
|
|
|
|
// Verify response doesn't contain sensitive data in logs
|
|
// This would be verified through log capture in a real implementation
|
|
})
|
|
}
|
|
|
|
// TestAPISecurityFlow tests complete API security workflow
|
|
func (suite *IntegrationSecurityTestSuite) TestAPISecurityFlow() {
|
|
tests := []struct {
|
|
body map[string]interface{}
|
|
name string
|
|
endpoint string
|
|
method string
|
|
apiKey string
|
|
description string
|
|
expectedStatus int
|
|
}{
|
|
{
|
|
name: "Unauthorized ban attempt",
|
|
endpoint: "/api/user-ban",
|
|
method: "POST",
|
|
apiKey: "",
|
|
body: map[string]interface{}{"user_id": "malicious-user", "reason": "test ban"},
|
|
expectedStatus: 401,
|
|
description: "Should reject unauthorized ban attempts",
|
|
},
|
|
{
|
|
name: "SQL injection in API key",
|
|
endpoint: "/api/user-ban",
|
|
method: "POST",
|
|
apiKey: "' OR '1'='1 --",
|
|
body: map[string]interface{}{"user_id": "test-user", "reason": "test ban"},
|
|
expectedStatus: 401,
|
|
description: "Should reject SQL injection in API key",
|
|
},
|
|
{
|
|
name: "Valid ban request",
|
|
endpoint: "/api/user-ban",
|
|
method: "POST",
|
|
apiKey: suite.validAPIKey,
|
|
body: map[string]interface{}{"user_id": "test-user-ban", "reason": "test ban reason"},
|
|
expectedStatus: 200,
|
|
description: "Should accept valid ban request",
|
|
},
|
|
{
|
|
name: "Cache clear without auth",
|
|
endpoint: "/api/cache-clear",
|
|
method: "POST",
|
|
apiKey: "",
|
|
body: nil,
|
|
expectedStatus: 401,
|
|
description: "Should reject unauthorized cache clear",
|
|
},
|
|
{
|
|
name: "Valid cache clear",
|
|
endpoint: "/api/cache-clear",
|
|
method: "POST",
|
|
apiKey: suite.validAPIKey,
|
|
body: nil,
|
|
expectedStatus: 200,
|
|
description: "Should accept authorized cache clear",
|
|
},
|
|
{
|
|
name: "Cache stats without auth",
|
|
endpoint: "/api/cache-stats",
|
|
method: "GET",
|
|
apiKey: "",
|
|
body: nil,
|
|
expectedStatus: 401,
|
|
description: "Should reject unauthorized cache stats",
|
|
},
|
|
{
|
|
name: "Valid cache stats",
|
|
endpoint: "/api/cache-stats",
|
|
method: "GET",
|
|
apiKey: suite.validAPIKey,
|
|
body: nil,
|
|
expectedStatus: 200,
|
|
description: "Should accept authorized cache stats",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
suite.Run(tt.name, func() {
|
|
var req *http.Request
|
|
var err error
|
|
|
|
if tt.body != nil {
|
|
bodyBytes, _ := json.Marshal(tt.body)
|
|
req, err = http.NewRequest(tt.method, tt.endpoint, bytes.NewBuffer(bodyBytes))
|
|
suite.NoError(err)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
} else {
|
|
req, err = http.NewRequest(tt.method, tt.endpoint, nil)
|
|
suite.NoError(err)
|
|
}
|
|
|
|
if tt.apiKey != "" {
|
|
req.Header.Set("X-API-Key", tt.apiKey)
|
|
}
|
|
|
|
resp, err := suite.apiApp.Test(req)
|
|
suite.NoError(err)
|
|
|
|
suite.Equal(tt.expectedStatus, resp.StatusCode,
|
|
"Status mismatch for %s: %s", tt.name, tt.description)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestFilePathSecurityIntegration tests path traversal prevention in real scenarios
|
|
func (suite *IntegrationSecurityTestSuite) TestFilePathSecurityIntegration() {
|
|
tests := []struct {
|
|
name string
|
|
requestedPath string
|
|
description string
|
|
shouldBeAllowed bool
|
|
}{
|
|
{
|
|
name: "Valid temp file",
|
|
requestedPath: filepath.Join(suite.tempDir, "valid_file.json"),
|
|
shouldBeAllowed: suite.tempDirShouldBeAllowed(), // Check if tempDir is in allowed paths
|
|
description: "Temp directory handling based on system temp location",
|
|
},
|
|
{
|
|
name: "Path traversal attempt",
|
|
requestedPath: "../../../../etc/passwd",
|
|
shouldBeAllowed: false,
|
|
description: "Path traversal should be blocked",
|
|
},
|
|
{
|
|
name: "Null byte injection",
|
|
requestedPath: "/tmp/file.txt\x00.jpg",
|
|
shouldBeAllowed: false,
|
|
description: "Null byte injection should be blocked",
|
|
},
|
|
{
|
|
name: "Current directory access",
|
|
requestedPath: "./config.json",
|
|
shouldBeAllowed: true,
|
|
description: "Current directory should be allowed",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
suite.Run(tt.name, func() {
|
|
_, err := validateFilePath(tt.requestedPath)
|
|
|
|
if tt.shouldBeAllowed {
|
|
suite.NoError(err, "Path should be allowed: %s", tt.description)
|
|
} else {
|
|
suite.Error(err, "Path should be rejected: %s", tt.description)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestConcurrentSecurityOperations tests security under concurrent load
|
|
func (suite *IntegrationSecurityTestSuite) TestConcurrentSecurityOperations() {
|
|
const numGoroutines = 20
|
|
const numRequestsPerGoroutine = 10
|
|
|
|
suite.Run("Concurrent API authentication", func() {
|
|
var wg sync.WaitGroup
|
|
results := make(chan int, numGoroutines*numRequestsPerGoroutine)
|
|
|
|
// Mix of valid and invalid API keys
|
|
apiKeys := []string{
|
|
suite.validAPIKey, // Valid
|
|
"invalid-key-1", // Invalid
|
|
"invalid-key-2", // Invalid
|
|
"' OR '1'='1", // SQL injection attempt
|
|
suite.validAPIKey, // Valid
|
|
"", // Empty
|
|
}
|
|
|
|
for i := 0; i < numGoroutines; i++ {
|
|
wg.Add(1)
|
|
go func(goroutineID int) {
|
|
defer wg.Done()
|
|
|
|
for j := 0; j < numRequestsPerGoroutine; j++ {
|
|
keyIndex := (goroutineID + j) % len(apiKeys)
|
|
apiKey := apiKeys[keyIndex]
|
|
|
|
req, err := http.NewRequest("GET", "/api/cache-stats", nil)
|
|
if err != nil {
|
|
results <- 500
|
|
continue
|
|
}
|
|
|
|
if apiKey != "" {
|
|
req.Header.Set("X-API-Key", apiKey)
|
|
}
|
|
|
|
resp, err := suite.apiApp.Test(req)
|
|
if err != nil {
|
|
results <- 500
|
|
continue
|
|
}
|
|
|
|
results <- resp.StatusCode
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
close(results)
|
|
|
|
// Analyze results
|
|
statusCounts := make(map[int]int)
|
|
totalRequests := 0
|
|
for status := range results {
|
|
statusCounts[status]++
|
|
totalRequests++
|
|
}
|
|
|
|
suite.Equal(numGoroutines*numRequestsPerGoroutine, totalRequests,
|
|
"Should process all requests")
|
|
suite.Greater(statusCounts[200], 0, "Should have some successful requests")
|
|
suite.Greater(statusCounts[401], 0, "Should have some rejected requests")
|
|
suite.Equal(0, statusCounts[500], "Should not have server errors")
|
|
})
|
|
}
|
|
|
|
// TestSecurityEventLogging tests that security events are properly logged
|
|
func (suite *IntegrationSecurityTestSuite) TestSecurityEventLogging() {
|
|
// This would require log capture mechanism in a real implementation
|
|
suite.Run("Security event logging", func() {
|
|
// Test unauthorized access logging
|
|
req, err := http.NewRequest("POST", "/api/user-ban", bytes.NewBuffer([]byte(`{"user_id": "test", "reason": "test ban"}`)))
|
|
suite.NoError(err)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("X-API-Key", "invalid-key")
|
|
|
|
resp, err := suite.apiApp.Test(req)
|
|
suite.NoError(err)
|
|
suite.Equal(401, resp.StatusCode)
|
|
|
|
// In a real implementation, we would verify that:
|
|
// 1. Unauthorized access attempt was logged
|
|
// 2. No sensitive data was included in logs
|
|
// 3. Appropriate log level was used
|
|
})
|
|
}
|
|
|
|
// TestRateLimitingIntegration tests rate limiting under security scenarios
|
|
func (suite *IntegrationSecurityTestSuite) TestRateLimitingIntegration() {
|
|
// This would test rate limiting if implemented
|
|
suite.Run("Rate limiting for security", func() {
|
|
// Rapid unauthorized requests
|
|
const numRequests = 100
|
|
unauthorizedCount := 0
|
|
|
|
for i := 0; i < numRequests; i++ {
|
|
req, err := http.NewRequest("POST", "/api/user-ban",
|
|
bytes.NewBuffer([]byte(`{"user_id": "test", "reason": "test ban"}`)))
|
|
suite.NoError(err)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("X-API-Key", "invalid-key")
|
|
|
|
resp, err := suite.apiApp.Test(req)
|
|
suite.NoError(err)
|
|
|
|
if resp.StatusCode == 401 {
|
|
unauthorizedCount++
|
|
}
|
|
}
|
|
|
|
// All should be unauthorized (no rate limiting implemented yet)
|
|
suite.Equal(numRequests, unauthorizedCount,
|
|
"All unauthorized requests should be rejected")
|
|
})
|
|
}
|
|
|
|
// TestSecurityHeadersIntegration tests security-related headers
|
|
func (suite *IntegrationSecurityTestSuite) TestSecurityHeadersIntegration() {
|
|
suite.Run("Security headers in responses", func() {
|
|
req, err := http.NewRequest("GET", "/api/cache-stats", nil)
|
|
suite.NoError(err)
|
|
req.Header.Set("X-API-Key", suite.validAPIKey)
|
|
|
|
resp, err := suite.apiApp.Test(req)
|
|
suite.NoError(err)
|
|
suite.Equal(200, resp.StatusCode)
|
|
|
|
// Check for security headers (if implemented)
|
|
// In a production system, you'd want headers like:
|
|
// - X-Content-Type-Options: nosniff
|
|
// - X-Frame-Options: DENY
|
|
// - X-XSS-Protection: 1; mode=block
|
|
})
|
|
}
|
|
|
|
// TestDataSanitizationIntegration tests end-to-end data sanitization
|
|
func (suite *IntegrationSecurityTestSuite) TestDataSanitizationIntegration() {
|
|
suite.Run("Request/Response sanitization", func() {
|
|
// Enable debug logging to test sanitization
|
|
originalLogLevel := cfg.LogLevel
|
|
cfg.LogLevel = "DEBUG"
|
|
defer func() { cfg.LogLevel = originalLogLevel }()
|
|
|
|
// Create request with sensitive data
|
|
sensitiveData := map[string]interface{}{
|
|
"query": "{ user { id name } }",
|
|
"variables": map[string]interface{}{
|
|
"password": "secret123",
|
|
"api_key": "sk-sensitive-123",
|
|
"credit_card": "4111111111111111",
|
|
},
|
|
}
|
|
|
|
bodyBytes, err := json.Marshal(sensitiveData)
|
|
suite.NoError(err)
|
|
|
|
req, err := http.NewRequest("POST", "/graphql", bytes.NewBuffer(bodyBytes))
|
|
suite.NoError(err)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", "Bearer sensitive-token")
|
|
|
|
resp, err := suite.proxyApp.Test(req)
|
|
suite.NoError(err)
|
|
suite.Equal(200, resp.StatusCode)
|
|
|
|
// Verify response
|
|
body, err := io.ReadAll(resp.Body)
|
|
suite.NoError(err)
|
|
|
|
var response map[string]interface{}
|
|
err = json.Unmarshal(body, &response)
|
|
suite.NoError(err)
|
|
|
|
suite.Contains(response, "data")
|
|
// In debug mode, logs would contain sanitized data (tested separately)
|
|
})
|
|
}
|
|
|
|
// TestErrorHandlingSecurityIntegration tests secure error handling
|
|
func (suite *IntegrationSecurityTestSuite) TestErrorHandlingSecurityIntegration() {
|
|
tests := []struct {
|
|
name string
|
|
endpoint string
|
|
method string
|
|
body string
|
|
description string
|
|
}{
|
|
{
|
|
name: "Malformed JSON",
|
|
endpoint: "/api/user-ban",
|
|
method: "POST",
|
|
body: `{"invalid": json}`,
|
|
description: "Should handle malformed JSON securely",
|
|
},
|
|
{
|
|
name: "Missing content type",
|
|
endpoint: "/api/user-ban",
|
|
method: "POST",
|
|
body: `{"user_id": "test", "reason": "test ban"}`,
|
|
description: "Should handle missing content type",
|
|
},
|
|
{
|
|
name: "Empty body",
|
|
endpoint: "/api/user-ban",
|
|
method: "POST",
|
|
body: "",
|
|
description: "Should handle empty body",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
suite.Run(tt.name, func() {
|
|
req, err := http.NewRequest(tt.method, tt.endpoint, strings.NewReader(tt.body))
|
|
suite.NoError(err)
|
|
req.Header.Set("X-API-Key", suite.validAPIKey)
|
|
if tt.name != "Missing content type" {
|
|
req.Header.Set("Content-Type", "application/json")
|
|
}
|
|
|
|
resp, err := suite.apiApp.Test(req)
|
|
suite.NoError(err)
|
|
|
|
// Should not return 500 errors for client errors
|
|
suite.NotEqual(500, resp.StatusCode, "Should not return server error for client error")
|
|
|
|
// Error response should not contain sensitive information
|
|
if resp.StatusCode >= 400 {
|
|
body, err := io.ReadAll(resp.Body)
|
|
suite.NoError(err)
|
|
|
|
bodyStr := strings.ToLower(string(body))
|
|
suite.NotContains(bodyStr, "stack", "Error should not contain stack trace")
|
|
suite.NotContains(bodyStr, "panic", "Error should not contain panic details")
|
|
suite.NotContains(bodyStr, "internal", "Error should not leak internal details")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestComprehensiveSecurityScenario tests a complete security scenario
|
|
func (suite *IntegrationSecurityTestSuite) TestComprehensiveSecurityScenario() {
|
|
suite.Run("Complete security workflow", func() {
|
|
// 1. Attempt SQL injection via GraphQL
|
|
maliciousGraphQL := map[string]interface{}{
|
|
"query": "{ user(id: \"'; DROP TABLE users; --\") { id } }",
|
|
}
|
|
|
|
bodyBytes, _ := json.Marshal(maliciousGraphQL)
|
|
req, _ := http.NewRequest("POST", "/graphql", bytes.NewBuffer(bodyBytes))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := suite.proxyApp.Test(req)
|
|
suite.NoError(err)
|
|
// Should not crash or return server error
|
|
suite.NotEqual(500, resp.StatusCode)
|
|
|
|
// 2. Attempt path traversal via API (if file operations were exposed)
|
|
maliciousPath := "../../../../etc/passwd"
|
|
_, err = validateFilePath(maliciousPath)
|
|
suite.Error(err, "Path traversal should be blocked")
|
|
|
|
// 3. Attempt unauthorized admin access
|
|
req, _ = http.NewRequest("POST", "/api/cache-clear", nil)
|
|
// No API key provided
|
|
|
|
resp, err = suite.apiApp.Test(req)
|
|
suite.NoError(err)
|
|
suite.Equal(401, resp.StatusCode, "Should reject unauthorized access")
|
|
|
|
// 4. Test with valid credentials
|
|
req, _ = http.NewRequest("GET", "/api/cache-stats", nil)
|
|
req.Header.Set("X-API-Key", suite.validAPIKey)
|
|
|
|
resp, err = suite.apiApp.Test(req)
|
|
suite.NoError(err)
|
|
suite.Equal(200, resp.StatusCode, "Should accept valid credentials")
|
|
|
|
// 5. Verify no sensitive data in logs (would need log capture)
|
|
// This would be tested in a real implementation with log capture
|
|
})
|
|
}
|
|
|
|
// BenchmarkSecurityOperations benchmarks security-related operations
|
|
func BenchmarkSecurityOperations(b *testing.B) {
|
|
// Setup
|
|
cfg = &config{}
|
|
cfg.Logger = libpack_logger.New()
|
|
|
|
validAPIKey := "benchmark-api-key"
|
|
os.Setenv("GMP_ADMIN_API_KEY", validAPIKey)
|
|
defer os.Unsetenv("GMP_ADMIN_API_KEY")
|
|
|
|
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
|
api := app.Group("/api")
|
|
api.Use(authMiddleware)
|
|
api.Get("/test", func(c *fiber.Ctx) error {
|
|
return c.JSON(fiber.Map{"status": "ok"})
|
|
})
|
|
|
|
b.ResetTimer()
|
|
|
|
b.Run("API Authentication", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
req, _ := http.NewRequest("GET", "/api/test", nil)
|
|
req.Header.Set("X-API-Key", validAPIKey)
|
|
app.Test(req)
|
|
}
|
|
})
|
|
|
|
b.Run("Path Validation", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
validateFilePath("./test/file.txt")
|
|
}
|
|
})
|
|
|
|
b.Run("Log Sanitization", func(b *testing.B) {
|
|
testData := map[string]interface{}{
|
|
"password": "secret123",
|
|
"api_key": "sk-123456",
|
|
"data": "normal data",
|
|
}
|
|
jsonData, _ := json.Marshal(testData)
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
sanitizeForLogging(jsonData, "application/json")
|
|
}
|
|
})
|
|
}
|