Skip to content

efs: add encrypted file system store - #1

Merged
franchb merged 3 commits into
masterfrom
encrypted-fs
Sep 30, 2025
Merged

efs: add encrypted file system store#1
franchb merged 3 commits into
masterfrom
encrypted-fs

Conversation

@franchb

@franchb franchb commented Sep 30, 2025

Copy link
Copy Markdown
Collaborator

Add a keystore implementation, that wraps a file system store, but encrypts the files.

Fixes: minio#392

Cherry-pick: minio#489

Summary by CodeRabbit

  • New Features

    • Added an encrypted filesystem-backed keystore option for at-rest encrypted key storage, with configurable master key path, cipher, and storage directory.
  • Documentation

    • Updated example server configuration to include the encrypted filesystem keystore block.
  • Tests

    • Added config and integration tests covering EncryptedFS setup, CRUD, encryption behavior, backward compatibility, and invalid/missing master-key cases.

Add a keystore implementation, that wraps a file system store, but encrypts the files.

Fixes: minio#392

Signed-off-by: Sascha Wolke <dersascha@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Sep 30, 2025

Copy link
Copy Markdown

Walkthrough

Adds an encrypted filesystem-backed keystore (EFS) implemented at internal/keystore/efs/efs.go, integrates EncryptedFS into configuration and connection codepaths, exposes a Dir() accessor on the FS store, and adds tests and YAML testdata exercising EFS behavior and error cases.

Changes

Cohort / File(s) Summary
Encrypted FS keystore implementation
internal/keystore/efs/efs.go
New EFS Store, NewStore, loadMasterKey, and methods: String, Status, Create (encrypt), Get (decrypt), Delete, List, Close. Validates master key size/type and FIPS constraints; delegates persistence to underlying FS store.
FS store accessor
internal/keystore/fs/fs.go
Adds func (s *Store) Dir() string getter exposing configured directory.
Config: type and wiring
kesconf/file.go
Adds EncryptedFSKeyStore type with MasterKeyPath, MasterKeyCipher, Path and Connect that constructs the EFS store via efs.NewStore.
Config: YAML parsing & validation
kesconf/config.go
Extends KeyStore config with encryptedfs block (masterKeyPath, masterKeyCipher, path); validates presence and exclusivity and constructs EncryptedFSKeyStore.
Config tests
kesconf/config_test.go
Adds TestReadServerConfigYAML_EncryptedFS asserting parsed keystore type and fields from test YAML.
EFS integration and behavior tests
kesconf/efs_test.go
New test suite covering initialization, Create/Get/Status/List, backward compatibility, on-disk encryption checks, encryption context handling, and negative cases (missing/invalid key, unknown cipher).
Test data
kesconf/testdata/efs.yml
Adds YAML test config with keystore.encryptedfs fields for tests.
Sample server config
server-config.yaml
Adds keystore.encryptedfs example block (masterKeyPath, masterKeyCipher, path).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant App
  participant EFS as "EFS Store"
  participant FS as "FS Store"
  participant KeyFile as "Key File"

  rect rgb(240,245,255)
  note over App,EFS: Initialization
  App->>EFS: NewStore(keyPath, keyCipher, dir)
  EFS->>KeyFile: loadMasterKey()
  KeyFile-->>EFS: master key (validated)
  EFS->>FS: init underlying store (ensure dirs)
  EFS-->>App: Store
  end

  rect rgb(245,255,245)
  note over App,EFS: Create (encrypt → write)
  App->>EFS: Create(name, value)
  EFS->>EFS: Encrypt(value, context=name, key)
  EFS->>FS: Write(name, ciphertext)
  FS-->>EFS: ok/error
  EFS-->>App: ok/error
  end

  rect rgb(255,250,240)
  note over App,EFS: Get (read → decrypt)
  App->>EFS: Get(name)
  EFS->>FS: Read(name)
  FS-->>EFS: ciphertext
  EFS->>EFS: Decrypt(ciphertext, context=name, key)
  EFS-->>App: value/error
  end
