network/e2e/shared/auth_negative_test.go
2026-01-29 07:50:40 +02:00

334 lines
12 KiB
Go

//go:build e2e
package shared_test
import (
"context"
"net/http"
"testing"
"time"
"unicode"
e2e "github.com/DeBrosOfficial/network/e2e"
"github.com/stretchr/testify/require"
)
// =============================================================================
// STRICT AUTHENTICATION NEGATIVE TESTS
// These tests verify that authentication is properly enforced.
// Tests FAIL if unauthenticated/invalid requests are allowed through.
// =============================================================================
func TestAuth_MissingAPIKey(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Request protected endpoint without auth headers
req, err := http.NewRequestWithContext(ctx, http.MethodGet, e2e.GetGatewayURL()+"/v1/cache/health", nil)
require.NoError(t, err, "FAIL: Could not create request")
client := e2e.NewHTTPClient(30 * time.Second)
resp, err := client.Do(req)
require.NoError(t, err, "FAIL: Request failed")
defer resp.Body.Close()
// STRICT: Must reject requests without authentication
require.True(t, resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden,
"FAIL: Protected endpoint allowed request without auth - expected 401/403, got %d", resp.StatusCode)
t.Logf(" ✓ Missing API key correctly rejected with status %d", resp.StatusCode)
}
func TestAuth_InvalidAPIKey(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Request with invalid API key
req, err := http.NewRequestWithContext(ctx, http.MethodGet, e2e.GetGatewayURL()+"/v1/cache/health", nil)
require.NoError(t, err, "FAIL: Could not create request")
req.Header.Set("Authorization", "Bearer invalid-key-xyz-123456789")
client := e2e.NewHTTPClient(30 * time.Second)
resp, err := client.Do(req)
require.NoError(t, err, "FAIL: Request failed")
defer resp.Body.Close()
// STRICT: Must reject invalid API keys
require.True(t, resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden,
"FAIL: Invalid API key was accepted - expected 401/403, got %d", resp.StatusCode)
t.Logf(" ✓ Invalid API key correctly rejected with status %d", resp.StatusCode)
}
func TestAuth_CacheWithoutAuth(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Request cache endpoint without auth
req := &e2e.HTTPRequest{
Method: http.MethodGet,
URL: e2e.GetGatewayURL() + "/v1/cache/health",
SkipAuth: true,
}
_, status, err := req.Do(ctx)
require.NoError(t, err, "FAIL: Request failed")
// STRICT: Cache endpoint must require authentication
require.True(t, status == http.StatusUnauthorized || status == http.StatusForbidden,
"FAIL: Cache endpoint accessible without auth - expected 401/403, got %d", status)
t.Logf(" ✓ Cache endpoint correctly requires auth (status %d)", status)
}
func TestAuth_StorageWithoutAuth(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Request storage endpoint without auth
req := &e2e.HTTPRequest{
Method: http.MethodGet,
URL: e2e.GetGatewayURL() + "/v1/storage/status/QmTest",
SkipAuth: true,
}
_, status, err := req.Do(ctx)
require.NoError(t, err, "FAIL: Request failed")
// STRICT: Storage endpoint must require authentication
require.True(t, status == http.StatusUnauthorized || status == http.StatusForbidden,
"FAIL: Storage endpoint accessible without auth - expected 401/403, got %d", status)
t.Logf(" ✓ Storage endpoint correctly requires auth (status %d)", status)
}
func TestAuth_RQLiteWithoutAuth(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Request rqlite endpoint without auth
req := &e2e.HTTPRequest{
Method: http.MethodGet,
URL: e2e.GetGatewayURL() + "/v1/rqlite/schema",
SkipAuth: true,
}
_, status, err := req.Do(ctx)
require.NoError(t, err, "FAIL: Request failed")
// STRICT: RQLite endpoint must require authentication
require.True(t, status == http.StatusUnauthorized || status == http.StatusForbidden,
"FAIL: RQLite endpoint accessible without auth - expected 401/403, got %d", status)
t.Logf(" ✓ RQLite endpoint correctly requires auth (status %d)", status)
}
func TestAuth_MalformedBearerToken(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Request with malformed bearer token (missing "Bearer " prefix)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, e2e.GetGatewayURL()+"/v1/cache/health", nil)
require.NoError(t, err, "FAIL: Could not create request")
req.Header.Set("Authorization", "invalid-token-format-no-bearer")
client := e2e.NewHTTPClient(30 * time.Second)
resp, err := client.Do(req)
require.NoError(t, err, "FAIL: Request failed")
defer resp.Body.Close()
// STRICT: Must reject malformed authorization headers
require.True(t, resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden,
"FAIL: Malformed auth header accepted - expected 401/403, got %d", resp.StatusCode)
t.Logf(" ✓ Malformed bearer token correctly rejected (status %d)", resp.StatusCode)
}
func TestAuth_ExpiredJWT(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Test with a clearly invalid JWT structure
req, err := http.NewRequestWithContext(ctx, http.MethodGet, e2e.GetGatewayURL()+"/v1/cache/health", nil)
require.NoError(t, err, "FAIL: Could not create request")
req.Header.Set("Authorization", "Bearer expired.jwt.token.invalid")
client := e2e.NewHTTPClient(30 * time.Second)
resp, err := client.Do(req)
require.NoError(t, err, "FAIL: Request failed")
defer resp.Body.Close()
// STRICT: Must reject invalid/expired JWT tokens
require.True(t, resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden,
"FAIL: Invalid JWT accepted - expected 401/403, got %d", resp.StatusCode)
t.Logf(" ✓ Invalid JWT correctly rejected (status %d)", resp.StatusCode)
}
func TestAuth_EmptyBearerToken(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Request with empty bearer token
req, err := http.NewRequestWithContext(ctx, http.MethodGet, e2e.GetGatewayURL()+"/v1/cache/health", nil)
require.NoError(t, err, "FAIL: Could not create request")
req.Header.Set("Authorization", "Bearer ")
client := e2e.NewHTTPClient(30 * time.Second)
resp, err := client.Do(req)
require.NoError(t, err, "FAIL: Request failed")
defer resp.Body.Close()
// STRICT: Must reject empty bearer tokens
require.True(t, resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden,
"FAIL: Empty bearer token accepted - expected 401/403, got %d", resp.StatusCode)
t.Logf(" ✓ Empty bearer token correctly rejected (status %d)", resp.StatusCode)
}
func TestAuth_DuplicateAuthHeaders(t *testing.T) {
if e2e.GetAPIKey() == "" {
t.Skip("No API key configured")
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Request with both valid API key in Authorization header
req := &e2e.HTTPRequest{
Method: http.MethodGet,
URL: e2e.GetGatewayURL() + "/v1/cache/health",
Headers: map[string]string{
"Authorization": "Bearer " + e2e.GetAPIKey(),
"X-API-Key": e2e.GetAPIKey(),
},
}
_, status, err := req.Do(ctx)
require.NoError(t, err, "FAIL: Request failed")
// Should succeed since we have a valid API key
require.Equal(t, http.StatusOK, status,
"FAIL: Valid API key rejected when multiple auth headers present - got %d", status)
t.Logf(" ✓ Duplicate auth headers with valid key succeeds (status %d)", status)
}
func TestAuth_CaseSensitiveAPIKey(t *testing.T) {
apiKey := e2e.GetAPIKey()
if apiKey == "" {
t.Skip("No API key configured")
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Create incorrectly cased API key
incorrectKey := ""
for i, ch := range apiKey {
if i%2 == 0 && unicode.IsLetter(ch) {
if unicode.IsLower(ch) {
incorrectKey += string(unicode.ToUpper(ch))
} else {
incorrectKey += string(unicode.ToLower(ch))
}
} else {
incorrectKey += string(ch)
}
}
// Skip if the key didn't change (no letters)
if incorrectKey == apiKey {
t.Skip("API key has no letters to change case")
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, e2e.GetGatewayURL()+"/v1/cache/health", nil)
require.NoError(t, err, "FAIL: Could not create request")
req.Header.Set("Authorization", "Bearer "+incorrectKey)
client := e2e.NewHTTPClient(30 * time.Second)
resp, err := client.Do(req)
require.NoError(t, err, "FAIL: Request failed")
defer resp.Body.Close()
// STRICT: API keys MUST be case-sensitive
require.True(t, resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden,
"FAIL: API key check is not case-sensitive - modified key accepted with status %d", resp.StatusCode)
t.Logf(" ✓ Case-modified API key correctly rejected (status %d)", resp.StatusCode)
}
func TestAuth_HealthEndpointNoAuth(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Health endpoint at /v1/health should NOT require auth
req, err := http.NewRequestWithContext(ctx, http.MethodGet, e2e.GetGatewayURL()+"/v1/health", nil)
require.NoError(t, err, "FAIL: Could not create request")
client := e2e.NewHTTPClient(30 * time.Second)
resp, err := client.Do(req)
require.NoError(t, err, "FAIL: Request failed")
defer resp.Body.Close()
// Health endpoint should be publicly accessible
require.Equal(t, http.StatusOK, resp.StatusCode,
"FAIL: Health endpoint should not require auth - expected 200, got %d", resp.StatusCode)
t.Logf(" ✓ Health endpoint correctly accessible without auth")
}
func TestAuth_StatusEndpointNoAuth(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Status endpoint at /v1/status should NOT require auth
req, err := http.NewRequestWithContext(ctx, http.MethodGet, e2e.GetGatewayURL()+"/v1/status", nil)
require.NoError(t, err, "FAIL: Could not create request")
client := e2e.NewHTTPClient(30 * time.Second)
resp, err := client.Do(req)
require.NoError(t, err, "FAIL: Request failed")
defer resp.Body.Close()
// Status endpoint should be publicly accessible
require.Equal(t, http.StatusOK, resp.StatusCode,
"FAIL: Status endpoint should not require auth - expected 200, got %d", resp.StatusCode)
t.Logf(" ✓ Status endpoint correctly accessible without auth")
}
func TestAuth_DeploymentsWithoutAuth(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Request deployments endpoint without auth
req := &e2e.HTTPRequest{
Method: http.MethodGet,
URL: e2e.GetGatewayURL() + "/v1/deployments/list",
SkipAuth: true,
}
_, status, err := req.Do(ctx)
require.NoError(t, err, "FAIL: Request failed")
// STRICT: Deployments endpoint must require authentication
require.True(t, status == http.StatusUnauthorized || status == http.StatusForbidden,
"FAIL: Deployments endpoint accessible without auth - expected 401/403, got %d", status)
t.Logf(" ✓ Deployments endpoint correctly requires auth (status %d)", status)
}
func TestAuth_SQLiteWithoutAuth(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Request SQLite endpoint without auth
req := &e2e.HTTPRequest{
Method: http.MethodGet,
URL: e2e.GetGatewayURL() + "/v1/db/sqlite/list",
SkipAuth: true,
}
_, status, err := req.Do(ctx)
require.NoError(t, err, "FAIL: Request failed")
// STRICT: SQLite endpoint must require authentication
require.True(t, status == http.StatusUnauthorized || status == http.StatusForbidden,
"FAIL: SQLite endpoint accessible without auth - expected 401/403, got %d", status)
t.Logf(" ✓ SQLite endpoint correctly requires auth (status %d)", status)
}