Sicherheitsarchitektur¶
TCM365 implementiert ein Defense-in-Depth-Sicherheitsmodell mit mehreren unabhängigen Schichten. Dieses Dokument behandelt Authentifizierung, Autorisierung, Tenant-Isolation, Verschlüsselung und operationale Sicherheitskontrollen.
Sicherheitsschichten im Ueberblick¶
graph TB
subgraph NET["Netzwerkschicht"]
N1["Helmet (Security Headers)"]
N2["CORS-Konfiguration"]
N3["Rate Limiting"]
end
subgraph AUTH["Authentifizierungsschicht"]
A1["JWT Bearer Tokens"]
A2["Local Strategy (bcrypt)"]
A3["Azure AD Integration"]
A4["Token Blacklisting"]
end
subgraph AUTHZ["Autorisierungsschicht"]
Z1["Rollenhierarchie (4 Stufen)"]
Z2["Permission System (resource:action)"]
Z3["Route Guards"]
end
subgraph TENANT["Tenant-Isolationsschicht"]
T1["Schicht 1: TenantAccessService (IDOR-Prävention)"]
T2["Schicht 2: PostgreSQL Row-Level Security"]
T3["Schicht 3: Schema-per-Tenant (physische Trennung)"]
end
subgraph DATA["Datenschutzschicht"]
D1["Per-Tenant-Verschlüsselung (AES-256 / Azure Key Vault)"]
D2["Credential-Verschlüsselung im Ruhezustand"]
D3["UUID-Primärschlüssel (keine Enumeration)"]
end
subgraph AUDIT["Auditschicht"]
AU1["Unveränderliche Audit Logs"]
AU2["Request Correlation (X-Request-ID)"]
AU3["AuditLog Interceptor"]
AU4["Strukturiertes Logging"]
end
NET --> AUTH --> AUTHZ --> TENANT --> DATA --> AUDIT
Authentifizierung¶
JWT-Authentifizierung¶
TCM365 verwendet JWT (JSON Web Tokens) für die API-Authentifizierung. Tokens werden bei erfolgreicher Anmeldung ausgestellt und müssen im Authorization-Header nachfolgender Anfragen enthalten sein.
Token-Lebenszyklus:
| Schritt | Beschreibung |
|---|---|
| Login | POST /api/v1/auth/login mit E-Mail + Passwort |
| Token-Ausstellung | Server gibt signiertes JWT mit User ID, Rolle, Org ID zurück |
| Request Auth | Client sendet Authorization: Bearer <token> |
| Validierung | JwtStrategy extrahiert und validiert das Token |
| Refresh | POST /api/v1/auth/refresh vor Token-Ablauf |
| Logout | Token wird zur Blacklist hinzugefügt (Redis oder In-Memory) |
Token Payload:
{
"sub": "user-uuid",
"email": "user@example.com",
"role": "TENANT_ADMIN",
"orgId": "org-uuid",
"permissions": ["tenants:read", "tenants:write", "snapshots:read"],
"iat": 1705312200,
"exp": 1705314000
}
Token-Konfiguration:
| Einstellung | Standard | Umgebungsvariable |
|---|---|---|
| Signaturalgorithmus | HS256 | -- |
| Ablaufzeit | 30 Minuten | APP_ACCESS_TOKEN_EXPIRE_MINUTES |
| Secret Key | -- | APP_SECRET_KEY (min. 32 Zeichen) |
Lokale Authentifizierung (Passwort)¶
Für lokale Benutzerkonten (nicht Azure AD SSO) werden Passwörter mit bcrypt gehasht:
// Passwort-Hashing (bei Registrierung/Änderung)
const hash = await bcrypt.hash(plaintext, 12); // 12 Salt Rounds
// Passwort-Verifizierung (bei Login)
const isValid = await bcrypt.compare(plaintext, hash);
Passwortsicherheit
- Bcrypt mit 12 Salt Rounds (adaptiver Kostenfaktor)
- Passwörter werden niemals im Klartext gespeichert
- Passwortänderung invalidiert bestehende Sessions
Azure AD-Authentifizierung¶
TCM365 unterstützt Azure AD SSO über MSAL (Microsoft Authentication Library):
const msalConfig = {
auth: {
clientId: configService.get('AZURE_AD_CLIENT_ID'),
authority: configService.get('AZURE_AD_AUTHORITY'),
clientSecret: configService.get('AZURE_AD_CLIENT_SECRET'),
},
};
Autorisierung¶
Rollenhierarchie¶
TCM365 implementiert ein hierarchisches Rollensystem, bei dem höhere Rollen alle Berechtigungen niedrigerer Rollen erben:
| Rolle | Stufe | Erbt von | Beschreibung |
|---|---|---|---|
SUPER_ADMIN |
100 | Alle Rollen | Vollständiger Plattformzugriff, Benutzerverwaltung, Systemeinstellungen |
TENANT_ADMIN |
75 | WORKFLOW_MANAGER | Tenant-Konfiguration, Verbindungsverwaltung |
WORKFLOW_MANAGER |
50 | VIEWER | Workflow-Erstellung, Snapshot Capture, Rollback-Ausführung |
VIEWER |
25 | -- | Lesezugriff auf Dashboards, Snapshots, Reports |
Rollendurchsetzung in Controllern:
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(UserRole.TENANT_ADMIN) // Minimale Rolle: TENANT_ADMIN oder höher
@Post()
async createTenant(@Body() dto: CreateTenantDto) {
// Nur TENANT_ADMIN und SUPER_ADMIN können darauf zugreifen
}
Permission System¶
Über Rollen hinaus unterstützt TCM365 feingranulare Berechtigungen im resource:action-Format:
Ressourcen:
| Ressource | Beschreibung |
|---|---|
users |
Benutzerverwaltung |
tenants |
Tenant-Verbindungen |
workflows |
Workflow-Operationen |
snapshots |
Snapshot-Operationen |
audit |
Audit Log-Zugriff |
settings |
Systemeinstellungen |
Aktionen:
| Aktion | Beschreibung |
|---|---|
read |
Ressourcen anzeigen/auflisten |
write |
Ressourcen erstellen/aktualisieren |
delete |
Ressourcen entfernen |
connect |
Verbindungen herstellen (Tenants) |
execute |
Operationen ausführen (Workflows, Snapshots) |
rollback |
Rollback-Operationen ausführen |
Berechtigungsdurchsetzung:
@UseGuards(JwtAuthGuard, PermissionsGuard)
@Permissions('snapshots:execute')
@Post('capture')
async captureSnapshot(@Body() dto: CaptureSnapshotDto) {
// Nur Benutzer mit snapshots:execute-Berechtigung können zugreifen
}
Guards-Stack¶
Mehrere Guards können auf einem einzelnen Endpoint kombiniert werden:
@UseGuards(JwtAuthGuard, RolesGuard, PermissionsGuard)
@Roles(UserRole.WORKFLOW_MANAGER)
@Permissions('workflows:execute')
@Post(':id/execute')
async executeWorkflow(@Param('id') id: string) {
// Erfordert: gueltiges JWT + WORKFLOW_MANAGER-Rolle + workflows:execute-Berechtigung
}
Drei-Schichten Tenant-Isolation¶
TCM365 erzwingt Tenant-Datenisolation auf drei unabhängigen Schichten. Wenn eine einzelne Schicht kompromittiert wird, schützen die verbleibenden Schichten weiterhin die Tenant-Daten.
Schicht 1: TenantAccessService (Anwendungsschicht)¶
Der TenantAccessService verhindert Insecure Direct Object Reference (IDOR)-Angriffe, indem er validiert, dass der authentifizierte Benutzer Zugriff auf die Organisation der angeforderten Ressource hat.
@Injectable()
export class TenantAccessService {
async validateAccess(userId: string, resourceOrgId: string): Promise<void> {
const user = await this.userRepository.findOne({
where: { id: userId },
relations: ['organization'],
});
if (user.organization.id !== resourceOrgId) {
this.logger.warn('TENANT_ACCESS_DENIED', {
userId,
attemptedOrgId: resourceOrgId,
userOrgId: user.organization.id,
});
throw new ForbiddenException('Access denied to this resource');
}
}
}
Verwendung in Controllern:
@Get(':id')
async getSnapshot(@Param('id') id: string, @CurrentUser() user: User) {
const snapshot = await this.snapshotRepo.findOne({ where: { id } });
await this.tenantAccess.validateAccess(user.id, snapshot.orgId);
return snapshot;
}
IDOR-Prävention
Jeder Controller, der auf tenant-scoped Daten zugreift, muss TenantAccessService.validateAccess() aufrufen, bevor Daten zurueckgegeben werden. Dies verhindert, dass Benutzer auf Daten anderer Organisationen zugreifen, indem sie UUIDs in API-Anfragen manipulieren.
Schicht 2: Row-Level Security (Datenbankschicht)¶
PostgreSQL Row-Level Security filtert Abfrageergebnisse automatisch, sodass nur Zeilen der aktuellen Organisation enthalten sind.
-- RLS-Policy (angewendet auf 18 tenant-scoped Tabellen)
CREATE POLICY tenant_isolation ON snapshots
USING (org_id = current_setting('app.current_org_id')::uuid);
-- Anwendung setzt die Session-Variable pro Anfrage
SET app.current_org_id = 'org-uuid-hier';
Zwei-Rollen-Architektur:
| Rolle | Verhalten | Anwendungsfall |
|---|---|---|
tcm_admin |
BYPASSRLS |
Migrationen, Schema-Verwaltung, Admin-Skripte |
tcm_app |
Respektiert RLS | Alle Anwendungsabfragen |
Selbst wenn die Anwendungsschicht die Filterung nach Organisation nicht durchfuehrt, stellt RLS sicher, dass die Datenbank niemals Cross-Tenant-Daten zurueckgibt.
Schicht 3: Schema-per-Tenant (Physische Trennung)¶
Data-Plane-Tabellen werden physisch in Per-Tenant PostgreSQL-Schemas getrennt:
public Schema -- Control-Plane (users, groups, system_settings)
tenant_<org-uuid> -- Data-Plane pro Organisation (snapshots, drifts etc.)
Jedes Tenant-Schema enthält 23 Tabellen mit operativen Daten. Der TenantSchemaManager-Service leitet Abfragen basierend auf der Organisation des authentifizierten Benutzers an das korrekte Schema weiter.
Isolationsgarantie: Selbst bei fehlerhaft konfigurierten RLS-Policies werden Data-Plane-Abfragen gegen das korrekte Tenant-Schema ausgeführt, wodurch Cross-Tenant-Datenlecks für Data-Plane-Operationen architektonisch ausgeschlossen sind.
Per-Tenant-Verschlüsselung¶
Sensible Tenant-Credentials (Azure AD Secrets, Zscaler API Keys, Atlassian OAuth Tokens, Passwörter) werden im Ruhezustand über ein erweiterbares SecretBackend-Interface verschlüsselt.
SecretBackend Interface¶
interface SecretBackend {
encrypt(plaintext: string, tenantId: string): Promise<string>;
decrypt(ciphertext: string, tenantId: string): Promise<string>;
}
Implementierungen¶
| Backend | Umgebung | Verschluesselungsmethode |
|---|---|---|
| LocalAES | Entwicklung | AES-256-GCM mit per-tenant abgeleiteten Schluesseln |
| AzureKeyVault | Produktion | Azure Key Vault Managed Keys |
LocalAES Details:
- Algorithmus: AES-256-GCM
- Schluesselableitung: HKDF aus Master Key + Tenant ID (eindeutiger Schlüssel pro Tenant)
- IV: Zufaellige 12 Bytes pro Verschluesselungsvorgang
- Ausgabeformat:
iv:ciphertext:authTag(Base64-kodiert)
Verschluesselte Spalten in VendorTenant:
| Spalte | Entity | Inhalt |
|---|---|---|
zia_api_key |
ZscalerVendorTenant | Zscaler ZIA API Key |
zia_admin_password |
ZscalerVendorTenant | Zscaler ZIA Admin-Passwort |
zpa_client_secret |
ZscalerVendorTenant | Zscaler ZPA Client Secret |
write_credentials |
M365VendorTenant | M365 Schreiboperations-Credentials (JSONB) |
oauth_access_token |
AtlassianVendorTenant | Atlassian OAuth 2LO Access Token |
oauth_refresh_token |
AtlassianVendorTenant | Atlassian OAuth 2LO Refresh Token |
Capture Lock Service¶
Der Capture Lock Service verhindert gleichzeitige Snapshot Captures gegen denselben Tenant, was zu API Rate Limit-Verletzungen oder inkonsistenten Daten führen koennte.
@Injectable()
export class CaptureLockService {
// Exklusiven Lock für den Tenant erwerben
async acquire(tenantId: string, ttlMs: number): Promise<boolean>;
// Lock nach Abschluss des Captures freigeben
async release(tenantId: string): Promise<void>;
}
| Backend | Implementierung | Geeignet für |
|---|---|---|
| Redis | SET key NX EX ttl (atomar) |
Multi-Instance-Bereitstellungen |
| In-Memory | Map<string, boolean> mit TTL |
Single-Instance-Bereitstellungen |
Der Lock wird nach Ablauf der TTL automatisch freigegeben (auch wenn der Capture-Prozess abstuerzt), wodurch permanente Lock-Zustaende verhindert werden.
HTTP-Sicherheit¶
Helmet¶
Helmet setzt sichere HTTP-Header auf alle Antworten:
| Header | Wert | Zweck |
|---|---|---|
X-Content-Type-Options |
nosniff |
MIME-Type-Sniffing verhindern |
X-Frame-Options |
SAMEORIGIN |
Clickjacking verhindern |
X-XSS-Protection |
1; mode=block |
XSS-Filter |
Strict-Transport-Security |
max-age=31536000 |
HTTPS erzwingen |
Content-Security-Policy |
Pro Deployment konfiguriert | Ressourcenladen beschränken |
CORS¶
Cross-Origin Resource Sharing wird über die CORS_ORIGINS-Umgebungsvariable konfiguriert:
app.enableCors({
origin: configService.get('CORS_ORIGINS').split(','),
credentials: true,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
});
Rate Limiting¶
Rate Limiting schuetzt gegen Brute-Force-Angriffe und API-Missbrauch:
| Bereich | Limit | Fenster | Backend |
|---|---|---|---|
| Global | 100 Anfragen | 1 Minute | Redis / In-Memory |
| Auth (Login) | 5 Versuche | 5 Minuten | Redis / In-Memory |
| Snapshot Capture | 1 pro Tenant | Lock TTL | Capture Lock Service |
Audit Trail¶
AuditLog Interceptor¶
Der AuditLogInterceptor erfasst automatisch alle zustandsaendernden Operationen:
@Injectable()
export class AuditLogInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(request.method)) {
return next.handle().pipe(
tap(() => {
this.auditLogService.create({
userId: request.user?.id,
action: `${request.method} ${request.path}`,
resourceType: this.extractResourceType(request.path),
resourceId: request.params?.id,
requestId: request.headers['x-request-id'],
metadata: { body: this.sanitize(request.body) },
});
}),
);
}
return next.handle();
}
}
Audit Log-Eintrag¶
| Feld | Beschreibung |
|---|---|
id |
Eindeutige Audit-Eintrags-ID (UUID) |
user_id |
Wer die Aktion durchgeführt hat |
action |
Welche Aktion durchgeführt wurde |
resource_type |
Typ der betroffenen Ressource |
resource_id |
ID der betroffenen Ressource |
request_id |
Korrelations-ID für Request Tracing |
metadata |
Zusaetzlicher Kontext (bereinigter Request Body) |
created_at |
Wann die Aktion stattfand |
Unveränderliche Audit Logs
Audit Log-Einträge sind append-only. Es gibt keine Update- oder Delete-Funktionalität für Audit-Einträge, was einen manipulationssicheren Audit Trail gewaehrleistet.
Request Correlation¶
Jede Anfrage erhält eine eindeutige X-Request-ID (automatisch generiert oder vom Client übergeben). Diese ID wird:
- In allen Log-Einträgen der Anfrage aufgenommen
- In den Response-Headern zurueckgegeben
- In Audit Log-Einträgen gespeichert
- Zur Korrelation von Aktionen über Services hinweg verwendet
Sicherheits-Best-Practices für Entwickler¶
Empfohlen¶
- Immer
TenantAccessService.validateAccess()in Controllern verwenden, die tenant-scoped Daten behandeln - Konfiguration über
ConfigServicezugreifen, niemalsprocess.env class-validator-Dekoratoren auf allen DTOs für Eingabevalidierung verwenden@UseGuards(JwtAuthGuard)auf allen nicht-öffentlichen Endpoints anwenden- UUID-Parameter (niemals sequentielle Integers) in URLs verwenden
- Sicherheitsereignisse mit strukturiertem Logging inkl. Request IDs protokollieren
- Alle Tenant-Credentials vor Speicherung verschluesseln
Vermeiden¶
- Niemals RLS umgehen, indem die
tcm_admin-Rolle im Anwendungscode verwendet wird - Niemals Passwörter im Klartext speichern oder reversible Verschlüsselung für Passwörter verwenden
- Niemals interne Fehlerdetails in API-Antworten exponieren (Produktion)
- Niemals client-bereitgestellten Organisations-IDs ohne Validierung vertrauen
- Niemals Helmet oder CORS in der Produktion deaktivieren
- Niemals Secrets (
.env, API Keys, Zertifikate) in die Versionskontrolle committen - Niemals
synchronize: truein der TypeORM-Konfiguration verwenden
Sicherheitstests¶
TCM365 enthält 70 Cross-Tenant-Isolationstests, die verifizieren:
- Benutzer können nicht auf Snapshots anderer Organisationen zugreifen
- Benutzer können nicht auf Tenants anderer Organisationen zugreifen
- Benutzer können nicht auf Drifts anderer Organisationen zugreifen
- Benutzer können nicht auf Workflows anderer Organisationen zugreifen
- RLS filtert Abfragen korrekt nach Organisation
TenantAccessServiceblockiert IDOR-Versuche- Token Blacklisting verhindert die Nutzung widerrufener Tokens