Aug 21 - Fernet key rotation

This commit is contained in:
2026-08-21 10:18:22 -04:00
parent cb7c244872
commit d04190ba09
5 changed files with 578 additions and 15 deletions
+22 -1
View File
@@ -95,6 +95,10 @@ class Tenant(ControlBase):
db_name = Column(String(64), unique=True, nullable=False)
db_user = Column(String(64), nullable=False)
db_password_enc = Column(Text, nullable=True) # Fernet token
# MT-23: which Fernet key version db_password_enc was written under, so
# rotation progress is queryable rather than assumed:
# SELECT COUNT(*) FROM tenants WHERE key_version < <current>;
key_version = Column(Integer, nullable=False, default=1, server_default='1')
alembic_head = Column(String(64), nullable=True) # last tenant-schema rev applied
created_at = Column(DateTime, nullable=False, default=now_eastern)
@@ -124,8 +128,25 @@ class Tenant(ControlBase):
# ── Encrypted credential handling ──────────────────────────────────────
def set_db_password(self, plaintext):
"""Store an encrypted tenant DB password."""
"""Store an encrypted tenant DB password, stamped with the key version."""
self.db_password_enc = crypto.encrypt(plaintext)
self.key_version = crypto.current_key_version()
def rotate_db_password_key(self):
"""MT-23: re-encrypt the stored credential under the primary key.
Returns True when the row was rewritten, False when there was nothing
stored. The plaintext is never materialized here — MultiFernet.rotate
decrypts and re-encrypts internally.
"""
if not self.db_password_enc:
# Nothing to re-encrypt, but stamp the version so the row stops
# showing up as outstanding in rotation progress queries.
self.key_version = crypto.current_key_version()
return False
self.db_password_enc = crypto.rotate(self.db_password_enc)
self.key_version = crypto.current_key_version()
return True
@property
def db_password(self):