Loading
sequenceDiagram
  autonumber
  participant App
  participant EFS as "EFS Store"
  participant FS as "FS Store"

  note over App,EFS: List/Delete delegate to FS
  App->>EFS: List(prefix, n)
  EFS->>FS: List(prefix, n)
  FS-->>EFS: names, token
  EFS-->>App: names, token

  App->>EFS: Delete(name)
  EFS->>FS: Delete(name)
  FS-->>EFS: ok/error
  EFS-->>App: ok/error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

I nibble on keys in a moonlit den,
I wrap each secret with whiskered pen.
I hop, I hide, I guard the stash,
Ciphered carrots in a tidy cache.
Thump — encrypted, listed, safe again. 🥕🔐

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “efs: add encrypted file system store” clearly summarizes the primary change of introducing an encrypted filesystem-backed keystore, using concise and module-prefixed phrasing that makes the feature addition immediately understandable.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch encrypted-fs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🧪 Early access (Sonnet 4.5): enabled

We are currently testing the Sonnet 4.5 model, which is expected to improve code review quality. However, this model may lead to increased noise levels in the review comments. Please disable the early access features if the noise level causes any inconvenience.

Note:

  • Public repositories are always opted into early access features.
  • You can enable or disable early access features from the CodeRabbit UI or by updating the CodeRabbit configuration file.

Comment @coderabbitai help to get the list of available commands and usage tips.

@franchb franchb self-assigned this Sep 30, 2025

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
kesconf/testdata/efs.yml (1)

9-10: Fix YAML formatting: remove extra spaces after colons.

Lines 9-10 have extra spaces after the colons. While this doesn't break YAML parsing, it violates YAML style conventions.

Apply this diff:

 tls:
-  key:      ./server.key  
-  cert:     ./server.cert  
+  key: ./server.key
+  cert: ./server.cert
internal/keystore/efs/efs.go (1)

54-85: Redundant file close operation.

The file is closed both via defer file.Close() (line 62) and explicitly at line 69. While harmless (Close is idempotent), the explicit close is unnecessary.

Consider removing the explicit close for cleaner code:

 	if err != nil {
 		return crypto.SecretKey{}, err
 	}
