482 lines
13 KiB
Markdown
482 lines
13 KiB
Markdown
# Configuration Validation
|
|
|
|
You are a configuration management expert specializing in validating, testing, and ensuring the correctness of application configurations. Create comprehensive validation schemas, implement configuration testing strategies, and ensure configurations are secure, consistent, and error-free across all environments.
|
|
|
|
## Context
|
|
The user needs to validate configuration files, implement configuration schemas, ensure consistency across environments, and prevent configuration-related errors. Focus on creating robust validation rules, type safety, security checks, and automated validation processes.
|
|
|
|
## Requirements
|
|
$ARGUMENTS
|
|
|
|
## Instructions
|
|
|
|
### 1. Configuration Analysis
|
|
|
|
Analyze existing configuration structure and identify validation needs:
|
|
|
|
```python
|
|
import os
|
|
import yaml
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Dict, List, Any
|
|
|
|
class ConfigurationAnalyzer:
|
|
def analyze_project(self, project_path: str) -> Dict[str, Any]:
|
|
analysis = {
|
|
'config_files': self._find_config_files(project_path),
|
|
'security_issues': self._check_security_issues(project_path),
|
|
'consistency_issues': self._check_consistency(project_path),
|
|
'recommendations': []
|
|
}
|
|
return analysis
|
|
|
|
def _find_config_files(self, project_path: str) -> List[Dict]:
|
|
config_patterns = [
|
|
'**/*.json', '**/*.yaml', '**/*.yml', '**/*.toml',
|
|
'**/*.ini', '**/*.env*', '**/config.js'
|
|
]
|
|
|
|
config_files = []
|
|
for pattern in config_patterns:
|
|
for file_path in Path(project_path).glob(pattern):
|
|
if not self._should_ignore(file_path):
|
|
config_files.append({
|
|
'path': str(file_path),
|
|
'type': self._detect_config_type(file_path),
|
|
'environment': self._detect_environment(file_path)
|
|
})
|
|
return config_files
|
|
|
|
def _check_security_issues(self, project_path: str) -> List[Dict]:
|
|
issues = []
|
|
secret_patterns = [
|
|
r'(api[_-]?key|apikey)',
|
|
r'(secret|password|passwd)',
|
|
r'(token|auth)',
|
|
r'(aws[_-]?access)'
|
|
]
|
|
|
|
for config_file in self._find_config_files(project_path):
|
|
content = Path(config_file['path']).read_text()
|
|
for pattern in secret_patterns:
|
|
if re.search(pattern, content, re.IGNORECASE):
|
|
if self._looks_like_real_secret(content, pattern):
|
|
issues.append({
|
|
'file': config_file['path'],
|
|
'type': 'potential_secret',
|
|
'severity': 'high'
|
|
})
|
|
return issues
|
|
```
|
|
|
|
### 2. Schema Validation
|
|
|
|
Implement configuration schema validation with JSON Schema:
|
|
|
|
```typescript
|
|
import Ajv from 'ajv';
|
|
import ajvFormats from 'ajv-formats';
|
|
import { JSONSchema7 } from 'json-schema';
|
|
|
|
interface ValidationResult {
|
|
valid: boolean;
|
|
errors?: Array<{
|
|
path: string;
|
|
message: string;
|
|
keyword: string;
|
|
}>;
|
|
}
|
|
|
|
export class ConfigValidator {
|
|
private ajv: Ajv;
|
|
|
|
constructor() {
|
|
this.ajv = new Ajv({
|
|
allErrors: true,
|
|
strict: false,
|
|
coerceTypes: true
|
|
});
|
|
ajvFormats(this.ajv);
|
|
this.addCustomFormats();
|
|
}
|
|
|
|
private addCustomFormats() {
|
|
this.ajv.addFormat('url-https', {
|
|
type: 'string',
|
|
validate: (data: string) => {
|
|
try {
|
|
return new URL(data).protocol === 'https:';
|
|
} catch { return false; }
|
|
}
|
|
});
|
|
|
|
this.ajv.addFormat('port', {
|
|
type: 'number',
|
|
validate: (data: number) => data >= 1 && data <= 65535
|
|
});
|
|
|
|
this.ajv.addFormat('duration', {
|
|
type: 'string',
|
|
validate: /^\d+[smhd]$/
|
|
});
|
|
}
|
|
|
|
validate(configData: any, schemaName: string): ValidationResult {
|
|
const validate = this.ajv.getSchema(schemaName);
|
|
if (!validate) throw new Error(`Schema '${schemaName}' not found`);
|
|
|
|
const valid = validate(configData);
|
|
|
|
if (!valid && validate.errors) {
|
|
return {
|
|
valid: false,
|
|
errors: validate.errors.map(error => ({
|
|
path: error.instancePath || '/',
|
|
message: error.message || 'Validation error',
|
|
keyword: error.keyword
|
|
}))
|
|
};
|
|
}
|
|
return { valid: true };
|
|
}
|
|
}
|
|
|
|
// Example schema
|
|
export const schemas = {
|
|
database: {
|
|
type: 'object',
|
|
properties: {
|
|
host: { type: 'string', format: 'hostname' },
|
|
port: { type: 'integer', format: 'port' },
|
|
database: { type: 'string', minLength: 1 },
|
|
user: { type: 'string', minLength: 1 },
|
|
password: { type: 'string', minLength: 8 },
|
|
ssl: {
|
|
type: 'object',
|
|
properties: {
|
|
enabled: { type: 'boolean' }
|
|
},
|
|
required: ['enabled']
|
|
}
|
|
},
|
|
required: ['host', 'port', 'database', 'user', 'password']
|
|
}
|
|
};
|
|
```
|
|
|
|
### 3. Environment-Specific Validation
|
|
|
|
```python
|
|
from typing import Dict, List, Any
|
|
|
|
class EnvironmentValidator:
|
|
def __init__(self):
|
|
self.environments = ['development', 'staging', 'production']
|
|
self.environment_rules = {
|
|
'development': {
|
|
'allow_debug': True,
|
|
'require_https': False,
|
|
'min_password_length': 8
|
|
},
|
|
'production': {
|
|
'allow_debug': False,
|
|
'require_https': True,
|
|
'min_password_length': 16,
|
|
'require_encryption': True
|
|
}
|
|
}
|
|
|
|
def validate_config(self, config: Dict, environment: str) -> List[Dict]:
|
|
if environment not in self.environment_rules:
|
|
raise ValueError(f"Unknown environment: {environment}")
|
|
|
|
rules = self.environment_rules[environment]
|
|
violations = []
|
|
|
|
if not rules['allow_debug'] and config.get('debug', False):
|
|
violations.append({
|
|
'rule': 'no_debug_in_production',
|
|
'message': 'Debug mode not allowed in production',
|
|
'severity': 'critical'
|
|
})
|
|
|
|
if rules['require_https']:
|
|
urls = self._extract_urls(config)
|
|
for url_path, url in urls:
|
|
if url.startswith('http://') and 'localhost' not in url:
|
|
violations.append({
|
|
'rule': 'require_https',
|
|
'message': f'HTTPS required for {url_path}',
|
|
'severity': 'high'
|
|
})
|
|
|
|
return violations
|
|
```
|
|
|
|
### 4. Configuration Testing
|
|
|
|
```typescript
|
|
import { describe, it, expect } from '@jest/globals';
|
|
import { ConfigValidator } from './config-validator';
|
|
|
|
describe('Configuration Validation', () => {
|
|
let validator: ConfigValidator;
|
|
|
|
beforeEach(() => {
|
|
validator = new ConfigValidator();
|
|
});
|
|
|
|
it('should validate database config', () => {
|
|
const config = {
|
|
host: 'localhost',
|
|
port: 5432,
|
|
database: 'myapp',
|
|
user: 'dbuser',
|
|
password: 'securepass123'
|
|
};
|
|
|
|
const result = validator.validate(config, 'database');
|
|
expect(result.valid).toBe(true);
|
|
});
|
|
|
|
it('should reject invalid port', () => {
|
|
const config = {
|
|
host: 'localhost',
|
|
port: 70000,
|
|
database: 'myapp',
|
|
user: 'dbuser',
|
|
password: 'securepass123'
|
|
};
|
|
|
|
const result = validator.validate(config, 'database');
|
|
expect(result.valid).toBe(false);
|
|
});
|
|
});
|
|
```
|
|
|
|
### 5. Runtime Validation
|
|
|
|
```typescript
|
|
import { EventEmitter } from 'events';
|
|
import * as chokidar from 'chokidar';
|
|
|
|
export class RuntimeConfigValidator extends EventEmitter {
|
|
private validator: ConfigValidator;
|
|
private currentConfig: any;
|
|
|
|
async initialize(configPath: string): Promise<void> {
|
|
this.currentConfig = await this.loadAndValidate(configPath);
|
|
this.watchConfig(configPath);
|
|
}
|
|
|
|
private async loadAndValidate(configPath: string): Promise<any> {
|
|
const config = await this.loadConfig(configPath);
|
|
|
|
const validationResult = this.validator.validate(
|
|
config,
|
|
this.detectEnvironment()
|
|
);
|
|
|
|
if (!validationResult.valid) {
|
|
this.emit('validation:error', {
|
|
path: configPath,
|
|
errors: validationResult.errors
|
|
});
|
|
|
|
if (!this.isDevelopment()) {
|
|
throw new Error('Configuration validation failed');
|
|
}
|
|
}
|
|
|
|
return config;
|
|
}
|
|
|
|
private watchConfig(configPath: string): void {
|
|
const watcher = chokidar.watch(configPath, {
|
|
persistent: true,
|
|
ignoreInitial: true
|
|
});
|
|
|
|
watcher.on('change', async () => {
|
|
try {
|
|
const newConfig = await this.loadAndValidate(configPath);
|
|
|
|
if (JSON.stringify(newConfig) !== JSON.stringify(this.currentConfig)) {
|
|
this.emit('config:changed', {
|
|
oldConfig: this.currentConfig,
|
|
newConfig
|
|
});
|
|
this.currentConfig = newConfig;
|
|
}
|
|
} catch (error) {
|
|
this.emit('config:error', { error });
|
|
}
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
### 6. Configuration Migration
|
|
|
|
```python
|
|
from typing import Dict
|
|
from abc import ABC, abstractmethod
|
|
import semver
|
|
|
|
class ConfigMigration(ABC):
|
|
@property
|
|
@abstractmethod
|
|
def version(self) -> str:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def up(self, config: Dict) -> Dict:
|
|
pass
|
|
|
|
@abstractmethod
|
|
def down(self, config: Dict) -> Dict:
|
|
pass
|
|
|
|
class ConfigMigrator:
|
|
def __init__(self):
|
|
self.migrations: List[ConfigMigration] = []
|
|
|
|
def migrate(self, config: Dict, target_version: str) -> Dict:
|
|
current_version = config.get('_version', '0.0.0')
|
|
|
|
if semver.compare(current_version, target_version) == 0:
|
|
return config
|
|
|
|
result = config.copy()
|
|
for migration in self.migrations:
|
|
if (semver.compare(migration.version, current_version) > 0 and
|
|
semver.compare(migration.version, target_version) <= 0):
|
|
result = migration.up(result)
|
|
result['_version'] = migration.version
|
|
|
|
return result
|
|
```
|
|
|
|
### 7. Secure Configuration
|
|
|
|
```typescript
|
|
import * as crypto from 'crypto';
|
|
|
|
interface EncryptedValue {
|
|
encrypted: true;
|
|
value: string;
|
|
algorithm: string;
|
|
iv: string;
|
|
authTag?: string;
|
|
}
|
|
|
|
export class SecureConfigManager {
|
|
private encryptionKey: Buffer;
|
|
|
|
constructor(masterKey: string) {
|
|
this.encryptionKey = crypto.pbkdf2Sync(masterKey, 'config-salt', 100000, 32, 'sha256');
|
|
}
|
|
|
|
encrypt(value: any): EncryptedValue {
|
|
const algorithm = 'aes-256-gcm';
|
|
const iv = crypto.randomBytes(16);
|
|
const cipher = crypto.createCipheriv(algorithm, this.encryptionKey, iv);
|
|
|
|
let encrypted = cipher.update(JSON.stringify(value), 'utf8', 'hex');
|
|
encrypted += cipher.final('hex');
|
|
|
|
return {
|
|
encrypted: true,
|
|
value: encrypted,
|
|
algorithm,
|
|
iv: iv.toString('hex'),
|
|
authTag: cipher.getAuthTag().toString('hex')
|
|
};
|
|
}
|
|
|
|
decrypt(encryptedValue: EncryptedValue): any {
|
|
const decipher = crypto.createDecipheriv(
|
|
encryptedValue.algorithm,
|
|
this.encryptionKey,
|
|
Buffer.from(encryptedValue.iv, 'hex')
|
|
);
|
|
|
|
if (encryptedValue.authTag) {
|
|
decipher.setAuthTag(Buffer.from(encryptedValue.authTag, 'hex'));
|
|
}
|
|
|
|
let decrypted = decipher.update(encryptedValue.value, 'hex', 'utf8');
|
|
decrypted += decipher.final('utf8');
|
|
|
|
return JSON.parse(decrypted);
|
|
}
|
|
|
|
async processConfig(config: any): Promise<any> {
|
|
const processed = {};
|
|
|
|
for (const [key, value] of Object.entries(config)) {
|
|
if (this.isEncryptedValue(value)) {
|
|
processed[key] = this.decrypt(value as EncryptedValue);
|
|
} else if (typeof value === 'object' && value !== null) {
|
|
processed[key] = await this.processConfig(value);
|
|
} else {
|
|
processed[key] = value;
|
|
}
|
|
}
|
|
|
|
return processed;
|
|
}
|
|
}
|
|
```
|
|
|
|
### 8. Documentation Generation
|
|
|
|
```python
|
|
from typing import Dict, List
|
|
import yaml
|
|
|
|
class ConfigDocGenerator:
|
|
def generate_docs(self, schema: Dict, examples: Dict) -> str:
|
|
docs = ["# Configuration Reference\n"]
|
|
|
|
docs.append("## Configuration Options\n")
|
|
sections = self._generate_sections(schema.get('properties', {}), examples)
|
|
docs.extend(sections)
|
|
|
|
return '\n'.join(docs)
|
|
|
|
def _generate_sections(self, properties: Dict, examples: Dict, level: int = 3) -> List[str]:
|
|
sections = []
|
|
|
|
for prop_name, prop_schema in properties.items():
|
|
sections.append(f"{'#' * level} {prop_name}\n")
|
|
|
|
if 'description' in prop_schema:
|
|
sections.append(f"{prop_schema['description']}\n")
|
|
|
|
sections.append(f"**Type:** `{prop_schema.get('type', 'any')}`\n")
|
|
|
|
if 'default' in prop_schema:
|
|
sections.append(f"**Default:** `{prop_schema['default']}`\n")
|
|
|
|
if prop_name in examples:
|
|
sections.append("**Example:**\n```yaml")
|
|
sections.append(yaml.dump({prop_name: examples[prop_name]}))
|
|
sections.append("```\n")
|
|
|
|
return sections
|
|
```
|
|
|
|
## Output Format
|
|
|
|
1. **Configuration Analysis**: Current configuration assessment
|
|
2. **Validation Schemas**: JSON Schema definitions
|
|
3. **Environment Rules**: Environment-specific validation
|
|
4. **Test Suite**: Configuration tests
|
|
5. **Migration Scripts**: Version migrations
|
|
6. **Security Report**: Issues and recommendations
|
|
7. **Documentation**: Auto-generated reference
|
|
|
|
Focus on preventing configuration errors, ensuring consistency, and maintaining security best practices.
|