familia 2.0.0.pre14 → 2.0.0.pre16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/code-quality.yml +138 -0
- data/.github/workflows/code-smellage.yml +145 -0
- data/.github/workflows/docs.yml +31 -8
- data/.gitignore +1 -1
- data/.pre-commit-config.yaml +7 -1
- data/.reek.yml +98 -0
- data/.rubocop.yml +48 -10
- data/.talismanrc +9 -0
- data/.yardopts +18 -13
- data/CHANGELOG.rst +66 -6
- data/CLAUDE.md +1 -1
- data/Gemfile +6 -5
- data/Gemfile.lock +99 -23
- data/LICENSE.txt +1 -1
- data/README.md +285 -85
- data/changelog.d/README.md +2 -2
- data/docs/archive/FAMILIA_RELATIONSHIPS.md +22 -22
- data/docs/archive/FAMILIA_TECHNICAL.md +41 -41
- data/docs/archive/FAMILIA_UPDATE.md +3 -3
- data/docs/archive/README.md +3 -2
- data/docs/{guides/API-Reference.md → archive/api-reference.md} +87 -101
- data/docs/conf.py +29 -0
- data/docs/guides/{Field-System-Guide.md → core-field-system.md} +9 -9
- data/docs/guides/feature-encrypted-fields.md +785 -0
- data/docs/guides/{Expiration-Feature-Guide.md → feature-expiration.md} +11 -2
- data/docs/guides/feature-external-identifiers.md +637 -0
- data/docs/guides/feature-object-identifiers.md +435 -0
- data/docs/guides/{Quantization-Feature-Guide.md → feature-quantization.md} +94 -29
- data/docs/guides/feature-relationships-methods.md +684 -0
- data/docs/guides/feature-relationships.md +200 -0
- data/docs/guides/{Features-System-Developer-Guide.md → feature-system-devs.md} +4 -4
- data/docs/guides/{Feature-System-Guide.md → feature-system.md} +5 -5
- data/docs/guides/{Transient-Fields-Guide.md → feature-transient-fields.md} +2 -2
- data/docs/guides/{Implementation-Guide.md → implementation.md} +3 -3
- data/docs/guides/index.md +176 -0
- data/docs/guides/{Security-Model.md → security-model.md} +1 -1
- data/docs/migrating/v2.0.0-pre.md +1 -1
- data/docs/migrating/v2.0.0-pre11.md +4 -4
- data/docs/migrating/v2.0.0-pre12.md +2 -2
- data/docs/migrating/v2.0.0-pre13.md +1 -1
- data/docs/migrating/v2.0.0-pre5.md +33 -12
- data/docs/migrating/v2.0.0-pre6.md +2 -2
- data/docs/migrating/v2.0.0-pre7.md +8 -8
- data/docs/overview.md +623 -19
- data/docs/reference/api-technical.md +1365 -0
- data/examples/autoloader/mega_customer/features/deprecated_fields.rb +7 -0
- data/examples/autoloader/mega_customer/safe_dump_fields.rb +1 -1
- data/examples/autoloader/mega_customer.rb +3 -1
- data/examples/encrypted_fields.rb +378 -0
- data/examples/json_usage_patterns.rb +144 -0
- data/examples/relationships.rb +13 -13
- data/examples/safe_dump.rb +6 -6
- data/examples/single_connection_transaction_confusions.rb +379 -0
- data/lib/familia/base.rb +49 -10
- data/lib/familia/connection/handlers.rb +223 -0
- data/lib/familia/connection/individual_command_proxy.rb +64 -0
- data/lib/familia/connection/middleware.rb +75 -0
- data/lib/familia/connection/operation_core.rb +93 -0
- data/lib/familia/connection/operations.rb +277 -0
- data/lib/familia/connection/pipeline_core.rb +87 -0
- data/lib/familia/connection/transaction_core.rb +100 -0
- data/lib/familia/connection.rb +60 -186
- data/lib/familia/data_type/commands.rb +53 -51
- data/lib/familia/data_type/serialization.rb +108 -107
- data/lib/familia/data_type/types/counter.rb +1 -1
- data/lib/familia/data_type/types/hashkey.rb +13 -10
- data/lib/familia/data_type/types/{list.rb → listkey.rb} +13 -5
- data/lib/familia/data_type/types/lock.rb +3 -2
- data/lib/familia/data_type/types/sorted_set.rb +26 -15
- data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -5
- data/lib/familia/data_type/types/unsorted_set.rb +20 -27
- data/lib/familia/data_type.rb +75 -47
- data/lib/familia/distinguisher.rb +85 -0
- data/lib/familia/encryption/encrypted_data.rb +15 -24
- data/lib/familia/encryption/manager.rb +6 -4
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +1 -1
- data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +7 -9
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -5
- data/lib/familia/encryption/request_cache.rb +7 -7
- data/lib/familia/encryption.rb +2 -3
- data/lib/familia/errors.rb +9 -3
- data/lib/familia/{autoloader.rb → features/autoloader.rb} +49 -23
- data/lib/familia/features/encrypted_fields/concealed_string.rb +3 -4
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +13 -14
- data/lib/familia/features/encrypted_fields.rb +68 -66
- data/lib/familia/features/expiration/extensions.rb +61 -0
- data/lib/familia/features/expiration.rb +35 -87
- data/lib/familia/features/external_identifier.rb +11 -12
- data/lib/familia/features/object_identifier.rb +58 -20
- data/lib/familia/features/quantization.rb +17 -22
- data/lib/familia/features/relationships/README.md +97 -0
- data/lib/familia/features/relationships/collection_operations.rb +104 -0
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +202 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +301 -0
- data/lib/familia/features/relationships/indexing.rb +176 -256
- data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
- data/lib/familia/features/relationships/participation/participant_methods.rb +160 -0
- data/lib/familia/features/relationships/participation/target_methods.rb +225 -0
- data/lib/familia/features/relationships/participation.rb +656 -0
- data/lib/familia/features/relationships/participation_relationship.rb +31 -0
- data/lib/familia/features/relationships/score_encoding.rb +20 -20
- data/lib/familia/features/relationships.rb +69 -271
- data/lib/familia/features/safe_dump.rb +127 -132
- data/lib/familia/features/transient_fields/redacted_string.rb +6 -6
- data/lib/familia/features/transient_fields/transient_field_type.rb +5 -5
- data/lib/familia/features/transient_fields.rb +5 -5
- data/lib/familia/features.rb +21 -21
- data/lib/familia/field_type.rb +24 -4
- data/lib/familia/horreum/core/connection.rb +229 -26
- data/lib/familia/horreum/core/database_commands.rb +27 -17
- data/lib/familia/horreum/core/serialization.rb +40 -20
- data/lib/familia/horreum/core/utils.rb +2 -1
- data/lib/familia/horreum/shared/settings.rb +2 -1
- data/lib/familia/horreum/subclass/definition.rb +33 -45
- data/lib/familia/horreum/subclass/management.rb +72 -24
- data/lib/familia/horreum/subclass/related_fields_management.rb +82 -21
- data/lib/familia/horreum.rb +196 -114
- data/lib/familia/json_serializer.rb +0 -1
- data/lib/familia/logging.rb +11 -114
- data/lib/familia/refinements/dear_json.rb +122 -0
- data/lib/familia/refinements/logger_trace.rb +20 -17
- data/lib/familia/refinements/stylize_words.rb +65 -0
- data/lib/familia/refinements/time_literals.rb +60 -52
- data/lib/familia/refinements.rb +2 -1
- data/lib/familia/secure_identifier.rb +60 -28
- data/lib/familia/settings.rb +83 -7
- data/lib/familia/utils.rb +5 -87
- data/lib/familia/verifiable_identifier.rb +4 -4
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +72 -15
- data/lib/middleware/database_middleware.rb +56 -14
- data/lib/{familia/multi_result.rb → multi_result.rb} +23 -16
- data/try/configuration/scenarios_try.rb +1 -1
- data/try/connection/fiber_context_preservation_try.rb +250 -0
- data/try/connection/handler_constraints_try.rb +59 -0
- data/try/connection/operation_mode_guards_try.rb +208 -0
- data/try/connection/pipeline_fallback_integration_try.rb +128 -0
- data/try/connection/responsibility_chain_tracking_try.rb +72 -0
- data/try/connection/transaction_fallback_integration_try.rb +288 -0
- data/try/connection/transaction_mode_permissive_try.rb +153 -0
- data/try/connection/transaction_mode_strict_try.rb +98 -0
- data/try/connection/transaction_mode_warn_try.rb +131 -0
- data/try/connection/transaction_modes_try.rb +249 -0
- data/try/core/autoloader_try.rb +129 -11
- data/try/core/connection_try.rb +7 -7
- data/try/core/conventional_inheritance_try.rb +130 -0
- data/try/core/create_method_try.rb +15 -23
- data/try/core/database_consistency_try.rb +10 -10
- data/try/core/errors_try.rb +8 -11
- data/try/core/familia_extended_try.rb +2 -2
- data/try/core/familia_members_methods_try.rb +76 -0
- data/try/core/isolated_dbclient_try.rb +165 -0
- data/try/core/middleware_try.rb +16 -16
- data/try/core/persistence_operations_try.rb +4 -4
- data/try/core/pools_try.rb +42 -26
- data/try/core/secure_identifier_try.rb +28 -24
- data/try/core/time_utils_try.rb +10 -10
- data/try/core/tools_try.rb +1 -1
- data/try/core/utils_try.rb +2 -2
- data/try/data_types/boolean_try.rb +4 -4
- data/try/data_types/datatype_base_try.rb +0 -2
- data/try/data_types/list_try.rb +10 -10
- data/try/data_types/sorted_set_try.rb +5 -5
- data/try/data_types/string_try.rb +12 -12
- data/try/data_types/unsortedset_try.rb +33 -0
- data/try/debugging/cache_behavior_tracer.rb +7 -7
- data/try/debugging/debug_aad_process.rb +1 -1
- data/try/debugging/debug_concealed_internal.rb +1 -1
- data/try/debugging/debug_cross_context.rb +1 -1
- data/try/debugging/debug_fresh_cross_context.rb +1 -1
- data/try/debugging/encryption_method_tracer.rb +10 -10
- data/try/edge_cases/hash_symbolization_try.rb +1 -1
- data/try/edge_cases/ttl_side_effects_try.rb +1 -1
- data/try/encryption/config_persistence_try.rb +2 -2
- data/try/encryption/encryption_core_try.rb +19 -19
- data/try/encryption/instance_variable_scope_try.rb +1 -1
- data/try/encryption/module_loading_try.rb +2 -2
- data/try/encryption/providers/aes_gcm_provider_try.rb +1 -1
- data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +1 -1
- data/try/encryption/secure_memory_handling_try.rb +1 -1
- data/try/features/encrypted_fields/concealed_string_core_try.rb +11 -7
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +3 -3
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +10 -10
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +14 -14
- data/try/features/encrypted_fields/error_conditions_try.rb +7 -7
- data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
- data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +7 -7
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +13 -20
- data/try/features/external_identifier/external_identifier_try.rb +1 -1
- data/try/features/feature_dependencies_try.rb +3 -3
- data/try/features/object_identifier/object_identifier_integration_try.rb +28 -34
- data/try/features/object_identifier/object_identifier_try.rb +10 -0
- data/try/features/quantization/quantization_try.rb +1 -1
- data/try/features/relationships/indexing_commands_verification_try.rb +136 -0
- data/try/features/relationships/indexing_try.rb +433 -0
- data/try/features/relationships/participation_commands_verification_spec.rb +102 -0
- data/try/features/relationships/participation_commands_verification_try.rb +105 -0
- data/try/features/relationships/participation_performance_improvements_try.rb +124 -0
- data/try/features/relationships/participation_reverse_index_try.rb +196 -0
- data/try/features/relationships/relationships_api_changes_try.rb +72 -71
- data/try/features/relationships/relationships_edge_cases_try.rb +15 -18
- data/try/features/relationships/relationships_performance_minimal_try.rb +2 -2
- data/try/features/relationships/relationships_performance_simple_try.rb +8 -8
- data/try/features/relationships/relationships_performance_try.rb +20 -20
- data/try/features/relationships/relationships_try.rb +27 -38
- data/try/features/safe_dump/safe_dump_advanced_try.rb +2 -2
- data/try/features/transient_fields/refresh_reset_try.rb +1 -1
- data/try/features/transient_fields/simple_refresh_test.rb +1 -1
- data/try/helpers/test_cleanup.rb +86 -0
- data/try/helpers/test_helpers.rb +3 -3
- data/try/horreum/base_try.rb +3 -2
- data/try/horreum/commands_try.rb +1 -1
- data/try/horreum/destroy_related_fields_cleanup_try.rb +330 -0
- data/try/horreum/initialization_try.rb +11 -7
- data/try/horreum/relations_try.rb +21 -13
- data/try/horreum/serialization_try.rb +12 -11
- data/try/integration/cross_component_try.rb +3 -3
- data/try/memory/memory_basic_test.rb +1 -1
- data/try/memory/memory_docker_ruby_dump.sh +1 -1
- data/try/models/customer_safe_dump_try.rb +1 -1
- data/try/models/customer_try.rb +8 -10
- data/try/models/datatype_base_try.rb +3 -3
- data/try/models/familia_object_try.rb +9 -8
- data/try/performance/benchmarks_try.rb +2 -2
- data/try/prototypes/atomic_saves_v1_context_proxy.rb +2 -2
- data/try/prototypes/atomic_saves_v3_connection_pool.rb +3 -3
- data/try/prototypes/atomic_saves_v4.rb +1 -1
- data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -4
- data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
- data/try/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
- data/try/prototypes/pooling/lib/connection_pool_metrics.rb +5 -5
- data/try/prototypes/pooling/lib/connection_pool_stress_test.rb +26 -26
- data/try/prototypes/pooling/lib/connection_pool_threading_models.rb +7 -7
- data/try/prototypes/pooling/lib/visualize_stress_results.rb +1 -1
- data/try/prototypes/pooling/pool_siege.rb +11 -11
- data/try/prototypes/pooling/run_stress_tests.rb +7 -7
- data/try/refinements/dear_json_array_methods_try.rb +53 -0
- data/try/refinements/dear_json_hash_methods_try.rb +54 -0
- data/try/refinements/logger_trace_methods_try.rb +44 -0
- data/try/refinements/time_literals_numeric_methods_try.rb +141 -0
- data/try/refinements/time_literals_string_methods_try.rb +80 -0
- metadata +77 -45
- data/.rubocop_todo.yml +0 -208
- data/docs/connection_pooling.md +0 -192
- data/docs/guides/Connection-Pooling-Guide.md +0 -437
- data/docs/guides/Encrypted-Fields-Overview.md +0 -101
- data/docs/guides/Feature-System-Autoloading.md +0 -228
- data/docs/guides/Home.md +0 -116
- data/docs/guides/Relationships-Guide.md +0 -737
- data/docs/guides/relationships-methods.md +0 -266
- data/docs/reference/auditing_database_commands.rb +0 -228
- data/examples/permissions.rb +0 -240
- data/lib/familia/features/autoloadable.rb +0 -113
- data/lib/familia/features/relationships/cascading.rb +0 -437
- data/lib/familia/features/relationships/membership.rb +0 -497
- data/lib/familia/features/relationships/permission_management.rb +0 -264
- data/lib/familia/features/relationships/querying.rb +0 -615
- data/lib/familia/features/relationships/redis_operations.rb +0 -274
- data/lib/familia/features/relationships/tracking.rb +0 -418
- data/lib/familia/refinements/snake_case.rb +0 -40
- data/lib/familia/validation/command_recorder.rb +0 -336
- data/lib/familia/validation/expectations.rb +0 -519
- data/lib/familia/validation/validation_helpers.rb +0 -443
- data/lib/familia/validation/validator.rb +0 -412
- data/lib/familia/validation.rb +0 -140
- data/try/data_types/set_try.rb +0 -33
- data/try/features/autoloadable/autoloadable_try.rb +0 -61
- data/try/features/relationships/categorical_permissions_try.rb +0 -515
- data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -111
- data/try/validation/atomic_operations_try.rb.disabled +0 -320
- data/try/validation/command_validation_try.rb.disabled +0 -207
- data/try/validation/performance_validation_try.rb.disabled +0 -324
- data/try/validation/real_world_scenarios_try.rb.disabled +0 -390
@@ -0,0 +1,785 @@
|
|
1
|
+
# Encrypted Fields Guide
|
2
|
+
|
3
|
+
> **💡 Quick Reference**
|
4
|
+
>
|
5
|
+
> Add persistent encrypted storage to any Familia model:
|
6
|
+
> ```ruby
|
7
|
+
> class User < Familia::Horreum
|
8
|
+
> feature :encrypted_fields
|
9
|
+
> encrypted_field :sensitive_data
|
10
|
+
> end
|
11
|
+
> ```
|
12
|
+
|
13
|
+
## Overview
|
14
|
+
|
15
|
+
The Encrypted Fields feature provides transparent field-level encryption for sensitive data stored in Redis/Valkey. It combines industry-standard encryption algorithms with Ruby-friendly APIs, ensuring your sensitive data is protected at rest while maintaining the performance and simplicity you expect from Familia.
|
16
|
+
|
17
|
+
## Why Use Encrypted Fields?
|
18
|
+
|
19
|
+
**Compliance**: Meet regulatory requirements (GDPR, HIPAA, PCI-DSS) for sensitive data protection.
|
20
|
+
|
21
|
+
**Defense in Depth**: Protect against database breaches, memory dumps, and unauthorized Valkey/Redis access.
|
22
|
+
|
23
|
+
**Transparent Security**: Encryption and decryption happen automatically - no changes to your application logic.
|
24
|
+
|
25
|
+
**Performance Focused**: Optimized for Ruby's memory model with request-level caching and efficient key derivation.
|
26
|
+
|
27
|
+
**Future Proof**: Modular provider system supports algorithm upgrades and key rotation.
|
28
|
+
|
29
|
+
> **⚠️ Security Consideration**
|
30
|
+
>
|
31
|
+
> Encrypted fields protect data at rest in Redis/Valkey. Consider your threat model: encryption keys are held in Ruby memory and may be visible in memory dumps.
|
32
|
+
|
33
|
+
## Quick Start
|
34
|
+
|
35
|
+
### Basic Encrypted Storage
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
class Customer < Familia::Horreum
|
39
|
+
feature :encrypted_fields
|
40
|
+
|
41
|
+
# Regular fields (stored as plaintext)
|
42
|
+
field :email, :company_name, :created_at
|
43
|
+
|
44
|
+
# Encrypted fields (automatically encrypted/decrypted)
|
45
|
+
encrypted_field :api_key
|
46
|
+
encrypted_field :notes
|
47
|
+
encrypted_field :credit_card_last_four
|
48
|
+
end
|
49
|
+
|
50
|
+
# Configure encryption keys
|
51
|
+
Familia.configure do |config|
|
52
|
+
config.encryption_keys = {
|
53
|
+
v1: ENV['FAMILIA_ENCRYPTION_KEY']
|
54
|
+
}
|
55
|
+
config.current_key_version = :v1
|
56
|
+
end
|
57
|
+
|
58
|
+
# Usage is identical to regular fields
|
59
|
+
customer = Customer.new(
|
60
|
+
email: 'contact@acme.com',
|
61
|
+
company_name: 'Acme Corporation',
|
62
|
+
api_key: 'sk-1234567890abcdef',
|
63
|
+
notes: 'VIP customer - handle with care'
|
64
|
+
)
|
65
|
+
|
66
|
+
customer.save
|
67
|
+
|
68
|
+
# Access returns ConcealedString for safety
|
69
|
+
customer.api_key.class # => ConcealedString
|
70
|
+
customer.api_key.to_s # => "[CONCEALED]" (safe for logging)
|
71
|
+
customer.api_key.reveal # => "sk-1234567890abcdef" (actual value)
|
72
|
+
```
|
73
|
+
|
74
|
+
### Key Generation and Setup
|
75
|
+
|
76
|
+
Generate secure encryption keys for your environment:
|
77
|
+
|
78
|
+
```bash
|
79
|
+
# Generate a new 32-byte key
|
80
|
+
export FAMILIA_ENCRYPTION_KEY=$(openssl rand -base64 32)
|
81
|
+
|
82
|
+
# For production, use a secure key management service
|
83
|
+
export FAMILIA_ENCRYPTION_KEY_V1="your-secure-key-from-vault"
|
84
|
+
export FAMILIA_ENCRYPTION_KEY_V2="new-key-for-rotation"
|
85
|
+
```
|
86
|
+
|
87
|
+
> **🔒 Key Management Best Practices**
|
88
|
+
>
|
89
|
+
> - Use different keys for each environment (development, staging, production)
|
90
|
+
> - Store keys in a secure key management service (AWS KMS, HashiCorp Vault)
|
91
|
+
> - Never commit keys to source control
|
92
|
+
> - Rotate keys regularly (recommended: every 90-180 days)
|
93
|
+
|
94
|
+
## Configuration Deep Dive
|
95
|
+
|
96
|
+
### Basic Configuration
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
Familia.configure do |config|
|
100
|
+
# Single key setup (simplest)
|
101
|
+
config.encryption_keys = {
|
102
|
+
v1: ENV['FAMILIA_ENCRYPTION_KEY']
|
103
|
+
}
|
104
|
+
config.current_key_version = :v1
|
105
|
+
|
106
|
+
# Optional: application-specific key derivation
|
107
|
+
config.encryption_personalization = 'MyApp-Production-2024'
|
108
|
+
end
|
109
|
+
|
110
|
+
# Validate configuration before use
|
111
|
+
Familia::Encryption.validate_configuration!
|
112
|
+
```
|
113
|
+
|
114
|
+
### Multi-Key Configuration (Key Rotation)
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
Familia.configure do |config|
|
118
|
+
# Multiple keys for rotation
|
119
|
+
config.encryption_keys = {
|
120
|
+
v1: ENV['FAMILIA_ENCRYPTION_KEY_V1'], # Legacy key
|
121
|
+
v2: ENV['FAMILIA_ENCRYPTION_KEY_V2'], # Current key
|
122
|
+
v3: ENV['FAMILIA_ENCRYPTION_KEY_V3'] # New key for rotation
|
123
|
+
}
|
124
|
+
|
125
|
+
# New data encrypted with v3, old data readable with v1/v2
|
126
|
+
config.current_key_version = :v3
|
127
|
+
|
128
|
+
# Provider-specific configuration
|
129
|
+
config.encryption_providers = {
|
130
|
+
xchacha20_poly1305: {
|
131
|
+
priority: 100,
|
132
|
+
require_gem: 'rbnacl'
|
133
|
+
},
|
134
|
+
aes_gcm: {
|
135
|
+
priority: 50,
|
136
|
+
always_available: true
|
137
|
+
}
|
138
|
+
}
|
139
|
+
end
|
140
|
+
```
|
141
|
+
|
142
|
+
> **💡 Key Rotation Strategy**
|
143
|
+
>
|
144
|
+
> 1. Add new key version to configuration
|
145
|
+
> 2. Update `current_key_version` to new version
|
146
|
+
> 3. Deploy application (new writes use new key)
|
147
|
+
> 4. Re-encrypt existing data with `re_encrypt_fields!`
|
148
|
+
> 5. Remove old key version after migration complete
|
149
|
+
|
150
|
+
## Encryption Providers
|
151
|
+
|
152
|
+
Familia supports multiple encryption algorithms with automatic provider selection:
|
153
|
+
|
154
|
+
### XChaCha20-Poly1305 (Recommended)
|
155
|
+
|
156
|
+
The preferred encryption algorithm offering excellent security and performance:
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
# Requires rbnacl gem
|
160
|
+
gem 'rbnacl', '~> 7.1'
|
161
|
+
|
162
|
+
# Automatic selection when available
|
163
|
+
class SecureVault < Familia::Horreum
|
164
|
+
feature :encrypted_fields
|
165
|
+
|
166
|
+
# Will use XChaCha20-Poly1305 if rbnacl is available
|
167
|
+
encrypted_field :master_password
|
168
|
+
encrypted_field :recovery_codes
|
169
|
+
end
|
170
|
+
```
|
171
|
+
|
172
|
+
**Characteristics:**
|
173
|
+
- **Algorithm**: XChaCha20-Poly1305 AEAD
|
174
|
+
- **Key Size**: 32 bytes (256 bits)
|
175
|
+
- **Nonce Size**: 24 bytes (192 bits) - collision resistant
|
176
|
+
- **Authentication**: Built-in with Poly1305 MAC
|
177
|
+
- **Performance**: Excellent on modern CPUs
|
178
|
+
|
179
|
+
> **🚀 Performance Tip**
|
180
|
+
>
|
181
|
+
> XChaCha20-Poly1305 is typically 20-30% faster than AES-GCM and provides better security margins.
|
182
|
+
|
183
|
+
### AES-256-GCM (Fallback)
|
184
|
+
|
185
|
+
Standard AES encryption using OpenSSL (always available):
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
# No additional gems required
|
189
|
+
class StandardVault < Familia::Horreum
|
190
|
+
feature :encrypted_fields
|
191
|
+
|
192
|
+
# Explicitly specify AES-GCM
|
193
|
+
encrypted_field :secret_data, provider: :aes_gcm
|
194
|
+
end
|
195
|
+
```
|
196
|
+
|
197
|
+
**Characteristics:**
|
198
|
+
- **Algorithm**: AES-256-GCM AEAD
|
199
|
+
- **Key Size**: 32 bytes (256 bits)
|
200
|
+
- **IV Size**: 12 bytes (96 bits)
|
201
|
+
- **Authentication**: Built-in with GCM mode
|
202
|
+
- **Availability**: Always available via OpenSSL
|
203
|
+
|
204
|
+
### Provider Selection Logic
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
# Check available providers
|
208
|
+
providers = Familia::Encryption.available_providers
|
209
|
+
# => [
|
210
|
+
# { name: :xchacha20_poly1305, priority: 100, available: true },
|
211
|
+
# { name: :aes_gcm, priority: 50, available: true }
|
212
|
+
# ]
|
213
|
+
|
214
|
+
# Force specific provider for testing
|
215
|
+
class TestVault < Familia::Horreum
|
216
|
+
feature :encrypted_fields
|
217
|
+
|
218
|
+
encrypted_field :test_data, provider: :aes_gcm # Force AES-GCM
|
219
|
+
end
|
220
|
+
```
|
221
|
+
|
222
|
+
## Advanced Field Configuration
|
223
|
+
|
224
|
+
### Additional Authenticated Data (AAD)
|
225
|
+
|
226
|
+
Protect against field tampering by including related fields in authentication:
|
227
|
+
|
228
|
+
```ruby
|
229
|
+
class SecureDocument < Familia::Horreum
|
230
|
+
feature :encrypted_fields
|
231
|
+
|
232
|
+
field :document_id, :owner_id, :created_at
|
233
|
+
|
234
|
+
# Include document_id and owner_id in authentication
|
235
|
+
encrypted_field :content, aad_fields: [:document_id, :owner_id]
|
236
|
+
encrypted_field :metadata, aad_fields: [:document_id, :created_at]
|
237
|
+
end
|
238
|
+
|
239
|
+
# AAD fields are included in encryption but not encrypted themselves
|
240
|
+
doc = SecureDocument.new(
|
241
|
+
document_id: 'doc123',
|
242
|
+
owner_id: 'user456',
|
243
|
+
content: 'Sensitive document content',
|
244
|
+
created_at: Time.now.to_i
|
245
|
+
)
|
246
|
+
|
247
|
+
doc.save
|
248
|
+
|
249
|
+
# If someone modifies document_id or owner_id, decryption will fail
|
250
|
+
# This prevents attacks where encrypted data is moved between records
|
251
|
+
```
|
252
|
+
|
253
|
+
> **🛡️ Security Enhancement**
|
254
|
+
>
|
255
|
+
> AAD prevents encrypted fields from being moved between objects. Use it for high-security scenarios where data integrity is critical.
|
256
|
+
|
257
|
+
### Per-Field Provider Selection
|
258
|
+
|
259
|
+
```ruby
|
260
|
+
class MultiAlgorithmVault < Familia::Horreum
|
261
|
+
feature :encrypted_fields
|
262
|
+
|
263
|
+
# Use best available algorithm (XChaCha20-Poly1305 preferred)
|
264
|
+
encrypted_field :general_secret
|
265
|
+
|
266
|
+
# Force AES-GCM for compliance requirements
|
267
|
+
encrypted_field :compliance_data, provider: :aes_gcm
|
268
|
+
|
269
|
+
# High-security field with AAD
|
270
|
+
encrypted_field :ultra_secure,
|
271
|
+
provider: :xchacha20_poly1305,
|
272
|
+
aad_fields: [:vault_id, :owner_id]
|
273
|
+
end
|
274
|
+
```
|
275
|
+
|
276
|
+
## ConcealedString Security
|
277
|
+
|
278
|
+
Encrypted fields return `ConcealedString` objects to prevent accidental exposure:
|
279
|
+
|
280
|
+
### Safe Handling
|
281
|
+
|
282
|
+
```ruby
|
283
|
+
class User < Familia::Horreum
|
284
|
+
feature :encrypted_fields
|
285
|
+
encrypted_field :api_key
|
286
|
+
end
|
287
|
+
|
288
|
+
user = User.create(api_key: "sk-1234567890abcdef")
|
289
|
+
|
290
|
+
# Safe operations (won't expose actual value)
|
291
|
+
puts user.api_key.to_s # => "[CONCEALED]"
|
292
|
+
puts user.api_key.inspect # => "[CONCEALED]"
|
293
|
+
logger.info("User key: #{user.api_key}") # => "User key: [CONCEALED]"
|
294
|
+
|
295
|
+
# JSON serialization safety
|
296
|
+
user_json = user.to_json
|
297
|
+
# All encrypted fields appear as "[CONCEALED]" in JSON
|
298
|
+
|
299
|
+
# Explicit access when needed
|
300
|
+
actual_key = user.api_key.reveal # => "sk-1234567890abcdef"
|
301
|
+
```
|
302
|
+
|
303
|
+
### String Operations
|
304
|
+
|
305
|
+
```ruby
|
306
|
+
api_key = user.api_key
|
307
|
+
|
308
|
+
# Length operations work on concealed representation
|
309
|
+
api_key.length # => 11 ("[CONCEALED]".length)
|
310
|
+
api_key.size # => 11
|
311
|
+
|
312
|
+
# Comparison operations
|
313
|
+
api_key == "[CONCEALED]" # => true
|
314
|
+
api_key.start_with?("[CONCEALED]") # => true
|
315
|
+
|
316
|
+
# Reveal for actual operations
|
317
|
+
actual_key = api_key.reveal
|
318
|
+
actual_key.length # => 17 (actual key length)
|
319
|
+
actual_key.start_with?("sk-") # => true
|
320
|
+
```
|
321
|
+
|
322
|
+
> **⚠️ Important**
|
323
|
+
>
|
324
|
+
> Always use `.reveal` explicitly when you need the actual value. This makes it obvious in code reviews where sensitive data is being accessed.
|
325
|
+
|
326
|
+
## Performance Optimization
|
327
|
+
|
328
|
+
### Request-Level Caching
|
329
|
+
|
330
|
+
For applications that perform many encryption operations:
|
331
|
+
|
332
|
+
```ruby
|
333
|
+
class BulkDataProcessor
|
334
|
+
def process_sensitive_batch(records)
|
335
|
+
# Enable key caching for the entire batch
|
336
|
+
Familia::Encryption.with_request_cache do
|
337
|
+
records.each do |record|
|
338
|
+
# Key derivation happens once per field type
|
339
|
+
record.encrypted_field1 = process_data(record.raw_data1)
|
340
|
+
record.encrypted_field2 = process_data(record.raw_data2)
|
341
|
+
record.save
|
342
|
+
end
|
343
|
+
end
|
344
|
+
# Cache automatically cleared at end of block
|
345
|
+
end
|
346
|
+
end
|
347
|
+
```
|
348
|
+
|
349
|
+
**Performance Improvements:**
|
350
|
+
- Key derivation: 1x per field type instead of per operation
|
351
|
+
- Typical improvement: 40-60% faster for batch operations
|
352
|
+
- Memory usage: Minimal (keys cached temporarily)
|
353
|
+
|
354
|
+
### Benchmarking Your Setup
|
355
|
+
|
356
|
+
```ruby
|
357
|
+
# Test encryption performance with your data
|
358
|
+
def benchmark_encryption
|
359
|
+
require 'benchmark'
|
360
|
+
|
361
|
+
test_data = {
|
362
|
+
small: "x" * 100,
|
363
|
+
medium: "x" * 1000,
|
364
|
+
large: "x" * 10000
|
365
|
+
}
|
366
|
+
|
367
|
+
Benchmark.bm(15) do |x|
|
368
|
+
test_data.each do |size, data|
|
369
|
+
x.report("#{size} (#{data.length}b)") do
|
370
|
+
1000.times do
|
371
|
+
TestModel.create(encrypted_field: data)
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
# Provider comparison
|
379
|
+
providers = Familia::Encryption.benchmark_providers(iterations: 1000)
|
380
|
+
providers.each do |name, stats|
|
381
|
+
puts "#{name}: #{stats[:ops_per_sec]} ops/sec"
|
382
|
+
end
|
383
|
+
```
|
384
|
+
|
385
|
+
### Memory Management
|
386
|
+
|
387
|
+
```ruby
|
388
|
+
class MemoryAwareModel < Familia::Horreum
|
389
|
+
feature :encrypted_fields
|
390
|
+
encrypted_field :large_data
|
391
|
+
|
392
|
+
def process_and_clear
|
393
|
+
# Process encrypted data
|
394
|
+
result = expensive_operation(large_data.reveal)
|
395
|
+
|
396
|
+
# Clear sensitive data from memory
|
397
|
+
clear_encrypted_fields!
|
398
|
+
|
399
|
+
result
|
400
|
+
end
|
401
|
+
|
402
|
+
def self.bulk_process_with_cleanup(ids)
|
403
|
+
ids.each_slice(100) do |batch|
|
404
|
+
objects = multiget(batch)
|
405
|
+
|
406
|
+
objects.each(&:process_and_clear)
|
407
|
+
|
408
|
+
# Force garbage collection periodically
|
409
|
+
GC.start if batch.first % 1000 == 0
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
```
|
414
|
+
|
415
|
+
## Key Rotation and Migration
|
416
|
+
|
417
|
+
### Planned Key Rotation
|
418
|
+
|
419
|
+
```ruby
|
420
|
+
# 1. Add new key to configuration
|
421
|
+
Familia.configure do |config|
|
422
|
+
config.encryption_keys = {
|
423
|
+
v1: ENV['OLD_KEY'],
|
424
|
+
v2: ENV['CURRENT_KEY'],
|
425
|
+
v3: ENV['NEW_KEY'] # Add new key
|
426
|
+
}
|
427
|
+
config.current_key_version = :v3 # Switch to new key
|
428
|
+
end
|
429
|
+
|
430
|
+
# 2. Deploy application (new data uses v3)
|
431
|
+
|
432
|
+
# 3. Migrate existing data
|
433
|
+
class KeyRotationTask
|
434
|
+
def self.rotate_all_encrypted_data
|
435
|
+
model_classes = [User, Document, Vault, SecretData]
|
436
|
+
|
437
|
+
model_classes.each do |model_class|
|
438
|
+
puts "Rotating keys for #{model_class.name}..."
|
439
|
+
|
440
|
+
model_class.all.each_slice(100) do |batch|
|
441
|
+
batch.each do |record|
|
442
|
+
begin
|
443
|
+
record.re_encrypt_fields!
|
444
|
+
record.save
|
445
|
+
rescue => e
|
446
|
+
puts "Failed to rotate #{record.identifier}: #{e.message}"
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
print "."
|
451
|
+
sleep 0.1 # Rate limiting
|
452
|
+
end
|
453
|
+
|
454
|
+
puts "\nCompleted #{model_class.name}"
|
455
|
+
end
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
# 4. Remove old key after migration
|
460
|
+
```
|
461
|
+
|
462
|
+
### Emergency Key Rotation
|
463
|
+
|
464
|
+
```ruby
|
465
|
+
# For compromised keys, rotate immediately
|
466
|
+
class EmergencyRotation
|
467
|
+
def self.emergency_key_rotation
|
468
|
+
# 1. Generate new key immediately
|
469
|
+
new_key = SecureRandom.base64(32)
|
470
|
+
|
471
|
+
# 2. Update configuration
|
472
|
+
Familia.configure do |config|
|
473
|
+
config.encryption_keys[:emergency] = new_key
|
474
|
+
config.current_key_version = :emergency
|
475
|
+
end
|
476
|
+
|
477
|
+
# 3. Re-encrypt all data immediately
|
478
|
+
KeyRotationTask.rotate_all_encrypted_data
|
479
|
+
|
480
|
+
# 4. Notify security team
|
481
|
+
SecurityNotifier.alert_key_rotated(reason: 'emergency')
|
482
|
+
end
|
483
|
+
end
|
484
|
+
```
|
485
|
+
|
486
|
+
## Error Handling and Debugging
|
487
|
+
|
488
|
+
### Common Configuration Errors
|
489
|
+
|
490
|
+
```ruby
|
491
|
+
begin
|
492
|
+
Familia::Encryption.validate_configuration!
|
493
|
+
rescue Familia::EncryptionError => e
|
494
|
+
case e.message
|
495
|
+
when /No encryption keys configured/
|
496
|
+
puts "Add encryption keys to Familia.configure block"
|
497
|
+
when /Invalid key format/
|
498
|
+
puts "Keys must be base64-encoded 32-byte strings"
|
499
|
+
when /Current key version not found/
|
500
|
+
puts "current_key_version must exist in encryption_keys"
|
501
|
+
else
|
502
|
+
puts "Configuration error: #{e.message}"
|
503
|
+
end
|
504
|
+
end
|
505
|
+
```
|
506
|
+
|
507
|
+
### Debugging Encryption Issues
|
508
|
+
|
509
|
+
```ruby
|
510
|
+
class DebugVault < Familia::Horreum
|
511
|
+
feature :encrypted_fields
|
512
|
+
encrypted_field :debug_data
|
513
|
+
|
514
|
+
def debug_encryption_status
|
515
|
+
status = {
|
516
|
+
feature_enabled: self.class.features_enabled.include?(:encrypted_fields),
|
517
|
+
field_encrypted: self.class.encrypted_field?(:debug_data),
|
518
|
+
data_encrypted: encrypted_data?,
|
519
|
+
fields_cleared: encrypted_fields_cleared?,
|
520
|
+
current_provider: Familia::Encryption.current_provider,
|
521
|
+
available_providers: Familia::Encryption.available_providers
|
522
|
+
}
|
523
|
+
|
524
|
+
puts JSON.pretty_generate(status)
|
525
|
+
status
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
# Debug individual field encryption
|
530
|
+
vault = DebugVault.new(debug_data: "test")
|
531
|
+
vault.save
|
532
|
+
|
533
|
+
field_status = vault.encrypted_fields_status
|
534
|
+
puts "Field status: #{field_status}"
|
535
|
+
# => {debug_data: {encrypted: true, key_version: :v2, provider: :xchacha20_poly1305}}
|
536
|
+
```
|
537
|
+
|
538
|
+
### Performance Debugging
|
539
|
+
|
540
|
+
```ruby
|
541
|
+
# Monitor encryption performance
|
542
|
+
class EncryptionMonitor
|
543
|
+
def self.monitor_encryption_calls
|
544
|
+
original_encrypt = Familia::Encryption.method(:encrypt)
|
545
|
+
|
546
|
+
call_count = 0
|
547
|
+
total_time = 0
|
548
|
+
|
549
|
+
Familia::Encryption.define_singleton_method(:encrypt) do |data, **opts|
|
550
|
+
start_time = Time.now
|
551
|
+
result = original_encrypt.call(data, **opts)
|
552
|
+
total_time += (Time.now - start_time)
|
553
|
+
call_count += 1
|
554
|
+
|
555
|
+
if call_count % 100 == 0
|
556
|
+
avg_time = (total_time / call_count * 1000).round(2)
|
557
|
+
puts "Encryption calls: #{call_count}, avg: #{avg_time}ms"
|
558
|
+
end
|
559
|
+
|
560
|
+
result
|
561
|
+
end
|
562
|
+
end
|
563
|
+
end
|
564
|
+
```
|
565
|
+
|
566
|
+
## Testing Strategies
|
567
|
+
|
568
|
+
### Test Configuration
|
569
|
+
|
570
|
+
```ruby
|
571
|
+
# test/test_helper.rb
|
572
|
+
require 'familia'
|
573
|
+
|
574
|
+
# Use predictable test keys
|
575
|
+
test_keys = {
|
576
|
+
v1: Base64.strict_encode64('a' * 32),
|
577
|
+
v2: Base64.strict_encode64('b' * 32)
|
578
|
+
}
|
579
|
+
|
580
|
+
Familia.configure do |config|
|
581
|
+
config.encryption_keys = test_keys
|
582
|
+
config.current_key_version = :v1
|
583
|
+
config.encryption_personalization = 'TestApp-Test'
|
584
|
+
end
|
585
|
+
|
586
|
+
# Validate test configuration
|
587
|
+
Familia::Encryption.validate_configuration!
|
588
|
+
```
|
589
|
+
|
590
|
+
### Testing Encrypted Fields
|
591
|
+
|
592
|
+
```ruby
|
593
|
+
# test/models/encrypted_model_test.rb
|
594
|
+
require 'test_helper'
|
595
|
+
|
596
|
+
class EncryptedModelTest < Minitest::Test
|
597
|
+
def setup
|
598
|
+
@model = EncryptedModel.new(
|
599
|
+
name: "Test Model",
|
600
|
+
secret_data: "sensitive information"
|
601
|
+
)
|
602
|
+
@model.save
|
603
|
+
end
|
604
|
+
|
605
|
+
def test_encryption_concealment
|
606
|
+
# Field should return ConcealedString
|
607
|
+
assert_instance_of Familia::Features::EncryptedFields::ConcealedString, @model.secret_data
|
608
|
+
|
609
|
+
# String representation should be concealed
|
610
|
+
assert_equal "[CONCEALED]", @model.secret_data.to_s
|
611
|
+
|
612
|
+
# Reveal should return actual value
|
613
|
+
assert_equal "sensitive information", @model.secret_data.reveal
|
614
|
+
end
|
615
|
+
|
616
|
+
def test_json_serialization_safety
|
617
|
+
json_data = @model.to_json
|
618
|
+
parsed = JSON.parse(json_data)
|
619
|
+
|
620
|
+
# Encrypted fields should be concealed in JSON
|
621
|
+
assert_equal "[CONCEALED]", parsed['secret_data']
|
622
|
+
|
623
|
+
# Regular fields should be normal
|
624
|
+
assert_equal "Test Model", parsed['name']
|
625
|
+
end
|
626
|
+
|
627
|
+
def test_encryption_persistence
|
628
|
+
# Reload from database
|
629
|
+
reloaded = EncryptedModel.load(@model.identifier)
|
630
|
+
|
631
|
+
# Should still be able to decrypt
|
632
|
+
assert_equal "sensitive information", reloaded.secret_data.reveal
|
633
|
+
end
|
634
|
+
|
635
|
+
def test_key_rotation
|
636
|
+
original_data = @model.secret_data.reveal
|
637
|
+
|
638
|
+
# Simulate key rotation
|
639
|
+
@model.re_encrypt_fields!
|
640
|
+
@model.save
|
641
|
+
|
642
|
+
# Should still decrypt to same value
|
643
|
+
reloaded = EncryptedModel.load(@model.identifier)
|
644
|
+
assert_equal original_data, reloaded.secret_data.reveal
|
645
|
+
end
|
646
|
+
end
|
647
|
+
```
|
648
|
+
|
649
|
+
### Mock Encryption for Fast Tests
|
650
|
+
|
651
|
+
```ruby
|
652
|
+
# test/support/mock_encryption.rb
|
653
|
+
module MockEncryption
|
654
|
+
def self.setup
|
655
|
+
# Replace encryption with reversible encoding for speed
|
656
|
+
Familia::Encryption.define_singleton_method(:encrypt) do |data, **opts|
|
657
|
+
Base64.strict_encode64("MOCK:#{data}")
|
658
|
+
end
|
659
|
+
|
660
|
+
Familia::Encryption.define_singleton_method(:decrypt) do |encrypted_data, **opts|
|
661
|
+
decoded = Base64.strict_decode64(encrypted_data)
|
662
|
+
decoded.sub(/^MOCK:/, '')
|
663
|
+
end
|
664
|
+
end
|
665
|
+
|
666
|
+
def self.teardown
|
667
|
+
# Restore original encryption methods
|
668
|
+
load 'familia/encryption.rb'
|
669
|
+
end
|
670
|
+
end
|
671
|
+
|
672
|
+
# Use in fast test suite
|
673
|
+
class FastEncryptedModelTest < Minitest::Test
|
674
|
+
def setup
|
675
|
+
MockEncryption.setup
|
676
|
+
end
|
677
|
+
|
678
|
+
def teardown
|
679
|
+
MockEncryption.teardown
|
680
|
+
end
|
681
|
+
|
682
|
+
# Tests run much faster with mock encryption
|
683
|
+
end
|
684
|
+
```
|
685
|
+
|
686
|
+
## Production Considerations
|
687
|
+
|
688
|
+
### Monitoring and Alerting
|
689
|
+
|
690
|
+
```ruby
|
691
|
+
# Monitor encryption health in production
|
692
|
+
class EncryptionHealthCheck
|
693
|
+
def self.check
|
694
|
+
results = {
|
695
|
+
configuration_valid: false,
|
696
|
+
providers_available: [],
|
697
|
+
key_versions_accessible: [],
|
698
|
+
sample_encrypt_decrypt: false
|
699
|
+
}
|
700
|
+
|
701
|
+
begin
|
702
|
+
# Test configuration
|
703
|
+
Familia::Encryption.validate_configuration!
|
704
|
+
results[:configuration_valid] = true
|
705
|
+
|
706
|
+
# Test providers
|
707
|
+
results[:providers_available] = Familia::Encryption.available_providers.map { |p| p[:name] }
|
708
|
+
|
709
|
+
# Test key access
|
710
|
+
Familia.config.encryption_keys.each do |version, key|
|
711
|
+
begin
|
712
|
+
# Test key derivation
|
713
|
+
Familia::Encryption.derive_key_for_field('test_field', version)
|
714
|
+
results[:key_versions_accessible] << version
|
715
|
+
rescue => e
|
716
|
+
puts "Key version #{version} error: #{e.message}"
|
717
|
+
end
|
718
|
+
end
|
719
|
+
|
720
|
+
# Test encrypt/decrypt cycle
|
721
|
+
test_data = "health_check_#{Time.now.to_i}"
|
722
|
+
encrypted = Familia::Encryption.encrypt(test_data)
|
723
|
+
decrypted = Familia::Encryption.decrypt(encrypted)
|
724
|
+
results[:sample_encrypt_decrypt] = (decrypted == test_data)
|
725
|
+
|
726
|
+
rescue => e
|
727
|
+
results[:error] = e.message
|
728
|
+
end
|
729
|
+
|
730
|
+
results
|
731
|
+
end
|
732
|
+
end
|
733
|
+
|
734
|
+
# Set up monitoring
|
735
|
+
# Nagios, DataDog, or other monitoring
|
736
|
+
results = EncryptionHealthCheck.check
|
737
|
+
if results[:configuration_valid] && results[:sample_encrypt_decrypt]
|
738
|
+
exit 0 # OK
|
739
|
+
else
|
740
|
+
puts "Encryption health check failed: #{results}"
|
741
|
+
exit 2 # Critical
|
742
|
+
end
|
743
|
+
```
|
744
|
+
|
745
|
+
### Backup and Recovery
|
746
|
+
|
747
|
+
```ruby
|
748
|
+
# Backup encryption keys securely
|
749
|
+
class EncryptionKeyBackup
|
750
|
+
def self.backup_keys_to_vault
|
751
|
+
keys = Familia.config.encryption_keys
|
752
|
+
|
753
|
+
keys.each do |version, key|
|
754
|
+
# Store in HashiCorp Vault, AWS KMS, etc.
|
755
|
+
VaultClient.store_secret(
|
756
|
+
path: "familia/encryption_keys/#{version}",
|
757
|
+
data: { key: key },
|
758
|
+
lease_duration: '8760h' # 1 year
|
759
|
+
)
|
760
|
+
end
|
761
|
+
end
|
762
|
+
|
763
|
+
def self.restore_keys_from_vault
|
764
|
+
versions = VaultClient.list_secrets('familia/encryption_keys/')
|
765
|
+
|
766
|
+
restored_keys = {}
|
767
|
+
versions.each do |version|
|
768
|
+
secret = VaultClient.read_secret("familia/encryption_keys/#{version}")
|
769
|
+
restored_keys[version.to_sym] = secret['key']
|
770
|
+
end
|
771
|
+
|
772
|
+
restored_keys
|
773
|
+
end
|
774
|
+
end
|
775
|
+
```
|
776
|
+
|
777
|
+
---
|
778
|
+
|
779
|
+
## See Also
|
780
|
+
|
781
|
+
- **[Overview](../overview.md#encrypted-fields)** - Conceptual introduction to encrypted fields
|
782
|
+
- **[Technical Reference](../reference/api-technical.md#encrypted-fields-feature-v200-pre5)** - Implementation details and advanced patterns
|
783
|
+
- **[Security Model Guide](security-model.md)** - Cryptographic design and threat model considerations
|
784
|
+
- **[Feature System Guide](feature-system.md)** - Understanding Familia's feature architecture
|
785
|
+
- **[Implementation Guide](implementation.md)** - Production deployment and configuration patterns
|