-
This commit is contained in:
@ -1,139 +0,0 @@
|
||||
# Token Prefix Security Recommendations
|
||||
|
||||
## Current Security Issues
|
||||
|
||||
### 1. Information Disclosure
|
||||
- Prefixes like "KMS", "TEST", "PROD" expose system architecture
|
||||
- Makes it easy for attackers to identify token origins and purposes
|
||||
- Leaks information about internal applications and environments
|
||||
|
||||
### 2. Predictable Token Structure
|
||||
- Static tokens: `PREFIX + "T-" + token_data`
|
||||
- User tokens: `PREFIX + "UT-" + jwt_token`
|
||||
- Highly predictable format aids in token identification and potential attacks
|
||||
|
||||
### 3. Application Fingerprinting
|
||||
- Trivial to map tokens to specific applications
|
||||
- Easy to identify different environments (dev/staging/prod)
|
||||
- Reveals system architecture from leaked tokens
|
||||
|
||||
## Recommended Security Improvements
|
||||
|
||||
### Option 1: Remove Custom Prefixes (Recommended)
|
||||
```go
|
||||
// Use a single, non-descriptive prefix for all tokens
|
||||
const SecureTokenPrefix = "kms_"
|
||||
|
||||
// All tokens would be: kms_<random_data>
|
||||
// No application or type information revealed
|
||||
```
|
||||
|
||||
### Option 2: Opaque Application Identifiers
|
||||
```go
|
||||
// Use random/hashed identifiers instead of descriptive names
|
||||
type Application struct {
|
||||
AppID string `json:"app_id"`
|
||||
TokenPrefix string `json:"token_prefix"` // Random: "A7B2", "X9K5", etc.
|
||||
}
|
||||
|
||||
// Generate random 4-character prefixes
|
||||
func GenerateSecurePrefix() string {
|
||||
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
result := make([]byte, 4)
|
||||
for i := range result {
|
||||
result[i] = charset[rand.Intn(len(charset))]
|
||||
}
|
||||
return string(result)
|
||||
}
|
||||
```
|
||||
|
||||
### Option 3: No Type Indicators
|
||||
```go
|
||||
// Remove "T-" and "UT-" type indicators
|
||||
// Token type should be determined by validation, not structure
|
||||
func GenerateToken(appPrefix string, tokenType string) string {
|
||||
token := appPrefix + randomTokenData // No type suffix
|
||||
return token
|
||||
}
|
||||
```
|
||||
|
||||
### Option 4: Encrypted Token Metadata
|
||||
```go
|
||||
// Encrypt sensitive information within tokens
|
||||
type TokenMetadata struct {
|
||||
AppID string `json:"app_id"`
|
||||
TokenType string `json:"token_type"`
|
||||
IssuedAt time.Time `json:"issued_at"`
|
||||
}
|
||||
|
||||
func CreateSecureToken(metadata TokenMetadata, key []byte) string {
|
||||
// Encrypt metadata and embed in token
|
||||
encrypted := encrypt(metadata, key)
|
||||
return "kms_" + base64.URLEncoding.EncodeToString(encrypted)
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Priority
|
||||
|
||||
1. **Immediate**: Remove descriptive prefixes ("KMS", "TEST", "PROD")
|
||||
2. **Short-term**: Remove type indicators ("T-", "UT-")
|
||||
3. **Long-term**: Implement encrypted token metadata
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### Token Storage
|
||||
- Always hash tokens before database storage
|
||||
- Use strong hashing algorithms (bcrypt, scrypt, Argon2)
|
||||
- Never log or expose full tokens
|
||||
|
||||
### Token Validation
|
||||
- Validate tokens by content, not by prefix patterns
|
||||
- Use cryptographic verification for all tokens
|
||||
- Implement proper token revocation mechanisms
|
||||
|
||||
### Monitoring
|
||||
- Monitor for token enumeration attempts
|
||||
- Alert on suspicious prefix-based attacks
|
||||
- Log token validation failures for security analysis
|
||||
|
||||
## Code Changes Required
|
||||
|
||||
### 1. Update Token Generation
|
||||
```go
|
||||
// Before: Predictable structure
|
||||
token := "TESTT-" + tokenData
|
||||
|
||||
// After: Opaque structure
|
||||
token := "kms_" + secureRandomToken()
|
||||
```
|
||||
|
||||
### 2. Update Validation Logic
|
||||
```go
|
||||
// Before: Prefix-based type detection
|
||||
if strings.HasPrefix(token, app.TokenPrefix + "UT-") {
|
||||
return TokenTypeUser
|
||||
}
|
||||
|
||||
// After: Cryptographic validation
|
||||
tokenType, err := validateAndExtractType(token, app.HMACKey)
|
||||
```
|
||||
|
||||
### 3. Database Migration
|
||||
```sql
|
||||
-- Remove descriptive prefixes from existing tokens
|
||||
UPDATE applications
|
||||
SET token_prefix = 'kms_'
|
||||
WHERE token_prefix IN ('KMS', 'TEST', 'PROD', 'DEV');
|
||||
```
|
||||
|
||||
## Risk Assessment
|
||||
|
||||
**Current Risk Level**: HIGH
|
||||
- Token structure reveals sensitive system information
|
||||
- Easy application and environment fingerprinting
|
||||
- Predictable token patterns aid in attacks
|
||||
|
||||
**Mitigated Risk Level**: LOW
|
||||
- Opaque token structure prevents information leakage
|
||||
- No application or environment identification possible
|
||||
- Cryptographic validation ensures security
|
||||
@ -1,150 +0,0 @@
|
||||
# Token Verification: App ID From Token vs Separate Parameter
|
||||
|
||||
## Current Implementation Analysis
|
||||
|
||||
### Current Flow (Requires app_id parameter):
|
||||
```
|
||||
1. Client: POST /api/verify {"token": "TESTT-abc123", "app_id": "test.example.com"}
|
||||
2. System: Get app by app_id → Validate token against app's stored hashes
|
||||
3. Result: Token validated within specific application scope
|
||||
```
|
||||
|
||||
### Proposed Alternative (Extract app_id from token):
|
||||
```
|
||||
1. Client: POST /api/verify {"token": "TESTT-abc123"}
|
||||
2. System: Extract prefix "TEST" → Find app by token_prefix → Validate token
|
||||
3. Result: Token validated without requiring app_id parameter
|
||||
```
|
||||
|
||||
## Security Analysis
|
||||
|
||||
### ✅ **Advantages of Token-Only Verification:**
|
||||
|
||||
#### 1. **Simpler API Usage**
|
||||
```javascript
|
||||
// Current (requires app knowledge)
|
||||
await verify({token: "TESTT-abc123", app_id: "test.example.com"});
|
||||
|
||||
// Proposed (token-only)
|
||||
await verify({token: "TESTT-abc123"});
|
||||
```
|
||||
|
||||
#### 2. **Prevents App ID Mismatches**
|
||||
- Current: Client could provide wrong app_id (though validation would fail)
|
||||
- Proposed: App is determined directly from token - no mismatch possible
|
||||
|
||||
#### 3. **Better Token Portability**
|
||||
- Tokens are self-describing and don't require external context
|
||||
- Useful for tokens shared across different applications/services
|
||||
|
||||
### ❌ **Security Risks of Token-Only Verification:**
|
||||
|
||||
#### 1. **Information Disclosure Through Enumeration**
|
||||
```bash
|
||||
# Attacker can discover applications by trying token prefixes
|
||||
curl -X POST /api/verify -d '{"token": "TESTX-fakehash"}'
|
||||
# Response reveals if "TEST" application exists
|
||||
|
||||
curl -X POST /api/verify -d '{"token": "PRODX-fakehash"}'
|
||||
# Response reveals if "PROD" application exists
|
||||
```
|
||||
|
||||
#### 2. **Reduced Access Control Granularity**
|
||||
```
|
||||
Current: Client must know both token AND which app it belongs to
|
||||
Proposed: Client only needs token - loses "need to know" app context
|
||||
```
|
||||
|
||||
#### 3. **Prefix Collision Risk**
|
||||
```sql
|
||||
-- Multiple apps could theoretically have same prefix
|
||||
INSERT INTO applications (app_id, token_prefix) VALUES
|
||||
('app1.com', 'TEST'),
|
||||
('app2.com', 'TEST'); -- Collision!
|
||||
```
|
||||
|
||||
#### 4. **Weaker Defense Against Token Leakage**
|
||||
- Current: Leaked token + need app_id knowledge = double barrier
|
||||
- Proposed: Leaked token alone is sufficient = single barrier
|
||||
|
||||
## Implementation Feasibility
|
||||
|
||||
### Required Changes for Token-Only Verification:
|
||||
|
||||
```go
|
||||
// 1. Add repository method
|
||||
func (r *ApplicationRepository) GetByTokenPrefix(ctx context.Context, prefix string) (*domain.Application, error)
|
||||
|
||||
// 2. Extract prefix from token
|
||||
func extractTokenPrefix(token string) string {
|
||||
dashIndex := strings.Index(token, "-")
|
||||
if dashIndex >= 3 && dashIndex <= 6 {
|
||||
return token[:dashIndex-1] // Remove "T" or "UT" part
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// 3. Modified verification flow
|
||||
func (s *tokenService) VerifyTokenOnly(ctx context.Context, token string) (*domain.VerifyResponse, error) {
|
||||
prefix := extractTokenPrefix(token)
|
||||
app, err := s.appRepo.GetByTokenPrefix(ctx, prefix)
|
||||
if err != nil {
|
||||
return &domain.VerifyResponse{Valid: false, Error: "Invalid token"}, nil
|
||||
}
|
||||
// Continue with existing verification logic...
|
||||
}
|
||||
```
|
||||
|
||||
### Database Schema Considerations:
|
||||
```sql
|
||||
-- Ensure unique prefixes (recommended)
|
||||
ALTER TABLE applications ADD CONSTRAINT unique_token_prefix
|
||||
UNIQUE (token_prefix) WHERE token_prefix != '';
|
||||
|
||||
-- Index already exists from migration 003
|
||||
-- CREATE INDEX idx_applications_token_prefix ON applications(token_prefix);
|
||||
```
|
||||
|
||||
## Recommendation: **Keep Current Implementation (Require app_id)**
|
||||
|
||||
### Reasoning:
|
||||
|
||||
#### **Security-First Approach** 🛡️
|
||||
1. **Prevents enumeration attacks** - attackers can't discover apps by probing prefixes
|
||||
2. **Maintains access control granularity** - clients must know token + app context
|
||||
3. **Defense in depth** - two required pieces of information instead of one
|
||||
4. **Clear audit trails** - logs show which app context was used for verification
|
||||
|
||||
#### **Architectural Benefits** 🏗️
|
||||
1. **Explicit application scoping** - makes it clear which app owns the token
|
||||
2. **Better error handling** - can distinguish between "invalid app" vs "invalid token"
|
||||
3. **Supports multi-tenancy** - different apps can have isolated token validation
|
||||
4. **Future extensibility** - can add per-app validation rules without breaking changes
|
||||
|
||||
#### **Operational Benefits** 🔧
|
||||
1. **Clear API contracts** - consumers explicitly specify their context
|
||||
2. **Better monitoring** - can track usage per application
|
||||
3. **Simpler debugging** - logs clearly show app context for each verification
|
||||
|
||||
### **Optional: Provide Both Endpoints**
|
||||
|
||||
```go
|
||||
// Existing secure endpoint (recommended for production)
|
||||
POST /api/verify
|
||||
{
|
||||
"token": "TESTT-abc123",
|
||||
"app_id": "test.example.com"
|
||||
}
|
||||
|
||||
// Optional convenience endpoint (for development/testing)
|
||||
POST /api/verify/token-only
|
||||
{
|
||||
"token": "TESTT-abc123"
|
||||
}
|
||||
```
|
||||
|
||||
## Final Answer
|
||||
|
||||
**Keep the current implementation requiring app_id** for security and architectural reasons. The slight inconvenience of requiring the app_id parameter provides significant security benefits and maintains better system architecture.
|
||||
|
||||
The token prefix provides tampering protection (which is working correctly), but requiring separate app_id provides additional security layers that outweigh the convenience of token-only verification.
|
||||
@ -1,852 +0,0 @@
|
||||
# KMS Database Schema Documentation
|
||||
|
||||
## Table of Contents
|
||||
1. [Schema Overview](#schema-overview)
|
||||
2. [Entity Relationship Diagram](#entity-relationship-diagram)
|
||||
3. [Table Definitions](#table-definitions)
|
||||
4. [Relationships and Constraints](#relationships-and-constraints)
|
||||
5. [Indexes and Performance](#indexes-and-performance)
|
||||
6. [Security Considerations](#security-considerations)
|
||||
7. [Migration Strategy](#migration-strategy)
|
||||
8. [Query Patterns](#query-patterns)
|
||||
|
||||
---
|
||||
|
||||
## Schema Overview
|
||||
|
||||
The KMS database schema is designed around core entities that manage applications, tokens, permissions, and user sessions. The schema follows PostgreSQL best practices with proper normalization, constraints, and indexing strategies.
|
||||
|
||||
### Core Entities
|
||||
- **Applications**: Central configuration for API key management
|
||||
- **Static Tokens**: Long-lived API tokens with HMAC signatures
|
||||
- **User Sessions**: JWT token tracking with metadata
|
||||
- **Available Permissions**: Hierarchical permission catalog
|
||||
- **Granted Permissions**: Token-permission relationships
|
||||
- **Audit Logs**: Complete audit trail of all operations
|
||||
|
||||
### Design Principles
|
||||
- **Normalized Design**: Reduces data redundancy
|
||||
- **Referential Integrity**: Foreign key constraints ensure consistency
|
||||
- **Audit Trail**: Complete history of all operations
|
||||
- **Performance Optimized**: Strategic indexing for common queries
|
||||
- **Security First**: Sensitive data protection and access controls
|
||||
|
||||
---
|
||||
|
||||
## Entity Relationship Diagram
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
%% Core Application Entity
|
||||
applications {
|
||||
string app_id PK "Application identifier"
|
||||
string app_link "Application URL"
|
||||
text[] type "static|user application types"
|
||||
string callback_url "OAuth2 callback URL"
|
||||
string hmac_key "HMAC signing key"
|
||||
string token_prefix "Custom token prefix"
|
||||
bigint token_renewal_duration "Token renewal window (ns)"
|
||||
bigint max_token_duration "Max token lifetime (ns)"
|
||||
string owner_type "individual|team"
|
||||
string owner_name "Owner display name"
|
||||
string owner_owner "Owner identifier"
|
||||
timestamp created_at
|
||||
timestamp updated_at
|
||||
}
|
||||
|
||||
%% Static Token Management
|
||||
static_tokens {
|
||||
uuid id PK
|
||||
string app_id FK "References applications.app_id"
|
||||
string owner_type "individual|team"
|
||||
string owner_name "Token owner name"
|
||||
string owner_owner "Token owner identifier"
|
||||
string key_hash "BCrypt hashed token (cost 14)"
|
||||
string type "Always 'hmac'"
|
||||
timestamp created_at
|
||||
timestamp updated_at
|
||||
}
|
||||
|
||||
%% User Session Tracking
|
||||
user_sessions {
|
||||
uuid id PK
|
||||
string user_id "User identifier"
|
||||
string app_id FK "References applications.app_id"
|
||||
string session_token "Hashed session identifier"
|
||||
text permissions "JSON array of permissions"
|
||||
timestamp expires_at "Session expiration"
|
||||
timestamp max_valid_at "Maximum validity window"
|
||||
string provider "header|jwt|oauth2|saml"
|
||||
json metadata "Provider-specific data"
|
||||
boolean active "Session status"
|
||||
timestamp created_at
|
||||
timestamp updated_at
|
||||
timestamp last_used_at
|
||||
}
|
||||
|
||||
%% Permission Catalog
|
||||
available_permissions {
|
||||
uuid id PK
|
||||
string scope UK "Unique permission scope"
|
||||
string name "Human-readable name"
|
||||
text description "Permission description"
|
||||
string category "Permission category"
|
||||
string parent_scope FK "References available_permissions.scope"
|
||||
boolean is_system "System permission flag"
|
||||
timestamp created_at
|
||||
string created_by "Creator identifier"
|
||||
timestamp updated_at
|
||||
string updated_by "Last updater"
|
||||
}
|
||||
|
||||
%% Token-Permission Relationships
|
||||
granted_permissions {
|
||||
uuid id PK
|
||||
string token_type "static|user"
|
||||
uuid token_id FK "References static_tokens.id"
|
||||
uuid permission_id FK "References available_permissions.id"
|
||||
string scope "Denormalized permission scope"
|
||||
timestamp created_at
|
||||
string created_by "Grant creator"
|
||||
boolean revoked "Permission revocation status"
|
||||
timestamp revoked_at "Revocation timestamp"
|
||||
string revoked_by "Revoker identifier"
|
||||
}
|
||||
|
||||
%% Audit Trail
|
||||
audit_logs {
|
||||
uuid id PK
|
||||
timestamp timestamp "Event timestamp"
|
||||
string user_id "Acting user"
|
||||
string action "Action performed"
|
||||
string resource_type "Resource type affected"
|
||||
string resource_id "Resource identifier"
|
||||
json old_values "Previous values"
|
||||
json new_values "New values"
|
||||
string ip_address "Client IP"
|
||||
string user_agent "Client user agent"
|
||||
json metadata "Additional context"
|
||||
}
|
||||
|
||||
%% Relationships
|
||||
applications ||--o{ static_tokens : "app_id"
|
||||
applications ||--o{ user_sessions : "app_id"
|
||||
available_permissions ||--o{ available_permissions : "parent_scope"
|
||||
available_permissions ||--o{ granted_permissions : "permission_id"
|
||||
static_tokens ||--o{ granted_permissions : "token_id"
|
||||
|
||||
%% Indexes and Constraints
|
||||
applications {
|
||||
index idx_applications_owner_type "owner_type"
|
||||
index idx_applications_created_at "created_at"
|
||||
check owner_type_valid "owner_type IN ('individual', 'team')"
|
||||
check type_not_empty "array_length(type, 1) > 0"
|
||||
}
|
||||
|
||||
static_tokens {
|
||||
index idx_static_tokens_app_id "app_id"
|
||||
index idx_static_tokens_key_hash "key_hash"
|
||||
unique key_hash_unique "key_hash"
|
||||
check type_hmac "type = 'hmac'"
|
||||
}
|
||||
|
||||
user_sessions {
|
||||
index idx_user_sessions_user_id "user_id"
|
||||
index idx_user_sessions_app_id "app_id"
|
||||
index idx_user_sessions_token "session_token"
|
||||
index idx_user_sessions_expires_at "expires_at"
|
||||
index idx_user_sessions_active "active"
|
||||
}
|
||||
|
||||
available_permissions {
|
||||
index idx_available_permissions_scope "scope"
|
||||
index idx_available_permissions_category "category"
|
||||
index idx_available_permissions_parent_scope "parent_scope"
|
||||
index idx_available_permissions_is_system "is_system"
|
||||
}
|
||||
|
||||
granted_permissions {
|
||||
index idx_granted_permissions_token "token_type, token_id"
|
||||
index idx_granted_permissions_permission_id "permission_id"
|
||||
index idx_granted_permissions_scope "scope"
|
||||
index idx_granted_permissions_revoked "revoked"
|
||||
unique token_permission_unique "token_type, token_id, permission_id"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Table Definitions
|
||||
|
||||
### Applications Table
|
||||
|
||||
The central configuration table for all applications in the system.
|
||||
|
||||
```sql
|
||||
CREATE TABLE applications (
|
||||
app_id VARCHAR(100) PRIMARY KEY,
|
||||
app_link TEXT NOT NULL,
|
||||
type TEXT[] NOT NULL DEFAULT '{}',
|
||||
callback_url TEXT,
|
||||
hmac_key TEXT NOT NULL,
|
||||
token_prefix VARCHAR(10),
|
||||
token_renewal_duration BIGINT NOT NULL DEFAULT 3600000000000, -- 1 hour in nanoseconds
|
||||
max_token_duration BIGINT NOT NULL DEFAULT 86400000000000, -- 24 hours in nanoseconds
|
||||
owner_type VARCHAR(20) NOT NULL CHECK (owner_type IN ('individual', 'team')),
|
||||
owner_name VARCHAR(255) NOT NULL,
|
||||
owner_owner VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
|
||||
-- Constraints
|
||||
CONSTRAINT app_id_format CHECK (app_id ~ '^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]$'),
|
||||
CONSTRAINT type_not_empty CHECK (array_length(type, 1) > 0),
|
||||
CONSTRAINT token_prefix_format CHECK (token_prefix IS NULL OR token_prefix ~ '^[A-Z]{2,4}$'),
|
||||
CONSTRAINT valid_durations CHECK (
|
||||
token_renewal_duration > 0 AND
|
||||
max_token_duration > 0 AND
|
||||
max_token_duration > token_renewal_duration
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
#### Field Descriptions
|
||||
- **`app_id`**: Unique application identifier, must follow naming conventions
|
||||
- **`app_link`**: URL to the application for reference
|
||||
- **`type`**: Array of supported token types (`static`, `user`)
|
||||
- **`callback_url`**: OAuth2/SAML callback URL for authentication flows
|
||||
- **`hmac_key`**: HMAC signing key for static token validation
|
||||
- **`token_prefix`**: Custom prefix for generated tokens (2-4 uppercase letters)
|
||||
- **`token_renewal_duration`**: How long tokens can be renewed (nanoseconds)
|
||||
- **`max_token_duration`**: Maximum token lifetime (nanoseconds)
|
||||
- **`owner_type`**: Individual or team ownership
|
||||
- **`owner_name`**: Display name of the owner
|
||||
- **`owner_owner`**: Identifier of the owner (email for individual, team ID for team)
|
||||
|
||||
### Static Tokens Table
|
||||
|
||||
Long-lived API tokens with HMAC-based authentication.
|
||||
|
||||
```sql
|
||||
CREATE TABLE static_tokens (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
app_id VARCHAR(100) NOT NULL REFERENCES applications(app_id) ON DELETE CASCADE,
|
||||
owner_type VARCHAR(20) NOT NULL CHECK (owner_type IN ('individual', 'team')),
|
||||
owner_name VARCHAR(255) NOT NULL,
|
||||
owner_owner VARCHAR(255) NOT NULL,
|
||||
key_hash TEXT NOT NULL UNIQUE,
|
||||
type VARCHAR(10) NOT NULL DEFAULT 'hmac' CHECK (type = 'hmac'),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
#### Security Features
|
||||
- **`key_hash`**: BCrypt hash of the actual token (cost 14)
|
||||
- **Unique constraint**: Prevents token duplication
|
||||
- **Cascade deletion**: Tokens deleted when application is removed
|
||||
- **Owner tracking**: Links tokens to their creators
|
||||
|
||||
### User Sessions Table
|
||||
|
||||
JWT token session tracking with comprehensive metadata.
|
||||
|
||||
```sql
|
||||
CREATE TABLE user_sessions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id VARCHAR(255) NOT NULL,
|
||||
app_id VARCHAR(100) NOT NULL REFERENCES applications(app_id) ON DELETE CASCADE,
|
||||
session_token VARCHAR(255) NOT NULL,
|
||||
permissions TEXT NOT NULL DEFAULT '[]', -- JSON array
|
||||
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
max_valid_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
provider VARCHAR(20) NOT NULL CHECK (provider IN ('header', 'jwt', 'oauth2', 'saml')),
|
||||
metadata JSONB DEFAULT '{}',
|
||||
active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
last_used_at TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Constraints
|
||||
CONSTRAINT valid_session_times CHECK (max_valid_at >= expires_at),
|
||||
CONSTRAINT valid_permissions CHECK (permissions::json IS NOT NULL)
|
||||
);
|
||||
```
|
||||
|
||||
#### Session Management Features
|
||||
- **Session tracking**: Complete session lifecycle management
|
||||
- **Multi-provider support**: Tracks authentication method
|
||||
- **Permission storage**: JSON array of granted permissions
|
||||
- **Activity tracking**: Last used timestamp for session cleanup
|
||||
- **Flexible metadata**: Provider-specific data storage
|
||||
|
||||
### Available Permissions Table
|
||||
|
||||
Hierarchical permission catalog with system and custom permissions.
|
||||
|
||||
```sql
|
||||
CREATE TABLE available_permissions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
scope VARCHAR(255) NOT NULL UNIQUE,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
category VARCHAR(100) NOT NULL DEFAULT 'custom',
|
||||
parent_scope VARCHAR(255) REFERENCES available_permissions(scope) ON DELETE SET NULL,
|
||||
is_system BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
created_by VARCHAR(255) NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_by VARCHAR(255) NOT NULL,
|
||||
|
||||
-- Constraints
|
||||
CONSTRAINT scope_format CHECK (scope ~ '^[a-zA-Z][a-zA-Z0-9._]*[a-zA-Z0-9]$'),
|
||||
CONSTRAINT no_self_reference CHECK (scope != parent_scope)
|
||||
);
|
||||
```
|
||||
|
||||
#### Permission Hierarchy Features
|
||||
- **Hierarchical structure**: Parent-child permission relationships
|
||||
- **System permissions**: Built-in permissions that cannot be deleted
|
||||
- **Flexible scoping**: Dot-notation permission scopes
|
||||
- **Audit tracking**: Creation and modification history
|
||||
|
||||
### Granted Permissions Table
|
||||
|
||||
Token-permission relationship management with revocation support.
|
||||
|
||||
```sql
|
||||
CREATE TABLE granted_permissions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
token_type VARCHAR(10) NOT NULL CHECK (token_type IN ('static', 'user')),
|
||||
token_id UUID NOT NULL,
|
||||
permission_id UUID NOT NULL REFERENCES available_permissions(id) ON DELETE CASCADE,
|
||||
scope VARCHAR(255) NOT NULL, -- Denormalized for performance
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
created_by VARCHAR(255) NOT NULL,
|
||||
revoked BOOLEAN NOT NULL DEFAULT false,
|
||||
revoked_at TIMESTAMP WITH TIME ZONE,
|
||||
revoked_by VARCHAR(255),
|
||||
|
||||
-- Constraints
|
||||
CONSTRAINT unique_token_permission UNIQUE (token_type, token_id, permission_id),
|
||||
CONSTRAINT valid_revocation CHECK (
|
||||
(revoked = false AND revoked_at IS NULL AND revoked_by IS NULL) OR
|
||||
(revoked = true AND revoked_at IS NOT NULL AND revoked_by IS NOT NULL)
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
#### Permission Grant Features
|
||||
- **Multi-token support**: Works with both static and user tokens
|
||||
- **Denormalized scope**: Performance optimization for common queries
|
||||
- **Revocation tracking**: Complete audit trail of permission changes
|
||||
- **Referential integrity**: Maintains consistency with permission catalog
|
||||
|
||||
### Audit Logs Table
|
||||
|
||||
Complete audit trail of all system operations.
|
||||
|
||||
```sql
|
||||
CREATE TABLE audit_logs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
user_id VARCHAR(255) NOT NULL,
|
||||
action VARCHAR(100) NOT NULL,
|
||||
resource_type VARCHAR(50) NOT NULL,
|
||||
resource_id VARCHAR(255),
|
||||
old_values JSONB,
|
||||
new_values JSONB,
|
||||
ip_address INET,
|
||||
user_agent TEXT,
|
||||
metadata JSONB DEFAULT '{}',
|
||||
|
||||
-- Constraints
|
||||
CONSTRAINT valid_action CHECK (action ~ '^[a-z][a-z_]*[a-z]$'),
|
||||
CONSTRAINT valid_resource_type CHECK (resource_type ~ '^[a-z][a-z_]*[a-z]$')
|
||||
);
|
||||
```
|
||||
|
||||
#### Audit Features
|
||||
- **Complete coverage**: All operations logged
|
||||
- **Before/after values**: Full change tracking
|
||||
- **Client context**: IP address and user agent
|
||||
- **Flexible metadata**: Additional context storage
|
||||
- **Time-series data**: Ordered by timestamp for analysis
|
||||
|
||||
---
|
||||
|
||||
## Relationships and Constraints
|
||||
|
||||
### Primary Relationships
|
||||
|
||||
#### **Application → Static Tokens (1:N)**
|
||||
```sql
|
||||
ALTER TABLE static_tokens
|
||||
ADD CONSTRAINT fk_static_tokens_app_id
|
||||
FOREIGN KEY (app_id) REFERENCES applications(app_id) ON DELETE CASCADE;
|
||||
```
|
||||
|
||||
#### **Application → User Sessions (1:N)**
|
||||
```sql
|
||||
ALTER TABLE user_sessions
|
||||
ADD CONSTRAINT fk_user_sessions_app_id
|
||||
FOREIGN KEY (app_id) REFERENCES applications(app_id) ON DELETE CASCADE;
|
||||
```
|
||||
|
||||
#### **Available Permissions → Self (Hierarchy)**
|
||||
```sql
|
||||
ALTER TABLE available_permissions
|
||||
ADD CONSTRAINT fk_available_permissions_parent
|
||||
FOREIGN KEY (parent_scope) REFERENCES available_permissions(scope) ON DELETE SET NULL;
|
||||
```
|
||||
|
||||
#### **Granted Permissions → Available Permissions (N:1)**
|
||||
```sql
|
||||
ALTER TABLE granted_permissions
|
||||
ADD CONSTRAINT fk_granted_permissions_permission
|
||||
FOREIGN KEY (permission_id) REFERENCES available_permissions(id) ON DELETE CASCADE;
|
||||
```
|
||||
|
||||
### Data Integrity Constraints
|
||||
|
||||
#### **Check Constraints**
|
||||
```sql
|
||||
-- Application ID format validation
|
||||
ALTER TABLE applications
|
||||
ADD CONSTRAINT app_id_format
|
||||
CHECK (app_id ~ '^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]$');
|
||||
|
||||
-- Owner type validation
|
||||
ALTER TABLE applications
|
||||
ADD CONSTRAINT owner_type_valid
|
||||
CHECK (owner_type IN ('individual', 'team'));
|
||||
|
||||
-- Token type validation
|
||||
ALTER TABLE static_tokens
|
||||
ADD CONSTRAINT type_hmac
|
||||
CHECK (type = 'hmac');
|
||||
|
||||
-- Permission scope format
|
||||
ALTER TABLE available_permissions
|
||||
ADD CONSTRAINT scope_format
|
||||
CHECK (scope ~ '^[a-zA-Z][a-zA-Z0-9._]*[a-zA-Z0-9]$');
|
||||
|
||||
-- Session time validation
|
||||
ALTER TABLE user_sessions
|
||||
ADD CONSTRAINT valid_session_times
|
||||
CHECK (max_valid_at >= expires_at);
|
||||
```
|
||||
|
||||
#### **Unique Constraints**
|
||||
```sql
|
||||
-- Unique token hashes
|
||||
ALTER TABLE static_tokens
|
||||
ADD CONSTRAINT key_hash_unique UNIQUE (key_hash);
|
||||
|
||||
-- Unique permission scopes
|
||||
ALTER TABLE available_permissions
|
||||
ADD CONSTRAINT scope_unique UNIQUE (scope);
|
||||
|
||||
-- Unique token-permission relationships
|
||||
ALTER TABLE granted_permissions
|
||||
ADD CONSTRAINT unique_token_permission
|
||||
UNIQUE (token_type, token_id, permission_id);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Indexes and Performance
|
||||
|
||||
### Primary Indexes
|
||||
|
||||
#### **Applications Table**
|
||||
```sql
|
||||
-- Primary key index (automatic)
|
||||
CREATE INDEX idx_applications_owner_type ON applications(owner_type);
|
||||
CREATE INDEX idx_applications_created_at ON applications(created_at);
|
||||
CREATE INDEX idx_applications_owner_owner ON applications(owner_owner);
|
||||
```
|
||||
|
||||
#### **Static Tokens Table**
|
||||
```sql
|
||||
-- Foreign key and lookup indexes
|
||||
CREATE INDEX idx_static_tokens_app_id ON static_tokens(app_id);
|
||||
CREATE INDEX idx_static_tokens_key_hash ON static_tokens(key_hash); -- For token verification
|
||||
CREATE INDEX idx_static_tokens_created_at ON static_tokens(created_at);
|
||||
CREATE INDEX idx_static_tokens_owner_owner ON static_tokens(owner_owner);
|
||||
```
|
||||
|
||||
#### **User Sessions Table**
|
||||
```sql
|
||||
-- Session lookup indexes
|
||||
CREATE INDEX idx_user_sessions_user_id ON user_sessions(user_id);
|
||||
CREATE INDEX idx_user_sessions_app_id ON user_sessions(app_id);
|
||||
CREATE INDEX idx_user_sessions_token ON user_sessions(session_token);
|
||||
CREATE INDEX idx_user_sessions_expires_at ON user_sessions(expires_at);
|
||||
CREATE INDEX idx_user_sessions_active ON user_sessions(active);
|
||||
|
||||
-- Composite index for active session lookup
|
||||
CREATE INDEX idx_user_sessions_active_lookup
|
||||
ON user_sessions(user_id, app_id, active) WHERE active = true;
|
||||
|
||||
-- Cleanup index for expired sessions
|
||||
CREATE INDEX idx_user_sessions_cleanup
|
||||
ON user_sessions(expires_at) WHERE active = true;
|
||||
```
|
||||
|
||||
#### **Available Permissions Table**
|
||||
```sql
|
||||
-- Permission lookup indexes
|
||||
CREATE INDEX idx_available_permissions_scope ON available_permissions(scope);
|
||||
CREATE INDEX idx_available_permissions_category ON available_permissions(category);
|
||||
CREATE INDEX idx_available_permissions_parent_scope ON available_permissions(parent_scope);
|
||||
CREATE INDEX idx_available_permissions_is_system ON available_permissions(is_system);
|
||||
|
||||
-- Hierarchy traversal index
|
||||
CREATE INDEX idx_available_permissions_hierarchy
|
||||
ON available_permissions(parent_scope, scope);
|
||||
```
|
||||
|
||||
#### **Granted Permissions Table**
|
||||
```sql
|
||||
-- Token permission lookup
|
||||
CREATE INDEX idx_granted_permissions_token ON granted_permissions(token_type, token_id);
|
||||
CREATE INDEX idx_granted_permissions_permission_id ON granted_permissions(permission_id);
|
||||
CREATE INDEX idx_granted_permissions_scope ON granted_permissions(scope);
|
||||
CREATE INDEX idx_granted_permissions_revoked ON granted_permissions(revoked);
|
||||
|
||||
-- Active permissions index
|
||||
CREATE INDEX idx_granted_permissions_active
|
||||
ON granted_permissions(token_type, token_id, scope) WHERE revoked = false;
|
||||
|
||||
-- Permission cleanup index
|
||||
CREATE INDEX idx_granted_permissions_revoked_at ON granted_permissions(revoked_at);
|
||||
```
|
||||
|
||||
#### **Audit Logs Table**
|
||||
```sql
|
||||
-- Audit query indexes
|
||||
CREATE INDEX idx_audit_logs_timestamp ON audit_logs(timestamp);
|
||||
CREATE INDEX idx_audit_logs_user_id ON audit_logs(user_id);
|
||||
CREATE INDEX idx_audit_logs_action ON audit_logs(action);
|
||||
CREATE INDEX idx_audit_logs_resource ON audit_logs(resource_type, resource_id);
|
||||
|
||||
-- Time-series partitioning preparation
|
||||
CREATE INDEX idx_audit_logs_monthly
|
||||
ON audit_logs(date_trunc('month', timestamp), timestamp);
|
||||
```
|
||||
|
||||
### Performance Optimization
|
||||
|
||||
#### **Partial Indexes**
|
||||
```sql
|
||||
-- Index only active sessions
|
||||
CREATE INDEX idx_active_sessions
|
||||
ON user_sessions(user_id, app_id) WHERE active = true;
|
||||
|
||||
-- Index only non-revoked permissions
|
||||
CREATE INDEX idx_active_permissions
|
||||
ON granted_permissions(token_id, scope) WHERE revoked = false;
|
||||
|
||||
-- Index only system permissions
|
||||
CREATE INDEX idx_system_permissions
|
||||
ON available_permissions(scope, parent_scope) WHERE is_system = true;
|
||||
```
|
||||
|
||||
#### **Composite Indexes**
|
||||
```sql
|
||||
-- Application ownership queries
|
||||
CREATE INDEX idx_applications_ownership
|
||||
ON applications(owner_type, owner_owner, created_at);
|
||||
|
||||
-- Token verification queries
|
||||
CREATE INDEX idx_token_verification
|
||||
ON static_tokens(app_id, key_hash);
|
||||
|
||||
-- Permission evaluation queries
|
||||
CREATE INDEX idx_permission_evaluation
|
||||
ON granted_permissions(token_type, token_id, permission_id, revoked);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Data Protection
|
||||
|
||||
#### **Sensitive Data Handling**
|
||||
```sql
|
||||
-- HMAC keys should be encrypted at application level
|
||||
-- Token hashes use BCrypt with cost 14
|
||||
-- Audit logs contain no sensitive data in plaintext
|
||||
|
||||
-- Row-level security (RLS) for multi-tenant isolation
|
||||
ALTER TABLE applications ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE static_tokens ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE user_sessions ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Example RLS policy for application isolation
|
||||
CREATE POLICY app_owner_policy ON applications
|
||||
FOR ALL TO app_user
|
||||
USING (owner_owner = current_setting('app.user_id'));
|
||||
```
|
||||
|
||||
#### **Access Control**
|
||||
```sql
|
||||
-- Database roles for different access patterns
|
||||
CREATE ROLE kms_api_service;
|
||||
CREATE ROLE kms_readonly;
|
||||
CREATE ROLE kms_admin;
|
||||
|
||||
-- Grant appropriate permissions
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON applications TO kms_api_service;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON static_tokens TO kms_api_service;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON user_sessions TO kms_api_service;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON granted_permissions TO kms_api_service;
|
||||
GRANT SELECT, INSERT ON audit_logs TO kms_api_service;
|
||||
|
||||
-- Read-only access for reporting
|
||||
GRANT SELECT ON ALL TABLES IN SCHEMA public TO kms_readonly;
|
||||
|
||||
-- Admin access for maintenance
|
||||
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO kms_admin;
|
||||
```
|
||||
|
||||
### Audit and Compliance
|
||||
|
||||
#### **Audit Triggers**
|
||||
```sql
|
||||
-- Automatic audit logging trigger
|
||||
CREATE OR REPLACE FUNCTION audit_trigger_function()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
INSERT INTO audit_logs (
|
||||
user_id, action, resource_type, resource_id,
|
||||
old_values, new_values, ip_address
|
||||
) VALUES (
|
||||
current_setting('app.user_id', true),
|
||||
TG_OP,
|
||||
TG_TABLE_NAME,
|
||||
COALESCE(NEW.id, OLD.id)::text,
|
||||
CASE WHEN TG_OP = 'DELETE' THEN row_to_json(OLD) ELSE NULL END,
|
||||
CASE WHEN TG_OP = 'INSERT' THEN row_to_json(NEW)
|
||||
WHEN TG_OP = 'UPDATE' THEN row_to_json(NEW)
|
||||
ELSE NULL END,
|
||||
inet_client_addr()
|
||||
);
|
||||
RETURN COALESCE(NEW, OLD);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
|
||||
-- Apply audit trigger to sensitive tables
|
||||
CREATE TRIGGER audit_applications
|
||||
AFTER INSERT OR UPDATE OR DELETE ON applications
|
||||
FOR EACH ROW EXECUTE FUNCTION audit_trigger_function();
|
||||
|
||||
CREATE TRIGGER audit_static_tokens
|
||||
AFTER INSERT OR UPDATE OR DELETE ON static_tokens
|
||||
FOR EACH ROW EXECUTE FUNCTION audit_trigger_function();
|
||||
|
||||
CREATE TRIGGER audit_granted_permissions
|
||||
AFTER INSERT OR UPDATE OR DELETE ON granted_permissions
|
||||
FOR EACH ROW EXECUTE FUNCTION audit_trigger_function();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Migration Framework
|
||||
|
||||
The KMS uses a custom Go migration system with the following structure:
|
||||
|
||||
```
|
||||
migrations/
|
||||
├── 001_initial_schema.up.sql
|
||||
├── 001_initial_schema.down.sql
|
||||
├── 002_user_sessions.up.sql
|
||||
├── 002_user_sessions.down.sql
|
||||
├── 003_add_token_prefix.up.sql
|
||||
└── 003_add_token_prefix.down.sql
|
||||
```
|
||||
|
||||
#### **Migration Management**
|
||||
```go
|
||||
// File: internal/database/migrations.go
|
||||
type Migration struct {
|
||||
Version int
|
||||
Name string
|
||||
UpScript string
|
||||
DownScript string
|
||||
AppliedAt time.Time
|
||||
}
|
||||
|
||||
func RunMigrations(db *sql.DB, migrationsPath string) error {
|
||||
// Create migration tracking table
|
||||
createMigrationTable(db)
|
||||
|
||||
// Get applied migrations
|
||||
applied, err := getAppliedMigrations(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Run pending migrations
|
||||
return runPendingMigrations(db, migrationsPath, applied)
|
||||
}
|
||||
```
|
||||
|
||||
### Schema Versioning
|
||||
|
||||
#### **Version Control**
|
||||
- **Sequential numbering**: 001, 002, 003...
|
||||
- **Descriptive names**: Clear migration purpose
|
||||
- **Rollback support**: Down scripts for every migration
|
||||
- **Atomic operations**: Each migration in a transaction
|
||||
|
||||
#### **Migration Best Practices**
|
||||
```sql
|
||||
-- Always start with BEGIN; and end with COMMIT;
|
||||
BEGIN;
|
||||
|
||||
-- Add new columns with default values
|
||||
ALTER TABLE applications
|
||||
ADD COLUMN token_prefix VARCHAR(10) DEFAULT NULL;
|
||||
|
||||
-- Add constraints after data migration
|
||||
ALTER TABLE applications
|
||||
ADD CONSTRAINT token_prefix_format
|
||||
CHECK (token_prefix IS NULL OR token_prefix ~ '^[A-Z]{2,4}$');
|
||||
|
||||
-- Create indexes concurrently (outside transaction)
|
||||
COMMIT;
|
||||
|
||||
CREATE INDEX CONCURRENTLY idx_applications_token_prefix
|
||||
ON applications(token_prefix) WHERE token_prefix IS NOT NULL;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Query Patterns
|
||||
|
||||
### Common Query Patterns
|
||||
|
||||
#### **Application Queries**
|
||||
```sql
|
||||
-- Get application with ownership check
|
||||
SELECT a.* FROM applications a
|
||||
WHERE a.app_id = $1
|
||||
AND (a.owner_owner = $2 OR $2 = 'admin@example.com');
|
||||
|
||||
-- List applications for user with pagination
|
||||
SELECT a.app_id, a.app_link, a.owner_name, a.created_at
|
||||
FROM applications a
|
||||
WHERE a.owner_owner = $1
|
||||
ORDER BY a.created_at DESC
|
||||
LIMIT $2 OFFSET $3;
|
||||
```
|
||||
|
||||
#### **Token Verification Queries**
|
||||
```sql
|
||||
-- Verify static token
|
||||
SELECT st.id, st.app_id, st.key_hash
|
||||
FROM static_tokens st
|
||||
WHERE st.app_id = $1;
|
||||
|
||||
-- Get token permissions
|
||||
SELECT ap.scope
|
||||
FROM granted_permissions gp
|
||||
JOIN available_permissions ap ON gp.permission_id = ap.id
|
||||
WHERE gp.token_type = 'static'
|
||||
AND gp.token_id = $1
|
||||
AND gp.revoked = false;
|
||||
```
|
||||
|
||||
#### **Session Management Queries**
|
||||
```sql
|
||||
-- Get active user session
|
||||
SELECT us.* FROM user_sessions us
|
||||
WHERE us.user_id = $1
|
||||
AND us.app_id = $2
|
||||
AND us.active = true
|
||||
AND us.expires_at > NOW()
|
||||
ORDER BY us.created_at DESC
|
||||
LIMIT 1;
|
||||
|
||||
-- Clean up expired sessions
|
||||
UPDATE user_sessions
|
||||
SET active = false, updated_at = NOW()
|
||||
WHERE active = true
|
||||
AND expires_at < NOW();
|
||||
```
|
||||
|
||||
#### **Permission Evaluation Queries**
|
||||
```sql
|
||||
-- Check user permission
|
||||
WITH user_permissions AS (
|
||||
SELECT DISTINCT ap.scope
|
||||
FROM user_sessions us
|
||||
JOIN granted_permissions gp ON gp.token_type = 'user'
|
||||
JOIN available_permissions ap ON gp.permission_id = ap.id
|
||||
WHERE us.user_id = $1
|
||||
AND us.app_id = $2
|
||||
AND us.active = true
|
||||
AND us.expires_at > NOW()
|
||||
AND gp.revoked = false
|
||||
)
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM user_permissions
|
||||
WHERE scope = $3 OR scope = split_part($3, '.', 1)
|
||||
);
|
||||
|
||||
-- Get permission hierarchy
|
||||
WITH RECURSIVE permission_tree AS (
|
||||
-- Base case: root permissions
|
||||
SELECT id, scope, name, parent_scope, 0 as level
|
||||
FROM available_permissions
|
||||
WHERE parent_scope IS NULL
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Recursive case: child permissions
|
||||
SELECT ap.id, ap.scope, ap.name, ap.parent_scope, pt.level + 1
|
||||
FROM available_permissions ap
|
||||
JOIN permission_tree pt ON ap.parent_scope = pt.scope
|
||||
)
|
||||
SELECT * FROM permission_tree ORDER BY level, scope;
|
||||
```
|
||||
|
||||
### Performance Tuning
|
||||
|
||||
#### **Query Optimization**
|
||||
```sql
|
||||
-- Use EXPLAIN ANALYZE for query planning
|
||||
EXPLAIN (ANALYZE, BUFFERS)
|
||||
SELECT st.id FROM static_tokens st
|
||||
WHERE st.app_id = 'test-app' AND st.key_hash = 'hash123';
|
||||
|
||||
-- Optimize with covering indexes
|
||||
CREATE INDEX idx_static_tokens_covering
|
||||
ON static_tokens(app_id, key_hash)
|
||||
INCLUDE (id, created_at);
|
||||
|
||||
-- Use partial indexes for frequent filters
|
||||
CREATE INDEX idx_active_sessions_partial
|
||||
ON user_sessions(user_id, app_id, expires_at)
|
||||
WHERE active = true;
|
||||
```
|
||||
|
||||
#### **Connection Pooling Configuration**
|
||||
```yaml
|
||||
database:
|
||||
host: postgres
|
||||
port: 5432
|
||||
name: kms
|
||||
user: kms_api_service
|
||||
max_open_connections: 25
|
||||
max_idle_connections: 5
|
||||
connection_max_lifetime: 300s
|
||||
connection_max_idle_time: 60s
|
||||
```
|
||||
|
||||
This database schema documentation provides comprehensive coverage of the KMS data model, suitable for developers, database administrators, and system architects who need to understand, maintain, or extend the database layer.
|
||||
@ -1,498 +0,0 @@
|
||||
# Security Audit Report - KMS API Key Management Service
|
||||
|
||||
**Date**: 2025-08-23
|
||||
**Auditor**: Claude Code Security Analysis
|
||||
**Version**: v1.0.0
|
||||
**Scope**: Complete codebase analysis including Go backend, React frontend, database schema, and Docker configuration
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This comprehensive security audit identified **17 critical security vulnerabilities**, **12 high-risk issues**, and **8 medium-risk concerns** across the KMS (Key Management Service) codebase. The system demonstrates good security practices in some areas but has significant gaps that require immediate attention, particularly in authentication, input validation, and configuration security.
|
||||
|
||||
### Risk Classification
|
||||
- **🔴 Critical (17)**: Immediate action required - vulnerabilities that could lead to system compromise
|
||||
- **🟠 High (12)**: High priority - significant security risks
|
||||
- **🟡 Medium (8)**: Medium priority - security improvements needed
|
||||
- **🟢 Low (15)**: Best practice improvements
|
||||
|
||||
---
|
||||
|
||||
## Critical Security Vulnerabilities (🔴)
|
||||
|
||||
### C-01: Authentication Bypass via Header Manipulation
|
||||
**File**: `internal/handlers/auth.go:46`, `internal/middleware/middleware.go`
|
||||
**Severity**: Critical
|
||||
**CVSS Score**: 9.8
|
||||
|
||||
The system relies solely on the `X-User-Email` header for user identification without verification.
|
||||
|
||||
```go
|
||||
userID := c.GetHeader("X-User-Email")
|
||||
if userID == "" {
|
||||
h.logger.Warn("User email not found in headers")
|
||||
// ... returns 401
|
||||
}
|
||||
```
|
||||
|
||||
**Impact**: Attackers can bypass authentication by setting arbitrary `X-User-Email` headers.
|
||||
**Recommendation**: Implement proper JWT/OAuth2 token validation or header signature verification.
|
||||
|
||||
### C-02: Hardcoded Production Secrets
|
||||
**File**: `internal/config/config.go:110,117,163`
|
||||
**Severity**: Critical
|
||||
|
||||
Default production secrets are hardcoded:
|
||||
- JWT Secret: `"bootstrap-jwt-secret-change-in-production"`
|
||||
- HMAC Key: `"bootstrap-hmac-key-change-in-production"`
|
||||
|
||||
**Impact**: Production deployments using default secrets are completely compromised.
|
||||
**Recommendation**: Force secret generation on startup if defaults are detected.
|
||||
|
||||
### C-03: SQL Injection Vulnerability in Dynamic Queries
|
||||
**File**: `internal/repository/postgres/application_repository.go:281-285`
|
||||
**Severity**: Critical
|
||||
|
||||
Dynamic query construction without proper parameterization:
|
||||
```go
|
||||
query := fmt.Sprintf(`
|
||||
UPDATE applications
|
||||
SET %s
|
||||
WHERE app_id = $%d
|
||||
`, strings.Join(setParts, ", "), argIndex)
|
||||
```
|
||||
|
||||
**Impact**: Potential SQL injection through crafted update requests.
|
||||
**Recommendation**: Use proper parameterized queries for all dynamic SQL.
|
||||
|
||||
### C-04: JWT Token Stored in URL Parameters
|
||||
**File**: `internal/handlers/auth.go:72-73`
|
||||
**Severity**: Critical
|
||||
|
||||
JWT tokens are passed via URL query parameters:
|
||||
```go
|
||||
response := domain.LoginResponse{
|
||||
RedirectURL: req.RedirectURI + "?token=" + token,
|
||||
}
|
||||
```
|
||||
|
||||
**Impact**: Tokens exposed in server logs, browser history, and referrer headers.
|
||||
**Recommendation**: Use POST requests or secure cookie-based token delivery.
|
||||
|
||||
### C-05: Weak Token Generation Fallback
|
||||
**File**: `internal/auth/jwt.go:275-277`
|
||||
**Severity**: Critical
|
||||
|
||||
Fallback to predictable timestamp-based token IDs:
|
||||
```go
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
return fmt.Sprintf("jti_%d", time.Now().UnixNano())
|
||||
}
|
||||
```
|
||||
|
||||
**Impact**: Predictable token IDs enable token enumeration attacks.
|
||||
**Recommendation**: Fail securely - abort token generation if crypto/rand fails.
|
||||
|
||||
### C-06: Information Disclosure in Error Messages
|
||||
**File**: Multiple files including `internal/handlers/*.go`
|
||||
**Severity**: Critical
|
||||
|
||||
Database and system errors are exposed to clients:
|
||||
```go
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Bad Request",
|
||||
"message": "Invalid request body: " + err.Error(),
|
||||
})
|
||||
```
|
||||
|
||||
**Impact**: Internal system information leaked to attackers.
|
||||
**Recommendation**: Log detailed errors internally, return generic messages to clients.
|
||||
|
||||
### C-07: No Rate Limiting on Authentication Endpoints
|
||||
**File**: Authentication handlers lack rate limiting
|
||||
**Severity**: Critical
|
||||
|
||||
Authentication endpoints are not protected by rate limiting, enabling brute force attacks.
|
||||
|
||||
**Impact**: Unlimited authentication attempts possible.
|
||||
**Recommendation**: Implement strict rate limiting on `/api/login` and `/api/verify`.
|
||||
|
||||
### C-08: Missing CSRF Protection
|
||||
**File**: All API handlers
|
||||
**Severity**: Critical
|
||||
|
||||
No CSRF token validation on state-changing operations.
|
||||
|
||||
**Impact**: Cross-site request forgery attacks possible.
|
||||
**Recommendation**: Implement CSRF protection for all POST/PUT/DELETE requests.
|
||||
|
||||
### C-09: Insecure Password Storage (bcrypt with default cost)
|
||||
**File**: `internal/crypto/token.go:73`
|
||||
**Severity**: Critical
|
||||
|
||||
Using bcrypt with default cost (10) which is insufficient for 2025:
|
||||
```go
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(token), bcrypt.DefaultCost)
|
||||
```
|
||||
|
||||
**Impact**: Tokens vulnerable to offline brute force attacks.
|
||||
**Recommendation**: Use minimum cost of 14 for bcrypt in 2025.
|
||||
|
||||
### C-10: Lack of Input Validation on Critical Fields
|
||||
**File**: `internal/handlers/application.go`, `internal/handlers/token.go`
|
||||
**Severity**: Critical
|
||||
|
||||
No validation on critical fields like app_id, permissions, callback URLs.
|
||||
|
||||
**Impact**: Injection attacks, malicious redirects, privilege escalation.
|
||||
**Recommendation**: Implement comprehensive input validation.
|
||||
|
||||
### C-11: Missing Authorization Checks
|
||||
**File**: `internal/handlers/application.go:164` (delete)
|
||||
**Severity**: Critical
|
||||
|
||||
No verification that users can only access/modify their own resources.
|
||||
|
||||
**Impact**: Users can modify/delete other users' applications and tokens.
|
||||
**Recommendation**: Implement resource ownership validation.
|
||||
|
||||
### C-12: Timing Attack Vulnerability
|
||||
**File**: `internal/crypto/token.go:82-84`
|
||||
**Severity**: Critical
|
||||
|
||||
Non-constant time token comparison:
|
||||
```go
|
||||
func (tg *TokenGenerator) VerifyToken(token, hash string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(token))
|
||||
return err == nil
|
||||
}
|
||||
```
|
||||
|
||||
**Impact**: Token hashes vulnerable to timing attacks.
|
||||
**Recommendation**: bcrypt.CompareHashAndPassword is already constant-time, but error handling should be consistent.
|
||||
|
||||
### C-13: Database Connection String in Logs
|
||||
**File**: `internal/config/config.go:271`
|
||||
**Severity**: Critical
|
||||
|
||||
Database passwords logged in connection strings:
|
||||
```go
|
||||
func (c *Config) GetDatabaseDSN() string {
|
||||
return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
|
||||
// ... includes password
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Impact**: Database credentials exposed in logs.
|
||||
**Recommendation**: Mask passwords in connection strings used for logging.
|
||||
|
||||
### C-14: Insufficient Session Security
|
||||
**File**: `internal/domain/session.go`
|
||||
**Severity**: Critical
|
||||
|
||||
Sessions lack proper security attributes:
|
||||
- No secure cookie flags
|
||||
- No SameSite protection
|
||||
- No proper session invalidation
|
||||
|
||||
**Impact**: Session hijacking via XSS or network interception.
|
||||
**Recommendation**: Implement secure session management.
|
||||
|
||||
### C-15: JWT Secret Exposed in Token Info
|
||||
**File**: `internal/auth/jwt.go:282-300`
|
||||
**Severity**: Critical
|
||||
|
||||
Token debugging function may expose sensitive information:
|
||||
```go
|
||||
func (j *JWTManager) GetTokenInfo(tokenString string) map[string]interface{} {
|
||||
// Returns all claims without filtering
|
||||
}
|
||||
```
|
||||
|
||||
**Impact**: Internal system information disclosure.
|
||||
**Recommendation**: Filter sensitive fields in debugging output.
|
||||
|
||||
### C-16: Cache Poisoning Vulnerability
|
||||
**File**: `internal/cache/cache.go`
|
||||
**Severity**: Critical
|
||||
|
||||
No validation of cached data integrity.
|
||||
|
||||
**Impact**: Attackers could poison cache with malicious data.
|
||||
**Recommendation**: Implement cache entry validation and integrity checks.
|
||||
|
||||
### C-17: Unrestricted File Upload (Docker Volumes)
|
||||
**File**: `docker-compose.yml:74`
|
||||
**Severity**: Critical
|
||||
|
||||
Docker volumes mounted without proper restrictions:
|
||||
```yaml
|
||||
volumes:
|
||||
- ./migrations:/app/migrations:ro,Z
|
||||
```
|
||||
|
||||
**Impact**: Potential container escape if migrations directory is writable.
|
||||
**Recommendation**: Ensure proper file permissions and use read-only mounts.
|
||||
|
||||
---
|
||||
|
||||
## High Risk Issues (🟠)
|
||||
|
||||
### H-01: Weak HMAC Signature Validation
|
||||
**File**: `internal/middleware/security.go:404,468-495`
|
||||
|
||||
Missing timestamp validation and replay attack prevention in HMAC signature validation.
|
||||
|
||||
### H-02: Insufficient HTTPS Enforcement
|
||||
**File**: `internal/middleware/security.go:139-141`
|
||||
|
||||
HSTS headers only set when TLS is detected, not enforced.
|
||||
|
||||
### H-03: Permissive CORS Configuration
|
||||
**File**: Missing CORS configuration
|
||||
|
||||
No explicit CORS policy defined, potentially allowing any origin.
|
||||
|
||||
### H-04: Inadequate Logging of Security Events
|
||||
**File**: Multiple security middleware files
|
||||
|
||||
Security failures not properly logged for monitoring.
|
||||
|
||||
### H-05: Missing Security Headers
|
||||
**File**: `internal/middleware/security.go:128-145`
|
||||
|
||||
Several important security headers missing:
|
||||
- `Permissions-Policy`
|
||||
- `Cross-Origin-Embedder-Policy`
|
||||
- `Cross-Origin-Resource-Policy`
|
||||
|
||||
### H-06: Insecure Random Number Generation Fallback
|
||||
**File**: `internal/crypto/token.go:44-46`
|
||||
|
||||
No validation that crypto/rand is working properly.
|
||||
|
||||
### H-07: Database Connection Pool Vulnerabilities
|
||||
**File**: `internal/database/postgres.go`
|
||||
|
||||
No proper connection validation or pool exhaustion protection.
|
||||
|
||||
### H-08: Metrics Endpoint Security
|
||||
**File**: Docker expose port 9090
|
||||
|
||||
Metrics endpoint exposed without authentication.
|
||||
|
||||
### H-09: Debug Information Leakage
|
||||
**File**: Multiple files with debug logging
|
||||
|
||||
Extensive debug logging may expose sensitive information in production.
|
||||
|
||||
### H-10: Insufficient Audit Logging
|
||||
**File**: `internal/audit/audit.go`
|
||||
|
||||
Audit logs missing critical security events like failed authentications.
|
||||
|
||||
### H-11: Frontend XSS Vulnerabilities
|
||||
**File**: `kms-frontend/src/services/apiService.ts:114-116`
|
||||
|
||||
User input parsed and used without proper sanitization:
|
||||
```typescript
|
||||
const userData = JSON.parse(user);
|
||||
config.headers['X-User-Email'] = userData.email;
|
||||
```
|
||||
|
||||
### H-12: Environment Variable Injection
|
||||
**File**: `kms-frontend/src/services/apiService.ts:98`
|
||||
|
||||
Base URL from environment variables without validation.
|
||||
|
||||
---
|
||||
|
||||
## Medium Risk Issues (🟡)
|
||||
|
||||
### M-01: Incomplete Error Handling
|
||||
**File**: Multiple files
|
||||
|
||||
Inconsistent error handling patterns across the codebase.
|
||||
|
||||
### M-02: Missing API Versioning
|
||||
**File**: API endpoints
|
||||
|
||||
No proper API versioning strategy implemented.
|
||||
|
||||
### M-03: Insufficient Input Length Limits
|
||||
**File**: Database schema and API handlers
|
||||
|
||||
No explicit length limits on user inputs.
|
||||
|
||||
### M-04: Weak Password Complexity Requirements
|
||||
**File**: No password policy enforcement
|
||||
|
||||
No password complexity requirements defined.
|
||||
|
||||
### M-05: Missing Request ID Tracing
|
||||
**File**: Logging infrastructure
|
||||
|
||||
No request ID correlation for security incident investigation.
|
||||
|
||||
### M-06: Inadequate Database Index Security
|
||||
**File**: `migrations/001_initial_schema.up.sql`
|
||||
|
||||
Some indexes might leak information through timing attacks.
|
||||
|
||||
### M-07: Cache TTL Security
|
||||
**File**: `internal/cache/cache.go`
|
||||
|
||||
No validation of cache TTL values, potentially allowing cache flooding.
|
||||
|
||||
### M-08: File Permission Issues
|
||||
**File**: Docker configuration
|
||||
|
||||
Potential file permission issues in Docker deployment.
|
||||
|
||||
---
|
||||
|
||||
## Incomplete/Dead Code Analysis
|
||||
|
||||
### TODO Items Found
|
||||
- `internal/auth/permissions.go:296`: "TODO: In a real implementation, this would:"
|
||||
- Incomplete permission evaluation system
|
||||
- Hardcoded test permissions instead of database-driven
|
||||
|
||||
### Debug Code in Production
|
||||
Multiple debug endpoints and verbose logging enabled in production configuration.
|
||||
|
||||
### Unused Security Features
|
||||
- SAML authentication implemented but not properly configured
|
||||
- OAuth2 providers implemented but not fully integrated
|
||||
- Redis caching available but disabled by default
|
||||
|
||||
### Missing Components
|
||||
- Rate limiting implementation incomplete
|
||||
- Token revocation not fully implemented
|
||||
- Session management incomplete
|
||||
- Audit trail system basic
|
||||
|
||||
---
|
||||
|
||||
## Security Architecture Gaps
|
||||
|
||||
### 1. Authentication Architecture
|
||||
- Relies on unverified HTTP headers
|
||||
- No multi-factor authentication
|
||||
- No account lockout mechanisms
|
||||
- No session timeout enforcement
|
||||
|
||||
### 2. Authorization Model
|
||||
- Insufficient granular permissions
|
||||
- No resource-level access control
|
||||
- Missing role-based access control implementation
|
||||
- Hierarchical permissions not enforced
|
||||
|
||||
### 3. Data Protection
|
||||
- No data encryption at rest
|
||||
- No field-level encryption for sensitive data
|
||||
- Missing data anonymization capabilities
|
||||
- No secure data deletion
|
||||
|
||||
### 4. Network Security
|
||||
- Missing network segmentation
|
||||
- No SSL/TLS termination configuration
|
||||
- Inadequate firewall rules
|
||||
- Missing VPC/network isolation
|
||||
|
||||
### 5. Monitoring & Alerting
|
||||
- No security event monitoring
|
||||
- Missing intrusion detection
|
||||
- No automated security alerts
|
||||
- Insufficient audit trail
|
||||
|
||||
---
|
||||
|
||||
## Recommended Immediate Actions
|
||||
|
||||
### Priority 1 (Fix Immediately)
|
||||
1. Replace all hardcoded secrets with secure generation
|
||||
2. Implement proper authentication validation
|
||||
3. Add input validation on all endpoints
|
||||
4. Fix SQL injection vulnerability
|
||||
5. Remove token from URL parameters
|
||||
6. Add authorization checks to all resources
|
||||
|
||||
### Priority 2 (Fix Within 1 Week)
|
||||
1. Implement proper rate limiting
|
||||
2. Add CSRF protection
|
||||
3. Secure session management
|
||||
4. Add comprehensive security headers
|
||||
5. Fix error message information disclosure
|
||||
6. Implement proper HTTPS enforcement
|
||||
|
||||
### Priority 3 (Fix Within 1 Month)
|
||||
1. Complete audit logging system
|
||||
2. Implement proper CORS policy
|
||||
3. Add API versioning
|
||||
4. Enhance monitoring and alerting
|
||||
5. Complete security testing
|
||||
6. Document security procedures
|
||||
|
||||
---
|
||||
|
||||
## Security Testing Recommendations
|
||||
|
||||
### Automated Security Testing
|
||||
1. **SAST (Static Application Security Testing)**
|
||||
- Run gosec for Go code analysis
|
||||
- Use ESLint security rules for React frontend
|
||||
- Implement pre-commit security hooks
|
||||
|
||||
2. **DAST (Dynamic Application Security Testing)**
|
||||
- OWASP ZAP scanning
|
||||
- SQL injection testing
|
||||
- Authentication bypass testing
|
||||
- Rate limiting validation
|
||||
|
||||
3. **Container Security**
|
||||
- Scan Docker images for vulnerabilities
|
||||
- Validate container configurations
|
||||
- Check for privilege escalation
|
||||
|
||||
### Manual Security Testing
|
||||
1. **Penetration Testing**
|
||||
- Authentication mechanisms
|
||||
- Authorization bypass attempts
|
||||
- Input validation testing
|
||||
- Session management testing
|
||||
|
||||
2. **Code Review**
|
||||
- Security-focused code reviews
|
||||
- Architecture security assessment
|
||||
- Threat modeling exercises
|
||||
|
||||
---
|
||||
|
||||
## Compliance Considerations
|
||||
|
||||
### Standards Alignment
|
||||
- **OWASP Top 10 2021**: Multiple vulnerabilities identified
|
||||
- **NIST Cybersecurity Framework**: Gaps in Identify, Protect, and Detect functions
|
||||
- **ISO 27001**: Missing security controls and procedures
|
||||
|
||||
### Regulatory Compliance
|
||||
- **GDPR**: Missing data protection controls
|
||||
- **SOC 2**: Insufficient security controls for Trust Services Criteria
|
||||
- **PCI DSS**: Not applicable but good security practices missing
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
The KMS system shows architectural understanding but has critical security vulnerabilities that must be addressed immediately. The authentication system is particularly vulnerable and requires complete redesign. Input validation, error handling, and secure configuration management need significant improvement.
|
||||
|
||||
**Overall Security Rating: HIGH RISK** ⚠️
|
||||
|
||||
Immediate action is required to address the critical vulnerabilities before any production deployment. A comprehensive security remediation plan should be implemented with regular security assessments.
|
||||
|
||||
---
|
||||
|
||||
**Report Generation Date**: 2025-08-23
|
||||
**Next Review Recommended**: After critical issues are resolved
|
||||
**Contact**: This is an automated security analysis report
|
||||
Reference in New Issue
Block a user