# Credentials & Security Schema **MSP Mode Database Schema - Security Tables** **Status:** Designed 2026-01-15 **Database:** msp_tracking (MariaDB on Jupiter) --- ## Overview The Credentials & Security subsystem provides encrypted credential storage, comprehensive audit logging, security incident tracking, and granular access control for MSP work. All sensitive data is encrypted at rest using AES-256-GCM. **Related Documentation:** - [MSP-MODE-SPEC.md](../MSP-MODE-SPEC.md) - Full system specification - [ARCHITECTURE_OVERVIEW.md](ARCHITECTURE_OVERVIEW.md) - System architecture - [API_SPEC.md](API_SPEC.md) - API endpoints for credential access - [SCHEMA_CONTEXT.md](SCHEMA_CONTEXT.md) - Learning and context tables --- ## Tables Summary | Table | Purpose | Encryption | |-------|---------|------------| | `credentials` | Encrypted credential storage | AES-256-GCM | | `credential_audit_log` | Comprehensive access audit trail | No (metadata only) | | `security_incidents` | Security event tracking | No | | `credential_permissions` | Granular access control (future multi-user) | No | **Total:** 4 tables --- ## Table Schemas ### `credentials` Encrypted credential storage for client infrastructure, services, and integrations. All sensitive fields encrypted at rest with AES-256-GCM. ```sql CREATE TABLE credentials ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), client_id UUID REFERENCES clients(id) ON DELETE CASCADE, service_id UUID REFERENCES services(id) ON DELETE CASCADE, infrastructure_id UUID REFERENCES infrastructure(id) ON DELETE CASCADE, -- Credential type and metadata credential_type VARCHAR(50) NOT NULL CHECK(credential_type IN ( 'password', 'api_key', 'oauth', 'ssh_key', 'shared_secret', 'jwt', 'connection_string', 'certificate' )), service_name VARCHAR(255) NOT NULL, -- "Gitea Admin", "AD2 sysadmin" username VARCHAR(255), -- Encrypted sensitive data (AES-256-GCM) password_encrypted BYTEA, api_key_encrypted BYTEA, client_secret_encrypted BYTEA, token_encrypted BYTEA, connection_string_encrypted BYTEA, -- OAuth-specific fields client_id_oauth VARCHAR(255), tenant_id_oauth VARCHAR(255), -- SSH key storage public_key TEXT, -- Service-specific integration_code VARCHAR(255), -- for services like Autotask -- Access metadata external_url VARCHAR(500), internal_url VARCHAR(500), custom_port INTEGER, role_description VARCHAR(500), requires_vpn BOOLEAN DEFAULT false, requires_2fa BOOLEAN DEFAULT false, ssh_key_auth_enabled BOOLEAN DEFAULT false, access_level VARCHAR(100), -- Lifecycle management expires_at TIMESTAMP, last_rotated_at TIMESTAMP, is_active BOOLEAN DEFAULT true, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_credentials_client (client_id), INDEX idx_credentials_service (service_id), INDEX idx_credentials_type (credential_type), INDEX idx_credentials_active (is_active) ); ``` **Security Features:** - All sensitive fields encrypted with AES-256-GCM - Encryption key stored separately (environment variable or vault) - Master password unlock mechanism - Automatic expiration tracking - Rotation reminders - VPN requirement flags **Example Records:** **Password Credential (AD2 sysadmin):** ```json { "service_name": "AD2\\sysadmin", "credential_type": "password", "username": "sysadmin", "password_encrypted": "", "internal_url": "192.168.0.6", "requires_vpn": true, "access_level": "Domain Admin", "infrastructure_id": "ad2-server-uuid", "client_id": "dataforth-uuid" } ``` **API Key (SyncroMSP):** ```json { "service_name": "SyncroMSP API", "credential_type": "api_key", "api_key_encrypted": "", "external_url": "https://azcomputerguru.syncromsp.com/api/v1", "integration_code": "syncro_psa", "expires_at": "2027-01-15T00:00:00Z" } ``` **OAuth Credential (Microsoft 365):** ```json { "service_name": "Dataforth M365 Admin", "credential_type": "oauth", "client_id_oauth": "app-client-id", "client_secret_encrypted": "", "tenant_id_oauth": "tenant-uuid", "token_encrypted": "", "requires_2fa": true, "client_id": "dataforth-uuid" } ``` **SSH Key (D2TESTNAS root):** ```json { "service_name": "D2TESTNAS root", "credential_type": "ssh_key", "username": "root", "public_key": "ssh-rsa AAAAB3Nza...", "internal_url": "192.168.0.9", "requires_vpn": true, "ssh_key_auth_enabled": true, "infrastructure_id": "d2testnas-uuid" } ``` --- ### `credential_audit_log` Comprehensive audit trail for all credential access operations. Tracks who accessed what credential, when, from where, and why. ```sql CREATE TABLE credential_audit_log ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), credential_id UUID NOT NULL REFERENCES credentials(id) ON DELETE CASCADE, -- Action tracking action VARCHAR(50) NOT NULL CHECK(action IN ( 'view', 'create', 'update', 'delete', 'rotate', 'decrypt' )), -- User context user_id VARCHAR(255) NOT NULL, -- JWT sub claim ip_address VARCHAR(45), user_agent TEXT, -- Session context session_id UUID, -- if accessed during MSP session work_item_id UUID, -- if accessed for specific work item -- Audit details details TEXT, -- JSON: what changed, why accessed, etc. timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_cred_audit_credential (credential_id), INDEX idx_cred_audit_user (user_id), INDEX idx_cred_audit_timestamp (timestamp), INDEX idx_cred_audit_action (action) ); ``` **Logged Actions:** - **view** - Credential viewed in UI/API - **create** - New credential stored - **update** - Credential modified - **delete** - Credential removed - **rotate** - Password/key rotated - **decrypt** - Credential decrypted for use **Example Audit Entries:** **Credential Access During Session:** ```json { "credential_id": "ad2-sysadmin-uuid", "action": "decrypt", "user_id": "mike@azcomputerguru.com", "ip_address": "172.16.3.101", "session_id": "current-session-uuid", "work_item_id": "fix-user-account-uuid", "details": { "reason": "Access AD2 to reset user account", "service_name": "AD2\\sysadmin" }, "timestamp": "2026-01-15T14:32:10Z" } ``` **Credential Rotation:** ```json { "credential_id": "nas-root-uuid", "action": "rotate", "user_id": "mike@azcomputerguru.com", "details": { "reason": "Scheduled 90-day rotation", "old_password_hash": "sha256:abc123...", "new_password_hash": "sha256:def456..." }, "timestamp": "2026-01-15T09:00:00Z" } ``` **Failed Access Attempt:** ```json { "credential_id": "client-api-uuid", "action": "view", "user_id": "unknown@external.com", "ip_address": "203.0.113.45", "details": { "error": "Unauthorized - invalid JWT token", "blocked": true }, "timestamp": "2026-01-15T03:22:05Z" } ``` **Audit Queries:** ```sql -- Who accessed this credential in last 30 days? SELECT user_id, action, timestamp, details FROM credential_audit_log WHERE credential_id = 'target-uuid' AND timestamp >= NOW() - INTERVAL 30 DAY ORDER BY timestamp DESC; -- All credential access by user SELECT c.service_name, cal.action, cal.timestamp FROM credential_audit_log cal JOIN credentials c ON cal.credential_id = c.id WHERE cal.user_id = 'mike@azcomputerguru.com' ORDER BY cal.timestamp DESC LIMIT 50; -- Recent decryption events (actual credential usage) SELECT c.service_name, cal.user_id, cal.timestamp, cal.session_id FROM credential_audit_log cal JOIN credentials c ON cal.credential_id = c.id WHERE cal.action = 'decrypt' AND cal.timestamp >= NOW() - INTERVAL 7 DAY ORDER BY cal.timestamp DESC; ``` --- ### `security_incidents` Security event and incident tracking for MSP clients. Documents incidents, investigations, remediation, and resolution. ```sql CREATE TABLE security_incidents ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), client_id UUID REFERENCES clients(id) ON DELETE CASCADE, service_id UUID REFERENCES services(id) ON DELETE SET NULL, infrastructure_id UUID REFERENCES infrastructure(id) ON DELETE SET NULL, -- Incident classification incident_type VARCHAR(100) CHECK(incident_type IN ( 'bec', 'backdoor', 'malware', 'unauthorized_access', 'data_breach', 'phishing', 'ransomware', 'brute_force', 'credential_compromise', 'ddos', 'injection_attack' )), incident_date TIMESTAMP NOT NULL, severity VARCHAR(50) CHECK(severity IN ('critical', 'high', 'medium', 'low')), -- Incident details description TEXT NOT NULL, affected_users TEXT, -- JSON array of affected users affected_systems TEXT, -- JSON array of affected systems -- Investigation findings TEXT, -- investigation results root_cause TEXT, indicators_of_compromise TEXT, -- JSON array: IPs, file hashes, domains -- Remediation remediation_steps TEXT, remediation_verified BOOLEAN DEFAULT false, -- Status tracking status VARCHAR(50) DEFAULT 'investigating' CHECK(status IN ( 'investigating', 'contained', 'resolved', 'monitoring' )), detected_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, contained_at TIMESTAMP, resolved_at TIMESTAMP, -- Follow-up lessons_learned TEXT, prevention_measures TEXT, -- what was implemented to prevent recurrence external_reporting_required BOOLEAN DEFAULT false, -- regulatory/client reporting external_report_details TEXT, notes TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_incidents_client (client_id), INDEX idx_incidents_type (incident_type), INDEX idx_incidents_severity (severity), INDEX idx_incidents_status (status), INDEX idx_incidents_date (incident_date) ); ``` **Real-World Examples from Session Logs:** **BEC (Business Email Compromise) - BG Builders:** ```json { "incident_type": "bec", "client_id": "bg-builders-uuid", "incident_date": "2025-12-XX", "severity": "critical", "description": "OAuth backdoor application discovered in M365 tenant allowing unauthorized email access", "affected_users": ["admin@bgbuilders.com", "accounting@bgbuilders.com"], "findings": "Malicious OAuth app registered with Mail.ReadWrite permissions. App created via phishing attack.", "root_cause": "User clicked phishing link and authorized malicious OAuth application", "remediation_steps": "1. Revoked OAuth app consent\n2. Forced password reset for affected users\n3. Enabled MFA for all users\n4. Reviewed audit logs for data exfiltration\n5. Configured conditional access policies", "remediation_verified": true, "status": "resolved", "prevention_measures": "Implemented OAuth app approval workflow, security awareness training, conditional access policies", "external_reporting_required": true, "external_report_details": "Notified client management, documented for cyber insurance" } ``` **BEC - CW Concrete:** ```json { "incident_type": "bec", "client_id": "cw-concrete-uuid", "incident_date": "2025-11-XX", "severity": "high", "description": "Business email compromise detected - unauthorized access to executive mailbox", "affected_users": ["ceo@cwconcrete.com"], "findings": "Attacker used compromised credentials to access mailbox and send fraudulent wire transfer requests", "root_cause": "Credential phishing via fake Office 365 login page", "remediation_steps": "1. Reset compromised credentials\n2. Enabled MFA\n3. Blocked sender domains\n4. Reviewed sent items for fraudulent emails\n5. Notified financial institutions", "status": "resolved", "lessons_learned": "MFA should be mandatory for all executive accounts. Email authentication (DMARC/DKIM/SPF) critical." } ``` **Malware - General Pattern:** ```json { "incident_type": "malware", "severity": "high", "description": "Ransomware infection detected on workstation", "affected_systems": ["WS-ACCT-01"], "findings": "CryptoLocker variant. Files encrypted with .encrypted extension. Ransom note left in directories.", "root_cause": "User opened malicious email attachment", "remediation_steps": "1. Isolated infected system\n2. Verified backups available\n3. Wiped and restored from backup\n4. Updated endpoint protection\n5. Implemented email attachment filtering", "status": "resolved", "prevention_measures": "Enhanced email filtering, user training, backup verification schedule" } ``` **Queries:** ```sql -- Critical unresolved incidents SELECT client_id, incident_type, description, incident_date FROM security_incidents WHERE severity = 'critical' AND status != 'resolved' ORDER BY incident_date DESC; -- Incident history for client SELECT incident_type, severity, incident_date, status FROM security_incidents WHERE client_id = 'target-client-uuid' ORDER BY incident_date DESC; -- BEC incidents requiring reporting SELECT client_id, description, incident_date, external_report_details FROM security_incidents WHERE incident_type = 'bec' AND external_reporting_required = true; ``` --- ### `credential_permissions` Granular access control for credentials. Supports future multi-user MSP team expansion by defining who can access which credentials. ```sql CREATE TABLE credential_permissions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), credential_id UUID NOT NULL REFERENCES credentials(id) ON DELETE CASCADE, user_id VARCHAR(255) NOT NULL, -- or role_id for role-based access -- Permission levels permission_level VARCHAR(50) CHECK(permission_level IN ('read', 'write', 'admin')), -- Constraints requires_2fa BOOLEAN DEFAULT false, -- force 2FA for this credential ip_whitelist TEXT, -- JSON array of allowed IPs time_restrictions TEXT, -- JSON: business hours only, etc. -- Audit granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, granted_by VARCHAR(255), expires_at TIMESTAMP, -- temporary access UNIQUE(credential_id, user_id), INDEX idx_cred_perm_credential (credential_id), INDEX idx_cred_perm_user (user_id) ); ``` **Permission Levels:** - **read** - Can view/decrypt credential - **write** - Can update credential - **admin** - Can grant/revoke permissions, delete credential **Example Permissions:** **Standard Technician Access:** ```json { "credential_id": "client-rdp-uuid", "user_id": "tech1@azcomputerguru.com", "permission_level": "read", "requires_2fa": false, "granted_by": "mike@azcomputerguru.com" } ``` **Sensitive Credential (Admin Only):** ```json { "credential_id": "domain-admin-uuid", "user_id": "mike@azcomputerguru.com", "permission_level": "admin", "requires_2fa": true, "ip_whitelist": ["172.16.3.0/24", "192.168.1.0/24"], "granted_by": "system" } ``` **Temporary Access (Contractor):** ```json { "credential_id": "temp-vpn-uuid", "user_id": "contractor@external.com", "permission_level": "read", "requires_2fa": true, "expires_at": "2026-02-01T00:00:00Z", "granted_by": "mike@azcomputerguru.com" } ``` **Time-Restricted Access:** ```json { "credential_id": "backup-system-uuid", "user_id": "nightshift@azcomputerguru.com", "permission_level": "read", "time_restrictions": { "allowed_hours": "18:00-06:00", "timezone": "America/Phoenix", "days": ["mon", "tue", "wed", "thu", "fri"] } } ``` --- ## Credential Workflows ### Credential Storage Workflow (Agent-Based) **When new credential discovered during MSP session:** 1. **User mentions credential:** - "SSH to AD2 as sysadmin" → Claude detects credential reference 2. **Check if credential exists:** - Query: `GET /api/v1/credentials?service=AD2&username=sysadmin` 3. **If not found, prompt user:** - "Store credential for AD2\\sysadmin? (y/n)" 4. **Launch Credential Storage Agent:** - Receives: credential data, client context, service info - Encrypts credential with AES-256-GCM - Links to client_id, service_id, infrastructure_id - Stores via API: `POST /api/v1/credentials` - Creates audit log entry (action: 'create') - Returns: credential_id 5. **Main Claude confirms:** - "Stored AD2\\sysadmin credential (ID: abc123)" ### Credential Retrieval Workflow (Agent-Based) **When credential needed for work:** 1. **Launch Credential Retrieval Agent:** - Task: "Retrieve credential for AD2\\sysadmin" 2. **Agent performs:** - Query API: `GET /api/v1/credentials?service=AD2&username=sysadmin` - Decrypt credential (API handles this with master key) - Log access to credential_audit_log: - action: 'decrypt' - user_id: from JWT - session_id: current MSP session - work_item_id: current work context - Return only credential value 3. **Agent returns:** - "Paper123!@#" (actual credential) 4. **Main Claude uses credential:** - Displays in context: "Using AD2\\sysadmin password from vault" - Never logs actual password value in session logs 5. **Audit trail created automatically** ### Credential Rotation Workflow **Scheduled or on-demand rotation:** 1. **Identify credentials needing rotation:** ```sql SELECT * FROM credentials WHERE expires_at <= NOW() + INTERVAL 7 DAY OR last_rotated_at <= NOW() - INTERVAL 90 DAY; ``` 2. **For each credential:** - Generate new password/key - Update service/infrastructure with new credential - Encrypt new credential - Update credentials table - Set last_rotated_at = NOW() - Log rotation in credential_audit_log 3. **Verify new credential works:** - Test authentication - Update verification status 4. **Notify user:** - "Rotated 3 credentials: AD2\\sysadmin, NAS root, Gitea admin" --- ## Security Considerations ### Encryption at Rest **AES-256-GCM Encryption:** - All `*_encrypted` fields use AES-256-GCM - Provides both confidentiality and authenticity - Per-credential random IV (initialization vector) - Master key stored separately from database **Master Key Management:** ```python # Example key storage (production) # Option 1: Environment variable (Docker secret) MASTER_KEY = os.environ['MSP_CREDENTIAL_MASTER_KEY'] # Option 2: HashiCorp Vault # vault = hvac.Client(url='https://vault.internal') # MASTER_KEY = vault.secrets.kv.v2.read_secret_version(path='msp/credential-key') # Option 3: AWS KMS / Azure Key Vault # MASTER_KEY = kms_client.decrypt(encrypted_key_blob) ``` **Encryption Process:** ```python from cryptography.hazmat.primitives.ciphers.aead import AESGCM import os def encrypt_credential(plaintext: str, master_key: bytes) -> bytes: """Encrypt credential with AES-256-GCM""" aesgcm = AESGCM(master_key) # 32-byte key nonce = os.urandom(12) # 96-bit random nonce ciphertext = aesgcm.encrypt(nonce, plaintext.encode(), None) return nonce + ciphertext # prepend nonce to ciphertext def decrypt_credential(encrypted: bytes, master_key: bytes) -> str: """Decrypt credential""" aesgcm = AESGCM(master_key) nonce = encrypted[:12] ciphertext = encrypted[12:] plaintext = aesgcm.decrypt(nonce, ciphertext, None) return plaintext.decode() ``` ### Access Control **JWT-Based Authentication:** - All API requests require valid JWT token - Token includes user_id (sub claim) - Token expires after 1 hour (refresh pattern) **Permission Checks:** ```python # Before decrypting credential def check_credential_access(credential_id: str, user_id: str) -> bool: # Check credential_permissions table perm = db.query(CredentialPermission).filter( CredentialPermission.credential_id == credential_id, CredentialPermission.user_id == user_id ).first() if not perm: # No explicit permission - deny by default return False if perm.expires_at and perm.expires_at < datetime.now(): # Permission expired return False if perm.requires_2fa: # Check if user has valid 2FA session if not check_2fa_session(user_id): return False return True ``` **Audit Logging:** - Every credential access logged automatically - Failed access attempts logged with details - Queryable for security investigations - Retention: 7 years (compliance) ### Key Rotation Strategy **Master Key Rotation (Annual or on-demand):** 1. Generate new master key 2. Re-encrypt all credentials with new key 3. Update key in secure storage 4. Audit log: key rotation event 5. Verify all credentials decrypt successfully 6. Archive old key (encrypted, for disaster recovery) **Credential Rotation (Per-credential schedule):** - **Critical credentials:** 90 days - **Standard credentials:** 180 days - **Service accounts:** 365 days - **API keys:** 365 days or vendor recommendation ### Compliance Considerations **Data Retention:** - Credentials: Retained while active - Audit logs: 7 years minimum - Security incidents: Permanent (unless client requests deletion) **Access Logging:** - Who accessed what credential - When and from where (IP) - Why (session/work item context) - Result (success/failure) **Encryption Standards:** - AES-256-GCM (FIPS 140-2 compliant) - TLS 1.3 for API transit encryption - Key length: 256 bits minimum --- ## Integration with Other Schemas **Links to:** - `clients` - Credentials belong to clients - `infrastructure` - Credentials access infrastructure - `services` - Credentials authenticate to services - `sessions` - Credential access logged per session - `work_items` - Credentials used for specific work **Used by:** - MSP Mode sessions (credential retrieval) - Security incident investigations (affected credentials) - Audit queries (compliance reporting) - Integration workflows (external system authentication) --- ## Example Queries ### Find all credentials for a client ```sql SELECT c.service_name, c.username, c.credential_type, c.requires_vpn FROM credentials c WHERE c.client_id = 'dataforth-uuid' AND c.is_active = true ORDER BY c.service_name; ``` ### Check credential expiration ```sql SELECT c.service_name, c.expires_at, c.last_rotated_at FROM credentials c WHERE c.expires_at <= NOW() + INTERVAL 30 DAY OR c.last_rotated_at <= NOW() - INTERVAL 90 DAY ORDER BY c.expires_at ASC; ``` ### Audit: Who accessed credential? ```sql SELECT cal.user_id, cal.action, cal.timestamp, cal.ip_address FROM credential_audit_log cal WHERE cal.credential_id = 'target-credential-uuid' ORDER BY cal.timestamp DESC LIMIT 20; ``` ### Find credentials accessed in session ```sql SELECT c.service_name, cal.action, cal.timestamp FROM credential_audit_log cal JOIN credentials c ON cal.credential_id = c.id WHERE cal.session_id = 'session-uuid' ORDER BY cal.timestamp; ``` ### Security incidents requiring follow-up ```sql SELECT si.client_id, si.incident_type, si.description, si.status FROM security_incidents si WHERE si.status IN ('investigating', 'contained') AND si.severity IN ('critical', 'high') ORDER BY si.incident_date DESC; ``` --- ## Future Enhancements **Planned:** 1. Hardware security module (HSM) integration 2. Multi-factor authentication for high-privilege credentials 3. Automatic credential rotation scheduling 4. Integration with password managers (1Password, Bitwarden) 5. Credential strength analysis and weak password detection 6. Breach detection integration (Have I Been Pwned API) 7. Role-based access control (RBAC) for team expansion 8. Credential sharing workflows with approval process **Under Consideration:** - Biometric authentication for critical credentials - Time-based one-time password (TOTP) storage - Certificate management and renewal automation - Secrets scanning in code repositories - Automated credential discovery (scan infrastructure) --- **Document Version:** 1.0 **Last Updated:** 2026-01-15 **Author:** MSP Mode Schema Design Team