Implement governed encryption lifecycle
This commit is contained in:
@@ -1,13 +1,33 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from govoplan_core.core.encryption import (
|
||||
CAPABILITY_ENCRYPTION_CONTENT_PROTECTION,
|
||||
CAPABILITY_ENCRYPTION_DISABLE_PREFLIGHT,
|
||||
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,
|
||||
DocumentationLink,
|
||||
DocumentationTopic,
|
||||
MigrationSpec,
|
||||
ModuleContext,
|
||||
ModuleInterfaceProvider,
|
||||
ModuleManifest,
|
||||
ModuleUninstallGuardResult,
|
||||
PermissionDefinition,
|
||||
RoleTemplate,
|
||||
)
|
||||
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.service import SqlEncryptionService
|
||||
|
||||
|
||||
MODULE_ID = "encryption"
|
||||
@@ -27,14 +47,11 @@ OPTIONAL_DEPENDENCIES = (
|
||||
"postbox",
|
||||
"campaigns",
|
||||
"workflow_engine",
|
||||
"identity_trust",
|
||||
)
|
||||
|
||||
|
||||
def _permission(
|
||||
scope: str,
|
||||
label: str,
|
||||
description: str,
|
||||
) -> PermissionDefinition:
|
||||
def _permission(scope: str, label: str, description: str) -> PermissionDefinition:
|
||||
module_id, resource, action = scope.split(":", 2)
|
||||
return PermissionDefinition(
|
||||
scope=scope,
|
||||
@@ -48,21 +65,64 @@ def _permission(
|
||||
)
|
||||
|
||||
|
||||
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 _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",
|
||||
"Protect and decrypt authorized content through an available profile.",
|
||||
"Register and resolve protected content through configured profiles.",
|
||||
),
|
||||
_permission(
|
||||
ADMIN_SCOPE,
|
||||
"Administer encryption",
|
||||
"Manage vaults, protection profiles, key rotation, and provider policy.",
|
||||
"Manage vault metadata, provider operations, rotation, migration, and policy provenance.",
|
||||
),
|
||||
_permission(
|
||||
RECOVERY_SCOPE,
|
||||
"Approve key recovery",
|
||||
"Participate in an auditable recovery ceremony without gaining content ownership.",
|
||||
"Participate in a high-assurance recovery ceremony without gaining content ownership.",
|
||||
),
|
||||
)
|
||||
|
||||
@@ -87,7 +147,10 @@ manifest = ModuleManifest(
|
||||
version=MODULE_VERSION,
|
||||
optional_dependencies=OPTIONAL_DEPENDENCIES,
|
||||
provides_interfaces=(
|
||||
ModuleInterfaceProvider(name="encryption.key_vault", version="1.0.0"),
|
||||
ModuleInterfaceProvider(
|
||||
name="encryption.key_vault",
|
||||
version="1.0.0",
|
||||
),
|
||||
ModuleInterfaceProvider(
|
||||
name="encryption.content_protection",
|
||||
version="1.0.0",
|
||||
@@ -103,6 +166,81 @@ manifest = ModuleManifest(
|
||||
),
|
||||
permissions=PERMISSIONS,
|
||||
role_templates=ROLE_TEMPLATES,
|
||||
route_factory=_router,
|
||||
capability_factories={
|
||||
CAPABILITY_ENCRYPTION_KEY_VAULT: _service,
|
||||
CAPABILITY_ENCRYPTION_CONTENT_PROTECTION: _service,
|
||||
CAPABILITY_ENCRYPTION_RECOVERY: _service,
|
||||
CAPABILITY_ENCRYPTION_DISABLE_PREFLIGHT: _service,
|
||||
},
|
||||
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_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",
|
||||
),
|
||||
},
|
||||
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.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,
|
||||
label=MODULE_NAME,
|
||||
),
|
||||
),
|
||||
documentation=(
|
||||
DocumentationTopic(
|
||||
id="encryption.boundary",
|
||||
@@ -114,26 +252,57 @@ manifest = ModuleManifest(
|
||||
body=(
|
||||
"Encryption protects feature-owned content without taking over "
|
||||
"its business ownership. Resource ownership recovery never "
|
||||
"implicitly grants cryptographic keys. Disabling the module is "
|
||||
"blocked until protected objects are decrypted, rewrapped, "
|
||||
"explicitly exported, or cryptographically deleted."
|
||||
"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. No bundled provider "
|
||||
"or E2EE claim is implied by enabling this module."
|
||||
),
|
||||
layer="available",
|
||||
documentation_types=("admin", "user"),
|
||||
audience=("user", "administrator", "security_officer", "product_owner"),
|
||||
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",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
architecture=declared_module_architecture(
|
||||
layer="institutional_foundation",
|
||||
kind="foundation",
|
||||
maturity="scaffold",
|
||||
maturity="vertical_slice",
|
||||
documentation_ref="docs/ENCRYPTION_BOUNDARY.md",
|
||||
known_limits=("No production key vault, protected-content persistence, rotation worker, or recovery ceremony is implemented yet.",),
|
||||
owned_concepts=("key vault", "content-protection envelope", "cryptographic recovery ceremony"),
|
||||
non_owned_concepts=("domain content", "resource ownership", "account authentication"),
|
||||
test_ref="tests/test_encryption.py",
|
||||
known_limits=(
|
||||
"The module orchestrates references and evidence but ships no concrete cryptographic provider, raw key store, cipher implementation, client E2EE protocol, KMS/HSM conformance suite, or production recovery executor.",
|
||||
"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",),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user