Files
govoplan-encryption/src/govoplan_encryption/backend/manifest.py
T
zemion df64af4d10
Module Package Release / publish-packages (push) Successful in 11s
docs(encryption): complete German reference coverage
2026-08-23 19:31:23 +02:00

594 lines
25 KiB
Python

from __future__ import annotations
from pathlib import Path
from govoplan_core.core.encryption import (
CAPABILITY_ENCRYPTION_CONTENT_CIPHER,
CAPABILITY_ENCRYPTION_CONTENT_CIPHER_PROVIDER_PREFIX,
CAPABILITY_ENCRYPTION_CONTENT_PROTECTION,
CAPABILITY_ENCRYPTION_DISABLE_PREFLIGHT,
CAPABILITY_ENCRYPTION_KEY_MATERIAL_PROVIDER_PREFIX,
CAPABILITY_ENCRYPTION_KEY_VAULT,
CAPABILITY_ENCRYPTION_RECOVERY,
)
from govoplan_core.core.module_guards import (
drop_table_retirement_provider,
persistent_table_uninstall_guard,
)
from govoplan_core.core.modules import (
CapabilityDocumentation,
DocumentationCondition,
DocumentationLink,
DocumentationTopic,
FrontendModule,
MigrationSpec,
ModuleContext,
ModuleInterfaceProvider,
ModuleManifest,
ModuleUninstallGuardResult,
PermissionDefinition,
RoleTemplate,
ViewSurface,
)
from govoplan_core.core.provider_governance import declared_module_architecture
from govoplan_core.db.base import Base
from govoplan_encryption.backend.db import models
from govoplan_encryption.backend.dsar_provider import (
ENCRYPTION_DSAR_CAPABILITY,
EncryptionDsarProvider,
)
from govoplan_encryption.backend.local_provider import (
LOCAL_PROVIDER_ID,
LocalAesGcmProvider,
)
from govoplan_encryption.backend.service import SqlEncryptionService
MODULE_ID = "encryption"
MODULE_NAME = "Encryption"
MODULE_VERSION = "0.1.19"
USE_SCOPE = "encryption:vault:use"
ADMIN_SCOPE = "encryption:vault:admin"
RECOVERY_SCOPE = "encryption:recovery:approve"
OPTIONAL_DEPENDENCIES = (
"access",
"audit",
"policy",
"notifications",
"files",
"postbox",
"campaigns",
"workflow_engine",
"identity_trust",
)
def _permission(scope: str, label: str, description: str) -> PermissionDefinition:
module_id, resource, action = scope.split(":", 2)
return PermissionDefinition(
scope=scope,
label=label,
description=description,
category="Encryption",
level="tenant",
module_id=module_id,
resource=resource,
action=action,
)
def _router(context: ModuleContext):
from govoplan_encryption.backend.router import create_router
return create_router(context.registry)
def _service(context: ModuleContext) -> SqlEncryptionService:
return SqlEncryptionService(context.registry)
def _local_provider(context: ModuleContext) -> LocalAesGcmProvider:
return LocalAesGcmProvider(getattr(context.settings, "master_key_b64", None))
def _dsar_provider(_context: ModuleContext) -> EncryptionDsarProvider:
return EncryptionDsarProvider()
def _disable_guard(
session: object | None,
_module_id: str,
) -> tuple[ModuleUninstallGuardResult, ...]:
if session is None:
return (
ModuleUninstallGuardResult(
"blocker",
"encryption_disable_unverified",
"Encryption cannot be disabled without proving the state of every protection envelope.",
),
)
try:
report = SqlEncryptionService().assess_disable(session)
except Exception as exc:
return (
ModuleUninstallGuardResult(
"blocker",
"encryption_disable_check_failed",
f"Encryption disable preflight failed: {type(exc).__name__}.",
),
)
if report.allowed:
return ()
return (
ModuleUninstallGuardResult(
"blocker",
"encryption_protected_content_present",
f"Encryption still protects {report.unresolved_count} unresolved envelope(s). Migrate, decrypt, explicitly export, or cryptographically destroy them before disabling the module.",
),
)
PERMISSIONS = (
_permission(
USE_SCOPE,
"Use encryption profiles",
"Register and resolve protected content through configured profiles.",
),
_permission(
ADMIN_SCOPE,
"Administer encryption",
"Manage vault metadata, provider operations, rotation, migration, and policy provenance.",
),
_permission(
RECOVERY_SCOPE,
"Approve key recovery",
"Participate in a high-assurance recovery ceremony without gaining content ownership.",
),
)
ROLE_TEMPLATES = (
RoleTemplate(
slug="encryption_user",
name="Encryption user",
description="Use configured content-protection profiles.",
permissions=(USE_SCOPE,),
),
RoleTemplate(
slug="encryption_custodian",
name="Encryption custodian",
description="Administer encryption and participate in key recovery.",
permissions=(USE_SCOPE, ADMIN_SCOPE, RECOVERY_SCOPE),
),
)
manifest = ModuleManifest(
id=MODULE_ID,
name=MODULE_NAME,
version=MODULE_VERSION,
optional_dependencies=OPTIONAL_DEPENDENCIES,
provides_interfaces=(
ModuleInterfaceProvider(
name=CAPABILITY_ENCRYPTION_KEY_VAULT,
version="1.0.0",
),
ModuleInterfaceProvider(
name=CAPABILITY_ENCRYPTION_CONTENT_PROTECTION,
version="1.0.0",
),
ModuleInterfaceProvider(
name=CAPABILITY_ENCRYPTION_CONTENT_CIPHER,
version="1.0.0",
),
ModuleInterfaceProvider(
name=f"{CAPABILITY_ENCRYPTION_KEY_MATERIAL_PROVIDER_PREFIX}{LOCAL_PROVIDER_ID}",
version="1.0.0",
),
ModuleInterfaceProvider(
name=f"{CAPABILITY_ENCRYPTION_CONTENT_CIPHER_PROVIDER_PREFIX}{LOCAL_PROVIDER_ID}",
version="1.0.0",
),
ModuleInterfaceProvider(
name=CAPABILITY_ENCRYPTION_RECOVERY,
version="1.0.0",
),
ModuleInterfaceProvider(
name=CAPABILITY_ENCRYPTION_DISABLE_PREFLIGHT,
version="1.0.0",
),
ModuleInterfaceProvider(name=ENCRYPTION_DSAR_CAPABILITY, version="0.1.0"),
),
permissions=PERMISSIONS,
role_templates=ROLE_TEMPLATES,
route_factory=_router,
frontend=FrontendModule(
module_id=MODULE_ID,
package_name="@govoplan/encryption-webui",
view_surfaces=(
ViewSurface(
id="encryption.admin.operations",
module_id=MODULE_ID,
kind="section",
label="Encryption administration",
order=10,
),
ViewSurface(
id="encryption.admin.vaults",
module_id=MODULE_ID,
kind="section",
label="Key vaults",
parent_id="encryption.admin.operations",
order=20,
),
ViewSurface(
id="encryption.admin.migrations",
module_id=MODULE_ID,
kind="section",
label="Protection migrations",
parent_id="encryption.admin.operations",
order=30,
),
ViewSurface(
id="encryption.admin.recovery",
module_id=MODULE_ID,
kind="section",
label="Recovery ceremonies",
parent_id="encryption.admin.operations",
order=40,
),
),
),
capability_factories={
CAPABILITY_ENCRYPTION_KEY_VAULT: _service,
CAPABILITY_ENCRYPTION_CONTENT_PROTECTION: _service,
CAPABILITY_ENCRYPTION_CONTENT_CIPHER: _service,
CAPABILITY_ENCRYPTION_RECOVERY: _service,
CAPABILITY_ENCRYPTION_DISABLE_PREFLIGHT: _service,
ENCRYPTION_DSAR_CAPABILITY: _dsar_provider,
f"{CAPABILITY_ENCRYPTION_KEY_MATERIAL_PROVIDER_PREFIX}{LOCAL_PROVIDER_ID}": _local_provider,
f"{CAPABILITY_ENCRYPTION_CONTENT_CIPHER_PROVIDER_PREFIX}{LOCAL_PROVIDER_ID}": _local_provider,
},
capability_documentation={
CAPABILITY_ENCRYPTION_KEY_VAULT: CapabilityDocumentation(
label="Governed encryption key vault",
summary=(
"Orchestrates opaque, idempotent provider key references, "
"lifecycle state, and policy provenance without exposing key material."
),
contract_version="1.0.0",
audience=("administrator", "security_officer", "auditor"),
),
CAPABILITY_ENCRYPTION_CONTENT_PROTECTION: CapabilityDocumentation(
label="Content-protection envelope registry",
summary=(
"Registers versioned protection envelopes and fail-closed, "
"evidence-backed migration state for feature-owned content."
),
contract_version="1.0.0",
),
CAPABILITY_ENCRYPTION_CONTENT_CIPHER: CapabilityDocumentation(
label="Server-side content cipher",
summary=(
"Protects and opens owner-module content through opaque, "
"versioned envelopes without exporting key material."
),
contract_version="1.0.0",
audience=("module_developer", "security_officer", "auditor"),
),
CAPABILITY_ENCRYPTION_RECOVERY: CapabilityDocumentation(
label="Encryption recovery ceremony",
summary=(
"Requires recent high assurance, distinct custodians, quorum, "
"expiry, and immutable evidence without changing ownership."
),
contract_version="1.0.0",
),
CAPABILITY_ENCRYPTION_DISABLE_PREFLIGHT: CapabilityDocumentation(
label="Encryption disable preflight",
summary=(
"Blocks disable or uninstall while any protection envelope "
"remains unresolved."
),
contract_version="1.0.0",
),
ENCRYPTION_DSAR_CAPABILITY: CapabilityDocumentation(
label="Encryption data-subject request provider",
summary=(
"Exports minimized cryptographic custody attribution without key "
"material, ciphertext references, or protected evidence."
),
contract_version="0.1.0",
),
},
migration_spec=MigrationSpec(
module_id=MODULE_ID,
metadata=Base.metadata,
script_location=str(Path(__file__).with_name("migrations") / "versions"),
retirement_supported=True,
retirement_provider=drop_table_retirement_provider(
models.RecoveryApproval,
models.RecoveryCeremony,
models.ProtectionMigration,
models.ContentProtectionRecord,
models.EncryptionLocalProviderOperation,
models.EncryptionLocalWrappedContentKey,
models.EncryptionLocalKeyMaterial,
models.EncryptionKeyOperation,
models.EncryptionKeyVersion,
models.EncryptionVault,
label=MODULE_NAME,
),
retirement_notes=(
"Destructive retirement remains blocked until disable preflight "
"proves that no unresolved protected envelope remains."
),
),
uninstall_guard_providers=(
_disable_guard,
persistent_table_uninstall_guard(
models.EncryptionVault,
models.EncryptionKeyVersion,
models.EncryptionKeyOperation,
models.ContentProtectionRecord,
models.ProtectionMigration,
models.RecoveryCeremony,
models.RecoveryApproval,
models.EncryptionLocalKeyMaterial,
models.EncryptionLocalWrappedContentKey,
models.EncryptionLocalProviderOperation,
label=MODULE_NAME,
),
),
documentation=(
DocumentationTopic(
id="encryption.data-subject-requests",
title="Encryption data-subject requests",
summary=(
"Export cryptographic custody participation without exposing protected "
"content or key material."
),
body=(
"Encryption correlates only an exact tenant account identifier and can "
"narrow an already verified search to a vault, key operation, envelope, "
"migration, or recovery ceremony. The export reports minimized vault "
"administration, key-operation requests, protection registrations, "
"migrations, and recovery participation. It never returns provider or "
"public key references, wrapped keys, nonces, ciphertext locations, "
"resource identifiers, digests, request payloads, assurance and policy "
"references, recovery reasons, idempotency keys, or provenance. Feature "
"modules remain responsible for exporting the plaintext semantics of "
"their own protected resources. Cryptographic lifecycle and custody "
"attribution remains immutable security evidence and is retained rather "
"than automatically erased."
),
layer="configured",
documentation_types=("admin", "user"),
audience=("user", "administrator", "security_officer", "auditor"),
related_modules=("core", "identity_trust", "audit", "policy"),
order=95,
metadata={
"kind": "reference",
"help_contexts": [
"encryption.admin.operations",
"privacy.data-subject-requests",
],
"consequence_classes": {
"export_custody_attribution": (
"Returns minimized lifecycle activity for the exact account."
),
"exclude_cryptographic_secrets": (
"Never returns key material, ciphertext references, nonces, or protected evidence."
),
"retain_cryptographic_evidence": (
"Preserves immutable custody and recovery accountability."
),
},
},
translations={
"de": {
"title": "Datenschutzanfragen zur Verschlüsselung",
"summary": (
"Mitwirkung an kryptografischer Verwahrung ausgeben, ohne geschützte Inhalte oder Schlüsselmaterial offenzulegen."
),
"body": (
"Encryption gleicht ausschließlich eine exakte Mandantenkontokennung ab und kann eine bereits verifizierte "
"Suche auf einen Tresor, Schlüsselvorgang, Umschlag, eine Migration oder Wiederherstellungszeremonie "
"einschränken. Die Ausgabe meldet minimierte Tresoradministration, Schlüsselvorgangsanfragen, "
"Schutzregistrierungen, Migrationen und Wiederherstellungsmitwirkung. Sie enthält niemals Anbieter- oder "
"öffentliche Schlüsselverweise, umhüllte Schlüssel, Nonces, Chiffratorte, Ressourcenkennungen, Prüfsummen, "
"Anfrageinhalte, Zusicherungs- und Regelverweise, Wiederherstellungsgründe, Idempotenzschlüssel oder Provenienz. "
"Fachmodule bleiben für die Ausgabe der Klartextsemantik ihrer geschützten Ressourcen verantwortlich. "
"Zuordnungen des kryptografischen Lebenszyklus und der Verwahrung bleiben unveränderliche Sicherheitsnachweise "
"und werden nicht automatisch gelöscht."
),
}
},
structured_translation_version="1",
structured_translations={
"de": {
"consequence_classes": {
"export_custody_attribution": "Gibt minimierte Lebenszyklusaktivität für das exakte Konto zurück.",
"exclude_cryptographic_secrets": "Gibt niemals Schlüsselmaterial, Chiffratverweise, Nonces oder geschützte Nachweise zurück.",
"retain_cryptographic_evidence": "Bewahrt unveränderliche Verantwortungsnachweise zu Verwahrung und Wiederherstellung.",
}
}
},
),
DocumentationTopic(
id="encryption.boundary",
title="Encryption and key-custody boundary",
summary=(
"Optional vault, content-protection, rotation, recovery, and "
"disable-assurance capabilities."
),
body=(
"Encryption protects feature-owned content without taking over "
"its business ownership. Resource ownership recovery never "
"implicitly grants cryptographic keys. High-risk lifecycle "
"actions require recent Identity Trust assurance. Disabling is "
"blocked until each envelope is migrated, decrypted, explicitly "
"exported, or cryptographically destroyed. The bundled local "
"AES-GCM provider is server-readable and requires the deployment "
"master key; it does not imply end-to-end encryption."
),
layer="available",
documentation_types=("admin", "user"),
audience=(
"user",
"administrator",
"security_officer",
"product_owner",
"auditor",
),
related_modules=OPTIONAL_DEPENDENCIES,
order=100,
links=(
DocumentationLink(
label="Encryption boundary and threat model",
href="govoplan-encryption/docs/ENCRYPTION_BOUNDARY.md",
kind="repository",
),
),
metadata={"kind": "reference"},
translations={
"de": {
"title": "Abgrenzung von Verschlüsselung und Schlüsselverwahrung",
"summary": (
"Optionale Fähigkeiten für Tresore, Inhaltsschutz, Rotation, Wiederherstellung und Deaktivierungsnachweise."
),
"body": (
"Encryption schützt fachmoduleigene Inhalte, ohne deren fachliche Eigentümerschaft zu übernehmen. Die "
"Wiederherstellung von Ressourceneigentum gewährt niemals stillschweigend kryptografische Schlüssel. "
"Lebenszyklusaktionen mit hohem Risiko erfordern eine aktuelle Identity-Trust-Zusicherung. Die Deaktivierung "
"bleibt gesperrt, bis jeder Umschlag migriert, entschlüsselt, ausdrücklich exportiert oder kryptografisch "
"vernichtet wurde. Der mitgelieferte lokale AES-GCM-Anbieter ist serverseitig lesbar und erfordert den "
"Deployment-Hauptschlüssel; er begründet keine Ende-zu-Ende-Verschlüsselung."
),
}
},
),
DocumentationTopic(
id="encryption.administration",
title="Administer encryption operations",
summary=(
"Inspect safe vault and envelope metadata, govern key lifecycle, "
"coordinate migrations, and verify disable readiness."
),
body=(
"Encryption administration exposes bounded tenant metadata but "
"never provider key references, wrapped keys, ciphertext locations, "
"or plaintext. Rotation creates a new current version while existing "
"envelopes remain version-bound. Revocation and destruction cannot "
"recall material already obtained and can make content unavailable. "
"Migrations remain two-phase: the owning module performs the durable "
"content operation and records evidence before success. Disable "
"preflight blocks until every envelope has a terminal disposition."
),
layer="available",
documentation_types=("admin",),
audience=("administrator", "security_officer", "auditor"),
related_modules=OPTIONAL_DEPENDENCIES,
order=110,
metadata={"kind": "reference"},
translations={
"de": {
"title": "Verschlüsselungsvorgänge administrieren",
"summary": (
"Sichere Tresor- und Umschlagmetadaten prüfen, den Schlüssellebenszyklus steuern, Migrationen koordinieren und Deaktivierungsbereitschaft nachweisen."
),
"body": (
"Die Encryption-Administration zeigt begrenzte Mandantenmetadaten, aber niemals Anbieterschlüsselverweise, "
"umhüllte Schlüssel, Chiffratorte oder Klartext. Rotation erzeugt eine neue aktuelle Version, während "
"bestehende Umschläge an ihre Version gebunden bleiben. Widerruf und Vernichtung können bereits erlangtes "
"Material nicht zurückrufen und Inhalte unzugänglich machen. Migrationen bleiben zweiphasig: Das "
"Eigentümermodul führt den dauerhaften Inhaltsvorgang aus und hält vor Erfolg Nachweise fest. Die "
"Deaktivierungsvorprüfung sperrt, bis jeder Umschlag einen endgültigen Verbleib hat."
),
}
},
),
DocumentationTopic(
id="encryption.recovery",
title="Run an encryption recovery ceremony",
summary=(
"Request and decide time-bounded recovery with high assurance and "
"a distinct-custodian quorum."
),
body=(
"A recovery requester supplies policy and recent high-assurance "
"evidence and cannot approve the same ceremony. Each custodian can "
"decide once; a rejection terminates the request and approvals must "
"reach the vault quorum before expiry. Approval authorizes a later "
"provider operation. It does not release key material, transfer "
"resource ownership, or prove that recovery execution succeeded."
),
layer="available",
documentation_types=("admin", "user"),
audience=("administrator", "security_officer", "auditor"),
conditions=(
DocumentationCondition(required_scopes=(RECOVERY_SCOPE,)),
),
related_modules=("identity_trust", "access", "audit", "policy"),
order=120,
metadata={"kind": "workflow"},
translations={
"de": {
"title": "Wiederherstellungszeremonie für Verschlüsselung durchführen",
"summary": (
"Zeitlich begrenzte Wiederherstellung mit hoher Zusicherung und Quorum unterschiedlicher Verwahrender beantragen und entscheiden."
),
"body": (
"Die antragstellende Person legt Regel und aktuellen Nachweis hoher Zusicherung vor und darf dieselbe "
"Zeremonie nicht genehmigen. Jede verwahrende Person kann genau einmal entscheiden; eine Ablehnung beendet "
"den Antrag und Genehmigungen müssen das Tresorquorum vor Ablauf erreichen. Die Genehmigung autorisiert einen "
"späteren Anbietervorgang. Sie gibt kein Schlüsselmaterial frei, überträgt kein Ressourceneigentum und beweist "
"nicht, dass die Wiederherstellung erfolgreich ausgeführt wurde."
),
}
},
),
),
architecture=declared_module_architecture(
layer="institutional_foundation",
kind="foundation",
maturity="vertical_slice",
documentation_ref="docs/ENCRYPTION_BOUNDARY.md",
test_ref="tests/test_encryption.py",
known_limits=(
"The bundled local AES-256-GCM provider is a server-side reference provider backed by shared SQL state and MASTER_KEY_B64; it is not an HSM/KMS, client E2EE protocol, or independent certification.",
"A true E2EE claim remains prohibited until a selected client/provider profile passes its threat model, interoperability fixtures, backup/restore tests, and independent review.",
),
owned_concepts=(
"key vault",
"content-protection envelope",
"cryptographic recovery ceremony",
),
non_owned_concepts=(
"domain content",
"resource ownership",
"account authentication",
"provider key material",
),
migration_docs=("docs/ENCRYPTION_BOUNDARY.md",),
recovery_docs=("docs/ENCRYPTION_BOUNDARY.md",),
security_docs=("docs/ENCRYPTION_BOUNDARY.md",),
operations_docs=("README.md",),
),
)
def get_manifest() -> ModuleManifest:
return manifest
__all__ = [
"ADMIN_SCOPE",
"MODULE_ID",
"MODULE_VERSION",
"RECOVERY_SCOPE",
"USE_SCOPE",
"get_manifest",
"manifest",
]