-	if err = file.Close(); err != nil {
-		return crypto.SecretKey{}, err
-	}
 	if len(value) != crypto.SecretKeySize {
kesconf/efs_test.go (1)

24-57: Master key file cleanup missing.

The master key file created at line 32 is never cleaned up after the test completes. Consider adding a defer statement to remove it.

Apply this diff:

 	masterKeyPath := filepath.Join(*EncryptedFSPath, "test-master-key")
 	masterKeyCipher := "AES256"
 	if err := os.WriteFile(masterKeyPath, []byte(masterKey), 0o644); err != nil {
 		t.Fatalf("Failed to write master key into test dir")
 	}
+	defer os.Remove(masterKeyPath)
 
 	config := kesconf.EncryptedFSKeyStore{
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63649d0 and 62273fd.

📒 Files selected for processing (8)
  • internal/keystore/efs/efs.go (1 hunks)
  • internal/keystore/fs/fs.go (1 hunks)
  • kesconf/config.go (2 hunks)
  • kesconf/config_test.go (1 hunks)
  • kesconf/efs_test.go (1 hunks)
  • kesconf/file.go (2 hunks)
  • kesconf/testdata/efs.yml (1 hunks)
  • server-config.yaml (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
kesconf/file.go (3)
kesconf/config_test.go (2)
  • MasterKeyPath (36-36)
  • MasterKeyCipher (37-37)
internal/keystore/efs/efs.go (1)
  • NewStore (35-47)
internal/keystore/fs/fs.go (1)
  • NewStore (34-48)
kesconf/config_test.go (1)
kesconf/file.go (3)
  • ReadFile (38-50)
  • KeyStore (374-378)
  • EncryptedFSKeyStore (402-413)
kesconf/config.go (2)
kesconf/config_test.go (2)
  • MasterKeyPath (36-36)
  • MasterKeyCipher (37-37)
kesconf/file.go (2)
  • KeyStore (374-378)
  • EncryptedFSKeyStore (402-413)
internal/keystore/fs/fs.go (1)
internal/keystore/efs/efs.go (1)
  • Store (92-95)
internal/keystore/efs/efs.go (4)
internal/keystore/fs/fs.go (3)
  • NewStore (34-48)
  • Store (55-58)
  • MaxSize (105-105)
internal/crypto/key.go (4)
  • SecretKeySize (32-32)
  • ParseSecretKeyType (58-67)
  • ChaCha20 (45-45)
  • NewSecretKey (216-226)
internal/fips/api.go (1)
  • Enabled (15-15)
keystore.go (1)
  • KeyStoreState (66-68)
kesconf/efs_test.go (2)
kesconf/file.go (2)
  • EncryptedFSKeyStore (402-413)
  • KeyStore (374-378)
kesconf/edge_test.go (1)
  • RandString (153-159)
🪛 YAMLlint (1.37.1)
kesconf/testdata/efs.yml

[warning] 9-9: too many spaces after colon

(colons)


[warning] 10-10: too many spaces after colon

(colons)

🔇 Additional comments (18)
internal/keystore/fs/fs.go (1)

62-63: LGTM! Clean accessor for directory inspection.

The Dir() method provides safe read-only access to the underlying directory path, which is useful for introspection by wrapper types like the encrypted filesystem store.

kesconf/config_test.go (1)

33-60: LGTM! Test follows established patterns and validates all key fields.

The test correctly mirrors the existing TestReadServerConfigYAML_FS pattern, validates the type assertion, and checks all three configuration fields (MasterKeyPath, MasterKeyCipher, Path) with clear error messages.

kesconf/file.go (2)

25-25: LGTM! Import added for encrypted filesystem keystore.

The import for the internal/keystore/efs package is correctly placed and necessary for the new EncryptedFSKeyStore functionality.


415-418: Resolved: validation already handled
fs.NewStore creates or verifies the directory and errors if it’s not a directory, and loadMasterKey checks that the key file exists, is readable, matches the expected size, and that the cipher is supported (including FIPS restrictions).

kesconf/config.go (1)

79-83: LGTM! Configuration struct follows established patterns.

The EncryptedFS configuration struct is well-defined with appropriate YAML tags and follows the same pattern as other keystore configurations in this file.

internal/keystore/efs/efs.go (5)

35-47: LGTM! Constructor follows best practices.

The NewStore constructor correctly initializes the filesystem store first, then loads the master key, with proper error propagation throughout.


92-95: LGTM! Store struct is well-designed.

The Store structure cleanly encapsulates the encryption key and the underlying filesystem store.


97-105: LGTM! Informational methods correctly delegate.

The String and Status methods appropriately delegate to the underlying filesystem store.


111-137: LGTM! Encryption with authenticated context prevents key swapping.

The Create and Get methods correctly use an authenticated encryption context (name=%s) that binds the ciphertext to the key name, preventing attackers from copying encrypted keys between files. This is properly tested in the EncryptionContext test.


142-165: LGTM! Delegation methods are correctly implemented.

Delete, List, and Close appropriately delegate to the underlying filesystem store since these operations work with filenames/metadata rather than encrypted content.

kesconf/efs_test.go (8)

60-115: LGTM! Comprehensive CRUD test coverage.

The efsTestList function provides thorough testing of all CRUD operations with proper validation of empty states before and after operations.


118-140: LGTM! Backward compatibility test ensures format stability.

This test validates that the encryption format remains stable by decrypting a known ciphertext, which is critical for ensuring existing encrypted keys remain readable after code changes.


143-162: LGTM! Basic encryption verification test.

This test provides a sanity check that keys are actually encrypted on disk rather than stored in plaintext.


165-191: LGTM! Critical security test for authenticated encryption context.

This test validates that the encryption context (key name binding) prevents key swapping attacks by verifying that copying an encrypted key to a different filename makes it unreadable.


194-214: LGTM! Negative test for missing master key.

This test correctly validates that initialization fails when the master key file is missing.


217-276: LGTM! Invalid key length tests with proper cleanup.

These tests correctly validate that both undersized and oversized master keys are rejected during initialization, with proper resource cleanup.


279-307: LGTM! Unknown cipher validation test.

This test correctly validates that an unknown cipher type is rejected during initialization, with proper cleanup.


50-51: Ensure correct package for accessing ranStringLength.
ranStringLength is defined in edge_test.go as an unexported constant, so it’s only visible if efs_test.go declares package kesconf. Confirm the package declaration at the top of efs_test.go; if it’s package kesconf_test, either switch it to package kesconf or define ranStringLength locally.

Comment thread kesconf/config.go Outdated
Comment thread kesconf/file.go
Comment thread kesconf/testdata/efs.yml
Comment thread server-config.yaml
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Eliah Rusin <hello@franchb.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
kesconf/file.go (1)

398-413: Grammar issue resolved; consider refining the description.

The grammar issue flagged in the previous review has been correctly fixed (line 401 now uses "An"). However, the struct comment on line 399 describes this as "a simple filesystem keystore," which may be misleading since EncryptedFSKeyStore wraps the filesystem store with encryption, making it more complex than the base FSKeyStore.

Consider updating line 399 to better distinguish this from FSKeyStore:

-// EncryptedFSKeyStore is a structure containing the configuration
-// for a simple filesystem keystore.
+// EncryptedFSKeyStore is a structure containing the configuration
+// for an encrypted filesystem keystore.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 62273fd and bbd8869.

📒 Files selected for processing (3)
  • kesconf/config.go (2 hunks)
  • kesconf/file.go (2 hunks)
  • server-config.yaml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • kesconf/config.go
  • server-config.yaml
🧰 Additional context used
🧬 Code graph analysis (1)
kesconf/file.go (3)
kesconf/config_test.go (2)
  • MasterKeyPath (36-36)
  • MasterKeyCipher (37-37)
internal/keystore/efs/efs.go (1)
  • NewStore (35-47)
internal/keystore/fs/fs.go (1)
  • NewStore (34-48)
🔇 Additional comments (1)
kesconf/file.go (1)

25-25: LGTM!

The import is necessary for the new EncryptedFSKeyStore functionality and follows the existing import organization pattern.

Comment thread kesconf/file.go Outdated
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Eliah Rusin <hello@franchb.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
kesconf/file.go (1)

398-399: Consider clarifying the comment to distinguish from FSKeyStore.

The comment on line 399 says "for a simple filesystem keystore" which is identical to the FSKeyStore comment above (line 381). Consider updating it to "for an encrypted filesystem keystore" to clearly distinguish this from the unencrypted FSKeyStore.

Apply this diff:

 // EncryptedFSKeyStore is a structure containing the configuration
-// for a simple filesystem keystore.
+// for an encrypted filesystem keystore.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bbd8869 and f419b58.

📒 Files selected for processing (1)
  • kesconf/file.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
kesconf/file.go (4)
kesconf/config_test.go (2)
  • MasterKeyPath (36-36)
  • MasterKeyCipher (37-37)
keystore.go (1)
  • KeyStore (30-62)
internal/keystore/fs/fs.go (1)
  • NewStore (34-48)
internal/keystore/efs/efs.go (1)
  • NewStore (35-47)
🔇 Additional comments (1)
kesconf/file.go (1)

25-25: LGTM!

The import is correctly placed and necessary for the new EncryptedFSKeyStore functionality.

Comment thread kesconf/file.go
@franchb
franchb merged commit 16db567 into master Sep 30, 2025
1 check passed
@franchb
franchb deleted the encrypted-fs branch September 30, 2025 12:09
@coderabbitai coderabbitai Bot mentioned this pull request Nov 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Encrypting files at rest for the filesystem target

2 participants