Skip to content

Conversation

@minwoox
Copy link
Contributor

@minwoox minwoox commented Nov 14, 2025

Motivation:
Central Dogma needs to support key rotation for both wrapped data encryption keys (WDEKs) and session master keys to maintain security best practices.

Modifications:

  • Renamed KeyManagementService to KeyWrapper
  • Introduced KeyManagementService for key APIs
    • Implemented WDEK rotation logic that generates new wrapped keys with incremented versions
    • Implemented session master key rotation with version increment and concurrency protection
  • Splitted DefaultEncryptionStorageManager into rocksDbStorage, sessionKeyStorage and repositoryEncryptionStorage.

Result:

  • System administrators can now rotate DEKs and session master key

Summary by CodeRabbit

Release Notes

  • New Features

    • Added key management REST API for system administrators to manage encryption keys, including WDEK and session master key rotation endpoints.
    • Added support for Key Encryption Key (KEK) identifiers in encryption-at-rest configuration.
    • Enhanced session key management with versioning and rotation capabilities.
  • Bug Fixes & Improvements

    • Improved encryption session handling with asynchronous key retrieval and version tracking.

@coderabbitai
Copy link

coderabbitai bot commented Nov 14, 2025

Walkthrough

This PR introduces Key Encryption Key (KEK) management with support for encrypted data key rotation. It adds KEK ID configuration, new command types for key rotation, a REST API for key management (sysadmin scope), and refactors the encryption storage layer with improved session key lifecycle handling and async session management in authentication flows.

Changes

Cohort / File(s) Change Summary
Configuration & Type System
server/src/main/java/com/linecorp/centraldogma/server/EncryptionAtRestConfig.java, server/src/main/java/com/linecorp/centraldogma/server/auth/SessionMasterKey.java, server/src/main/java/com/linecorp/centraldogma/server/command/CommandType.java, server/src/main/java/com/linecorp/centraldogma/server/internal/api/sysadmin/SessionMasterKeyDto.java
Added KEK ID field to EncryptionAtRestConfig; refactored SessionMasterKey to use String-based wrapped keys and added KEK ID + creation timestamp fields; added ROTATE_WDEK and ROTATE_SESSION_MASTER_KEY command type enums; introduced new SessionMasterKeyDto for REST responses.
Session Key & Authentication
server/src/main/java/com/linecorp/centraldogma/server/auth/SessionKey.java, server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/AuthSessionService.java, server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionCookieHandler.java, server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionUtil.java, server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionCookieAuthorizer.java
Refactored SessionKey to accept SessionMasterKey and expose JWS/JWE cryptographic components; switched session handling to async flows in cookie/auth handlers; added session key versioning and listener-based updates in SessionCookieHandler.
Command & Execution
server/src/main/java/com/linecorp/centraldogma/server/command/Command.java, server/src/main/java/com/linecorp/centraldogma/server/command/CreateProjectCommand.java, server/src/main/java/com/linecorp/centraldogma/server/command/CreateRepositoryCommand.java, server/src/main/java/com/linecorp/centraldogma/server/command/MigrateToEncryptedRepositoryCommand.java, server/src/main/java/com/linecorp/centraldogma/server/command/RotateWdekCommand.java, server/src/main/java/com/linecorp/centraldogma/server/command/RotateSessionMasterKeyCommand.java, server/src/main/java/com/linecorp/centraldogma/server/command/StandaloneCommandExecutor.java
Introduced WrappedDekDetails to encapsulate WDEK metadata; added new rotation commands; updated existing commands to use WrappedDekDetails instead of raw byte arrays; added dispatch handlers for rotation commands in executor.
Encryption Storage Layer
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/EncryptionStorageManager.java, server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/DefaultEncryptionStorageManager.java, server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/NoopEncryptionStorageManager.java, server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/RocksDBStorage.java, server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/RepositoryEncryptionStorage.java, server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/SessionKeyStorage.java
Refactored encryption storage with KEK support; introduced wrapper classes (RocksDBStorage, RepositoryEncryptionStorage, SessionKeyStorage) for cleaner separation of concerns; added session key rotation, versioning, and listener pattern; changed WDEK generation to return String (Base64-encoded).
Key Wrapper Interface
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/KeyWrapper.java, server/src/test/resources/META-INF/services/com.linecorp.centraldogma.server.storage.encryption.KeyWrapper, server/src/test/java/com/linecorp/centraldogma/server/storage/encryption/TestKeyWrapper.java, webapp/javaTest/java/com/linecorp/centraldogma/webapp/TestKeyWrapper.java, webapp/javaTest/resources/META-INF/services/com.linecorp.centraldogma.server.storage.encryption.KeyWrapper, webapp/javaTest/resources/META-INF/services/com.linecorp.centraldogma.server.storage.encryption.KeyManagementService
Renamed KeyManagementService to KeyWrapper; updated wrap/unwrap signatures to accept KEK ID and return/accept Base64-encoded strings; added rewrap method for re-encryption with different KEK; updated ServiceLoader configurations.
New REST API & Data Models
server/src/main/java/com/linecorp/centraldogma/server/internal/api/sysadmin/KeyManagementService.java, server/src/main/java/com/linecorp/centraldogma/server/internal/api/sysadmin/ServerStatusService.java, server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java, server/src/main/java/com/linecorp/centraldogma/server/CentralDogma.java
Introduced new KeyManagementService REST controller (sysadmin-only) with endpoints for listing WDEKs and rotating session/repository keys; created WrappedDekDetails data model; added conditional binding of KeyManagementService when encryption enabled; applied class-level @RequiresSystemAdministrator to ServerStatusService.
API Layer Integration
server/src/main/java/com/linecorp/centraldogma/server/internal/api/RepositoryServiceUtil.java, server/src/main/java/com/linecorp/centraldogma/server/internal/api/RepositoryServiceV1.java, server/src/main/java/com/linecorp/centraldogma/server/internal/storage/project/ProjectApiManager.java, server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/git/rocksdb/EncryptionGitStorage.java
Updated repository creation and migration flows to use WrappedDekDetails; refactored EncryptionGitStorage to fetch DEKs on-demand from storage manager instead of using in-memory caches.
Logout & User Service
server/src/main/java/com/linecorp/centraldogma/server/internal/admin/service/DefaultLogoutService.java, server/src/main/java/com/linecorp/centraldogma/server/internal/admin/service/UserService.java
Converted to async request handling flows for session retrieval and validation; refactored CSRF validation to work within async composition chains.
Testing Infrastructure
testing/common/src/main/java/com/linecorp/centraldogma/testing/internal/CentralDogmaRuleDelegate.java, testing/junit/src/main/java/com/linecorp/centraldogma/testing/junit/CentralDogmaExtension.java, testing/junit4/src/main/java/com/linecorp/centraldogma/testing/junit4/CentralDogmaRule.java
Added encryptionStorageManager() accessor methods to test rule/extension classes.
Tests
server/src/test/java/com/linecorp/centraldogma/server/auth/SessionKeyTest.java, server/src/test/java/com/linecorp/centraldogma/server/internal/admin/auth/AuthServerTest.java, server/src/test/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionUtilTest.java, server/src/test/java/com/linecorp/centraldogma/server/internal/api/MigrateToEncryptedRepositoryTest.java, server/src/test/java/com/linecorp/centraldogma/server/internal/api/sysadmin/KeyManagementServiceTest.java, server/src/test/java/com/linecorp/centraldogma/server/internal/storage/repository/git/EncryptedGitRepositoryRemoveTest.java, server/src/test/java/com/linecorp/centraldogma/server/internal/storage/repository/git/GitRepositoryTest.java, server/src/test/java/com/linecorp/centraldogma/server/internal/storage/repository/git/rocksdb/EncryptionGitStorageTest.java, server/src/test/java/com/linecorp/centraldogma/server/storage/encryption/DefaultEncryptionStorageManagerTest.java
Updated tests to use new KEK ID parameters, WrappedDekDetails, and Base64-encoded WDEK handling; added comprehensive test suite for WDEK and session master key rotation flows.
Server Initialization
server/src/main/java/com/linecorp/centraldogma/server/storage/project/InternalProjectInitializer.java, webapp/javaTest/java/com/linecorp/centraldogma/webapp/SamlCentralDogmaTestServer.java
Updated session master key generation calls to include explicit version parameter; updated encryption config initialization to include KEK ID.

Sequence Diagram(s)

sequenceDiagram
    actor Admin as System Administrator
    participant API as KeyManagementService (REST)
    participant Executor as StandaloneCommandExecutor
    participant Storage as EncryptionStorageManager
    participant KeyStorage as SessionKeyStorage
    participant Cache as SessionKeyCache

    Admin->>API: POST /masterkeys/session/rotate
    API->>API: Verify encryption enabled
    API->>Storage: getCurrentSessionMasterKey()
    Storage->>KeyStorage: getCurrentSessionMasterKey()
    KeyStorage->>Cache: fetch current version
    KeyStorage-->>Storage: SessionMasterKey(version=N)
    Storage-->>API: SessionMasterKey(version=N)
    API->>API: New version = N + 1
    API->>API: Generate new SessionMasterKey(N+1)
    API->>Executor: rotateSessionMasterKey(newKey)
    Executor->>Storage: rotateSessionMasterKey(newKey)
    Storage->>KeyStorage: Store new version
    KeyStorage->>Cache: Update currentSessionKey
    KeyStorage-->>Cache: Notify listeners
    Cache->>Cache: SessionKey derived from newKey
    Storage-->>Executor: ✓ Complete
    Executor-->>API: ✓ 200 OK
    API-->>Admin: Success
Loading
sequenceDiagram
    actor User as User
    participant Auth as AuthSessionService
    participant Handler as SessionCookieHandler
    participant KeyStorage as SessionKeyStorage
    participant Verifier as JWTVerifier
    participant Encrypter as JWEEncrypter

    User->>Auth: Login Request
    Auth->>Handler: getSessionInfo(ctx)
    Handler->>Handler: Check if encryption enabled
    Handler->>KeyStorage: getCurrentSessionKey()
    KeyStorage->>KeyStorage: Fetch current version key
    KeyStorage-->>Handler: SessionKey(version=N, signer, encrypter)
    Handler-->>Auth: SessionInfo ready
    Auth->>Auth: Create JWT with sessionKey.signer()
    Auth->>Auth: Encrypt session cookie with sessionKey.encrypter()
    Auth-->>User: JWT + Encrypted Cookie(version=N)

    User->>Auth: Later Request with Encrypted Cookie
    Auth->>Handler: getSessionInfo(ctx)
    Handler->>Handler: Extract version N from JWE header
    Handler->>KeyStorage: getSessionKey(N)
    KeyStorage->>KeyStorage: Fetch versioned key
    KeyStorage-->>Handler: SessionKey(version=N, decrypter, verifier)
    Handler->>Verifier: Verify JWT signature
    Handler->>Encrypter: Decrypt session cookie
    Handler-->>Auth: SessionInfo restored
    Auth-->>User: ✓ Authorized
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Areas requiring extra attention:

  • Async flow changes in session/auth handlers (SessionCookieHandler, SessionCookieAuthorizer, DefaultLogoutService, UserService) — complex async composition chains with error handling paths may have race conditions or missing edge cases around concurrent key rotation
  • Storage layer refactoring (DefaultEncryptionStorageManager, RepositoryEncryptionStorage, SessionKeyStorage) — new classes with intricate DEK/WDEK versioning, caching, and concurrent access patterns; verify thread-safety and correctness of listener notifications
  • SessionKey and SessionMasterKey changes — refactored to accept new parameter types; verify all call sites propagate the correct object structure and that Base64 encoding/decoding is consistent
  • KeyWrapper interface migration — renamed from KeyManagementService; ensure all ServiceLoader entries and implementations are consistent; verify wrap/unwrap/rewrap logic handles KEK ID transitions correctly
  • Command execution for rotation — new RotateWdekCommand and RotateSessionMasterKeyCommand dispatch paths should be verified against state machine constraints and rollback scenarios
  • Crypto component instantiation in SessionKey — try-catch wrapping around JWS/JWE component creation; verify that failures are properly handled and that partial initialization doesn't leave dangling resources

Possibly related PRs

Suggested labels

new feature, encryption, key-management

Suggested reviewers

  • trustin
  • jrhee17
  • ikhoon

Poem

🔐 A rabbit's ode to encrypted keys
Spinning KEKs with graceful ease,
Wrapped and rotated, versioned spree,
Sessions bloom from master-key trees,
Async flows through mystery,
Safe and sound—hop, hop, wheee! 🐰✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.62% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly and clearly describes the main objective of the changeset: adding support for rotating wrapped data encryption keys (WDEK) and session master key.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

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

Motivation:
Central Dogma needs to support key rotation for both wrapped data encryption keys (WDEKs) and session master keys to maintain security best practices.

Modifications:
- Renamed `KeyManagementService` to `KeyWrapper`
- Introduced `KeyManagementService` for key APIs
  - Implemented WDEK rotation logic that generates new wrapped keys with incremented versions
  - Implemented session master key rotation with version increment and concurrency protection

Result:
- System administrators can now rotate DEKs and session master key
/**
* Re-wraps the given wrapped data encryption key (WDEK).
*/
CompletableFuture<String> rewrap(String wdek, String oldKekId, String newKekId);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Some KMS providers, such as AWS KMS, embed version information directly within the wrapped key. In contrast, other providers do not support versioning, which requires users to manually specify a different key to perform a key rotation.

Copy link
Contributor

Choose a reason for hiding this comment

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

Note) No issue with this API, but it seems like it isn't called from anywhere

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, let me use it in the follow-up PR. 😉

@minwoox minwoox marked this pull request as ready for review November 14, 2025 06:12
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🧹 Nitpick comments (14)
server/src/main/java/com/linecorp/centraldogma/server/internal/admin/service/DefaultLogoutService.java (2)

71-107: Consider adding explicit error handling for async operations.

The CompletableFuture chains lack explicit error handling. If any async operation fails (session retrieval, invalidation, or propagation), users may receive generic error responses rather than meaningful logout-specific error messages.

Consider adding .exceptionally() or .handle() to provide better error responses:

return HttpResponse.of(sessionCookieHandler.getSessionInfo(ctx).thenCompose(sessionInfo -> {
    // ... existing logic ...
}).exceptionally(cause -> {
    logger.warn("Logout failed", cause);
    return HttpResponse.of(HttpStatus.INTERNAL_SERVER_ERROR, 
                          MediaType.PLAIN_TEXT_UTF_8, 
                          "Logout failed");
}));

86-88: Consider extracting duplicate success response creation.

The same success response with invalidatingCookie is created in two places. Extracting this to a helper method would improve maintainability.

private static HttpResponse logoutSuccessResponse(Cookie invalidatingCookie) {
    return HttpResponse.of(ResponseHeaders.builder(HttpStatus.OK)
                                          .cookie(invalidatingCookie)
                                          .build());
}

Then replace both occurrences with logoutSuccessResponse(invalidatingCookie).

Also applies to: 102-104

server/src/test/java/com/linecorp/centraldogma/server/storage/encryption/TestKeyWrapper.java (1)

40-42: Remove debug assertion from test utility.

The assertion at line 42 verifies the prefix but serves no functional purpose in a test utility. The length check at lines 36-38 already validates the structure. Consider removing this assertion or converting it to a proper validation if needed.

-        final byte[] prefix = new byte[prefixLength];
-        System.arraycopy(decoded, 0, prefix, 0, prefixLength);
-        assert "wrapped-".equals(new String(prefix));
-
         final byte[] dek = new byte[decoded.length - prefixLength];
         System.arraycopy(decoded, prefixLength, dek, 0, dek.length);
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)

32-64: Consider adding equals/hashCode for this value-type

WrappedDekDetails behaves like a value object (all-final fields, JSON DTO, used across commands), but currently relies on reference equality. This makes comparisons like RotateWdekCommand.equals depend on instance identity rather than logical equality, which is likely not what you want long term.

You can make it more predictable by overriding equals/hashCode here instead of per-command:

@@
-import java.time.Instant;
+import java.time.Instant;
+import java.util.Objects;
@@
 public final class WrappedDekDetails {
@@
     public String repoName() {
         return repoName;
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof WrappedDekDetails)) {
+            return false;
+        }
+        final WrappedDekDetails that = (WrappedDekDetails) o;
+        return dekVersion == that.dekVersion &&
+               wrappedDek.equals(that.wrappedDek) &&
+               kekId.equals(that.kekId) &&
+               projectName.equals(that.projectName) &&
+               repoName.equals(that.repoName) &&
+               creation.equals(that.creation);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(wrappedDek, dekVersion, kekId, projectName, repoName, creation);
+    }
+
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(this)
                           .add("wrappedDek", "****")
                           .add("dekVersion", dekVersion)

Also applies to: 114-124

webapp/javaTest/java/com/linecorp/centraldogma/webapp/TestKeyWrapper.java (1)

26-54: TestKeyWrapper ignores kekId and relies on assert-only validation

For a test stub this is fine functionally, but two limitations reduce how much rotation logic it exercises:

  • wrap/unwrap/rewrap ignore kekId/oldKekId/newKekId, so rewrapping with a different KEK ID produces the same WDEK and can’t catch KEK-mismatch bugs.
  • Prefix checking uses assert, which may be disabled in normal test runs, so malformed inputs can pass silently after the length check.

If you want tests to better simulate real key wrapping and KEK rotation, consider:

  • Incorporating the KEK ID into the encoded form (and validating it on unwrap/rewrap).
  • Replacing the assert with a runtime check that throws IllegalArgumentException on prefix mismatch.
server/src/test/java/com/linecorp/centraldogma/server/internal/api/sysadmin/KeyManagementServiceTest.java (1)

277-315: Rename pro to projectName in WrappedDek for clarity

The inner WrappedDek helper is clear, but the field name pro is slightly cryptic compared to the other code in this file. Renaming it to projectName would make the helper more self‑documenting and consistent with the rest of the codebase.

server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/RepositoryEncryptionStorage.java (1)

443-447: Clarify key‑version extraction from metadataValue

In the rev2sha and HEAD/refs branches you call:

final SecretKey dek = getDek(projectName, repoName, Ints.fromByteArray(metadataValue));

with metadataValue.length == 4 + NONCE_SIZE_BYTES. Ints.fromByteArray will read only the first four bytes (the version), but the fact that the array contains additional nonce bytes is not obvious at first glance.

Consider making this intent explicit, e.g.:

final int keyVersion = Ints.fromByteArray(Arrays.copyOfRange(metadataValue, 0, 4));
final SecretKey dek = getDek(projectName, repoName, keyVersion);

or by introducing a small helper method. This would reduce confusion for future readers without changing behavior.

Also applies to: 468-472

server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/EncryptionStorageManager.java (2)

58-62: Tighten kekId null-handling in factory methods

EncryptionStorageManager.of(CentralDogmaConfig) effectively enforces non-null kekId via the constructor, but the @VisibleForTesting of(Path, boolean, String kekId) overload only checks path. Letting a null kekId flow into the implementation fails later with a less-obvious NPE.

Consider validating kekId at the factory boundary for symmetry with path:

 @VisibleForTesting
 static EncryptionStorageManager of(Path path, boolean encryptSessionCookie, String kekId) {
-    requireNonNull(path, "path");
-    return new DefaultEncryptionStorageManager(path.toString(), encryptSessionCookie, kekId);
+    requireNonNull(path, "path");
+    requireNonNull(kekId, "kekId");
+    return new DefaultEncryptionStorageManager(path.toString(), encryptSessionCookie, kekId);
 }

Also applies to: 68-71


208-211: Document threading/ordering guarantees for addSessionKeyListener

The listener API is a nice addition, but it’s unclear:

  • Whether listeners are invoked synchronously or asynchronously.
  • From which threads they are called and whether invocations may be concurrent.
  • Whether newly added listeners see only future keys or also the current one.

Given that listeners will likely touch security-sensitive state, adding brief Javadoc about these aspects would help prevent misuse.

server/src/main/java/com/linecorp/centraldogma/server/command/Command.java (1)

46-66: Consider whether rotation commands should be force-pushable

New rotation support is wired via:

  • JSON subtypes: ROTATE_WDEK and ROTATE_SESSION_MASTER_KEY.
  • Factory methods: rotateWdek(...) and rotateSessionMasterKey(...).

Currently, forcePush only allows a fixed set of command types and excludes these new rotation commands. If you expect to perform key rotations while the server is in read-only/maintenance mode, you may want to extend the allowed CommandTypes; if not, the current restriction is fine but worth confirming explicitly.

No changes are required for correctness, but please double-check the intended operational behavior.

Also applies to: 412-424, 462-473

server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/SessionKeyStorage.java (2)

84-131: Rotation semantics and RocksDB writes look correct; consider logging unwrap failures

The rotation logic enforces newVersion == currentVersion + 1 when rotate == true and rejects duplicate versions using a RocksDB existence check, then atomically:

  • Writes the versioned master key entry, and
  • Updates the “current version” pointer under session/master/current.

This is a good, simple contract. Two improvements to consider:

  • The get + write sequence is non‑atomic at the RocksDB level; concurrent rotations with the same version could race. In practice admin calls are usually serialized, but if you expect multi‑node or concurrent rotations, you may want a stronger coordination mechanism at the command layer.
  • maybeSetCurrentSessionKey(sessionMasterKey) is invoked but its returned CompletableFuture is ignored. If unwrapping fails, the failure is only visible to callers of getCurrentSessionKey()/getSessionKey. Adding an .exceptionally(...) or logger hook to record unwrap/derivation failures would improve diagnosability without changing behavior.

168-201: SessionKey caching and listener notification are correct; note lock scope

getCurrentSessionKey() + maybeSetCurrentSessionKey(...):

  • Use a short lock to return a cached currentSessionKey if present, otherwise resolve it lazily by unwrapping the current master key.
  • Ensure currentSessionKey is only updated to a newer version (version() monotonic), preventing regressions if multiple async unwraps race.
  • Notify all listeners whenever a newer key is installed.

The design is solid. One thing to keep in mind: listeners are called while holding lock. Current usages (e.g., SessionCookieHandler just assigning a field) are cheap, but if future listeners perform heavier work or call back into this storage, this could lengthen lock hold time or create re‑entrancy hazards. If you expect non‑trivial listeners, consider copying the listener list under the lock and invoking them after releasing it.

server/src/main/java/com/linecorp/centraldogma/server/auth/SessionMasterKey.java (1)

18-105: SessionMasterKey refactor is solid; minor Javadoc nit

The class now:

  • Validates wrappedMasterKey, salt, kekId, creation as non‑null and version > 0.
  • Exposes base64‑encoded wrapped master key and salt, plus KEK ID and creation timestamp.
  • Redacts both wrapped key and salt in toString(), while keeping non‑sensitive metadata.

This is a good security posture. The only minor nit is the incomplete Javadoc on kekId (“used to wrap the …”); consider finishing that sentence for clarity.

server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/NoopEncryptionStorageManager.java (1)

18-172: No‑op implementation aligns with the expanded API; clarify null‑return expectations

The noop manager cleanly implements the expanded EncryptionStorageManager surface by:

  • Always returning false from enabled() and encryptSessionCookie().
  • Returning UnmodifiableFuture.completedFuture(null) for key‑generation and key‑retrieval methods, and ImmutableList.of() / empty maps for listing methods.
  • Making all mutation methods (store/rotate/remove WDEK, rotate session master key, listener registration, etc.) true no‑ops.

This is consistent with a disabled‑encryption environment. To avoid accidental null handling bugs, it’s worth documenting at the interface level that these async methods may complete with null when encryption is disabled, and that callers are expected to guard on enabled() / encryptSessionCookie() and not rely on these futures in that case.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 74e8f1c and 841a90d.

📒 Files selected for processing (53)
  • server/src/main/java/com/linecorp/centraldogma/server/CentralDogma.java (2 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/EncryptionAtRestConfig.java (3 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/auth/SessionKey.java (5 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/auth/SessionMasterKey.java (2 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/command/Command.java (6 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/command/CommandType.java (1 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/command/CreateProjectCommand.java (5 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/command/CreateRepositoryCommand.java (5 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/command/MigrateToEncryptedRepositoryCommand.java (4 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/command/RotateSessionMasterKeyCommand.java (1 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/command/RotateWdekCommand.java (1 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/command/StandaloneCommandExecutor.java (7 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/AuthSessionService.java (4 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionCookieAuthorizer.java (1 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionCookieHandler.java (1 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionUtil.java (4 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/internal/admin/service/DefaultLogoutService.java (1 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/internal/admin/service/UserService.java (1 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/internal/api/RepositoryServiceUtil.java (2 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/internal/api/RepositoryServiceV1.java (3 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/internal/api/sysadmin/KeyManagementService.java (1 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/internal/api/sysadmin/ServerStatusService.java (1 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/internal/api/sysadmin/SessionMasterKeyDetails.java (1 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/internal/storage/project/ProjectApiManager.java (2 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/git/rocksdb/EncryptionGitStorage.java (9 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/DefaultEncryptionStorageManager.java (2 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/EncryptionStorageManager.java (6 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/KeyWrapper.java (1 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/NoopEncryptionStorageManager.java (4 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/RepositoryEncryptionStorage.java (1 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/RocksDBStorage.java (1 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/SessionKeyStorage.java (1 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/storage/project/InternalProjectInitializer.java (1 hunks)
  • server/src/test/java/com/linecorp/centraldogma/server/auth/SessionKeyTest.java (2 hunks)
  • server/src/test/java/com/linecorp/centraldogma/server/internal/admin/auth/AuthServerTest.java (2 hunks)
  • server/src/test/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionUtilTest.java (2 hunks)
  • server/src/test/java/com/linecorp/centraldogma/server/internal/api/MigrateToEncryptedRepositoryTest.java (1 hunks)
  • server/src/test/java/com/linecorp/centraldogma/server/internal/api/sysadmin/KeyManagementServiceTest.java (1 hunks)
  • server/src/test/java/com/linecorp/centraldogma/server/internal/storage/repository/git/EncryptedGitRepositoryRemoveTest.java (3 hunks)
  • server/src/test/java/com/linecorp/centraldogma/server/internal/storage/repository/git/GitRepositoryTest.java (2 hunks)
  • server/src/test/java/com/linecorp/centraldogma/server/internal/storage/repository/git/rocksdb/EncryptionGitStorageTest.java (2 hunks)
  • server/src/test/java/com/linecorp/centraldogma/server/storage/encryption/DefaultEncryptionStorageManagerTest.java (5 hunks)
  • server/src/test/java/com/linecorp/centraldogma/server/storage/encryption/TestKeyWrapper.java (1 hunks)
  • server/src/test/resources/META-INF/services/com.linecorp.centraldogma.server.storage.encryption.KeyManagementService (0 hunks)
  • server/src/test/resources/META-INF/services/com.linecorp.centraldogma.server.storage.encryption.KeyWrapper (1 hunks)
  • testing/common/src/main/java/com/linecorp/centraldogma/testing/internal/CentralDogmaRuleDelegate.java (2 hunks)
  • testing/junit/src/main/java/com/linecorp/centraldogma/testing/junit/CentralDogmaExtension.java (2 hunks)
  • testing/junit4/src/main/java/com/linecorp/centraldogma/testing/junit4/CentralDogmaRule.java (2 hunks)
  • webapp/javaTest/java/com/linecorp/centraldogma/webapp/SamlCentralDogmaTestServer.java (1 hunks)
  • webapp/javaTest/java/com/linecorp/centraldogma/webapp/TestKeyWrapper.java (1 hunks)
  • webapp/javaTest/resources/META-INF/services/com.linecorp.centraldogma.server.storage.encryption.KeyManagementService (0 hunks)
  • webapp/javaTest/resources/META-INF/services/com.linecorp.centraldogma.server.storage.encryption.KeyWrapper (1 hunks)
💤 Files with no reviewable changes (2)
  • server/src/test/resources/META-INF/services/com.linecorp.centraldogma.server.storage.encryption.KeyManagementService
  • webapp/javaTest/resources/META-INF/services/com.linecorp.centraldogma.server.storage.encryption.KeyManagementService
🧰 Additional context used
🧬 Code graph analysis (23)
server/src/main/java/com/linecorp/centraldogma/server/command/RotateWdekCommand.java (1)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)
  • WrappedDekDetails (30-125)
server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/AuthSessionService.java (1)
server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionUtil.java (1)
  • SessionUtil (59-314)
server/src/test/java/com/linecorp/centraldogma/server/internal/api/sysadmin/KeyManagementServiceTest.java (8)
common/src/main/java/com/linecorp/centraldogma/internal/api/v1/HttpApiV1Constants.java (1)
  • HttpApiV1Constants (22-48)
server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionUtil.java (1)
  • SessionUtil (59-314)
server/src/main/java/com/linecorp/centraldogma/server/internal/storage/MigratingMetaToDogmaRepositoryService.java (1)
  • MigratingMetaToDogmaRepositoryService (53-226)
testing-internal/src/main/java/com/linecorp/centraldogma/testing/internal/auth/TestAuthMessageUtil.java (1)
  • TestAuthMessageUtil (44-146)
server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/git/rocksdb/EncryptionGitStorage.java (1)
  • EncryptionGitStorage (58-398)
server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/git/rocksdb/GitObjectMetadata.java (1)
  • GitObjectMetadata (29-101)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)
  • WrappedDekDetails (30-125)
server/src/main/java/com/linecorp/centraldogma/server/storage/project/InternalProjectInitializer.java (1)
  • InternalProjectInitializer (66-314)
server/src/main/java/com/linecorp/centraldogma/server/internal/api/sysadmin/KeyManagementService.java (3)
server/src/main/java/com/linecorp/centraldogma/server/auth/SessionMasterKey.java (1)
  • SessionMasterKey (28-107)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/SecretKeyWithVersion.java (1)
  • SecretKeyWithVersion (28-63)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)
  • WrappedDekDetails (30-125)
server/src/test/java/com/linecorp/centraldogma/server/internal/storage/repository/git/EncryptedGitRepositoryRemoveTest.java (1)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)
  • WrappedDekDetails (30-125)
server/src/main/java/com/linecorp/centraldogma/server/command/StandaloneCommandExecutor.java (1)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)
  • WrappedDekDetails (30-125)
server/src/main/java/com/linecorp/centraldogma/server/command/RotateSessionMasterKeyCommand.java (1)
server/src/main/java/com/linecorp/centraldogma/server/auth/SessionMasterKey.java (1)
  • SessionMasterKey (28-107)
server/src/test/java/com/linecorp/centraldogma/server/storage/encryption/TestKeyWrapper.java (1)
webapp/javaTest/java/com/linecorp/centraldogma/webapp/TestKeyManagementService.java (2)
  • TestKeyManagementService (22-43)
  • Override (24-31)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/SessionKeyStorage.java (5)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/RocksDBStorage.java (1)
  • RocksDBStorage (47-175)
common/src/main/java/com/linecorp/centraldogma/internal/Jackson.java (1)
  • Jackson (68-390)
server/src/main/java/com/linecorp/centraldogma/server/auth/SessionKey.java (1)
  • SessionKey (41-136)
server/src/main/java/com/linecorp/centraldogma/server/auth/SessionMasterKey.java (1)
  • SessionMasterKey (28-107)
server/src/main/java/com/linecorp/centraldogma/server/internal/storage/AesGcmSivCipher.java (1)
  • AesGcmSivCipher (33-94)
server/src/main/java/com/linecorp/centraldogma/server/internal/api/sysadmin/ServerStatusService.java (1)
server/src/main/java/com/linecorp/centraldogma/server/internal/api/LoggerService.java (1)
  • RequiresSystemAdministrator (43-147)
server/src/test/java/com/linecorp/centraldogma/server/internal/storage/repository/git/GitRepositoryTest.java (1)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)
  • WrappedDekDetails (30-125)
server/src/main/java/com/linecorp/centraldogma/server/command/CreateProjectCommand.java (1)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)
  • WrappedDekDetails (30-125)
server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionCookieHandler.java (2)
server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionUtil.java (1)
  • SessionUtil (59-314)
server/src/main/java/com/linecorp/centraldogma/server/auth/SessionKey.java (1)
  • SessionKey (41-136)
server/src/main/java/com/linecorp/centraldogma/server/internal/storage/project/ProjectApiManager.java (1)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)
  • WrappedDekDetails (30-125)
server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionUtil.java (1)
server/src/main/java/com/linecorp/centraldogma/server/auth/SessionKey.java (1)
  • SessionKey (41-136)
server/src/main/java/com/linecorp/centraldogma/server/command/MigrateToEncryptedRepositoryCommand.java (1)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)
  • WrappedDekDetails (30-125)
server/src/main/java/com/linecorp/centraldogma/server/internal/api/RepositoryServiceUtil.java (1)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)
  • WrappedDekDetails (30-125)
server/src/main/java/com/linecorp/centraldogma/server/command/Command.java (1)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)
  • WrappedDekDetails (30-125)
server/src/test/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionUtilTest.java (1)
server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionUtil.java (1)
  • SessionUtil (59-314)
server/src/main/java/com/linecorp/centraldogma/server/command/CreateRepositoryCommand.java (1)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)
  • WrappedDekDetails (30-125)
server/src/main/java/com/linecorp/centraldogma/server/internal/api/RepositoryServiceV1.java (1)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)
  • WrappedDekDetails (30-125)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/RepositoryEncryptionStorage.java (5)
server/src/main/java/com/linecorp/centraldogma/server/internal/storage/AesGcmSivCipher.java (1)
  • AesGcmSivCipher (33-94)
server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/git/rocksdb/EncryptionGitStorage.java (1)
  • EncryptionGitStorage (58-398)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/RocksDBStorage.java (1)
  • RocksDBStorage (47-175)
common/src/main/java/com/linecorp/centraldogma/internal/Jackson.java (1)
  • Jackson (68-390)
server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/git/rocksdb/GitObjectMetadata.java (1)
  • GitObjectMetadata (29-101)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/DefaultEncryptionStorageManager.java (2)
server/src/main/java/com/linecorp/centraldogma/server/auth/SessionKey.java (1)
  • SessionKey (41-136)
server/src/main/java/com/linecorp/centraldogma/server/auth/SessionMasterKey.java (1)
  • SessionMasterKey (28-107)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: build-ubuntu-latest-jdk-17-coverage
  • GitHub Check: build-ubuntu-latest-jdk-17-min-java-11
  • GitHub Check: build-macos-latest-jdk-21
  • GitHub Check: build-windows-latest-jdk-21
  • GitHub Check: build-ubuntu-latest-jdk-11
  • GitHub Check: build-ubuntu-latest-jdk-21-snapshot
🔇 Additional comments (60)
server/src/main/java/com/linecorp/centraldogma/server/internal/api/RepositoryServiceUtil.java (1)

69-76: LGTM!

The WDEK wrapping logic correctly constructs a WrappedDekDetails object with version 1 (appropriate for new repository creation) and passes all required metadata (kekId, projectName, repoName) to the Command.createRepository method.

server/src/main/java/com/linecorp/centraldogma/server/CentralDogma.java (1)

1030-1032: LGTM!

The conditional binding of KeyManagementService is correctly guarded by the encryption enabled check, ensuring key management endpoints are only exposed when encryption-at-rest is configured.

server/src/main/java/com/linecorp/centraldogma/server/internal/api/sysadmin/ServerStatusService.java (1)

36-36: LGTM!

The class-level @RequiresSystemAdministrator annotation correctly restricts access to all server status endpoints, consistent with other administrative services like LoggerService.

webapp/javaTest/resources/META-INF/services/com.linecorp.centraldogma.server.storage.encryption.KeyWrapper (1)

1-1: LGTM!

Standard Java ServiceLoader registration for the test KeyWrapper implementation.

testing/common/src/main/java/com/linecorp/centraldogma/testing/internal/CentralDogmaRuleDelegate.java (1)

206-211: LGTM!

The new encryptionStorageManager() accessor follows the same pattern as other delegate methods like projectManager(), appropriately exposing encryption storage management for test scenarios.

server/src/main/java/com/linecorp/centraldogma/server/internal/admin/service/UserService.java (1)

81-101: LGTM!

The refactoring correctly transforms the synchronous session retrieval into an asynchronous composition. The nested CompletableFuture handling is appropriate here—Armeria's HttpResponse.of() correctly handles CompletableFuture at both levels, and all original logic branches are preserved.

webapp/javaTest/java/com/linecorp/centraldogma/webapp/SamlCentralDogmaTestServer.java (1)

55-55: LGTM!

The EncryptionAtRestConfig constructor correctly includes the new kekId parameter, aligning with the PR's introduction of KEK (Key Encryption Key) support.

server/src/test/java/com/linecorp/centraldogma/server/internal/api/MigrateToEncryptedRepositoryTest.java (1)

58-58: LGTM!

The test correctly updates the EncryptionAtRestConfig constructor to include the new kekId parameter.

server/src/test/java/com/linecorp/centraldogma/server/internal/storage/repository/git/rocksdb/EncryptionGitStorageTest.java (2)

82-85: LGTM!

The lenient stubbing approach is appropriate for test setup where stubs may not be invoked in every test method. The addition of the versioned getDek stub correctly aligns with the getCurrentDek returning version 1, supporting the WDEK versioning introduced in this PR.


332-336: Good addition of clarifying comment.

The comment and times(2) verification correctly validates that multiple ref updates are stored properly. The test structure is clear and appropriate.

server/src/test/resources/META-INF/services/com.linecorp.centraldogma.server.storage.encryption.KeyWrapper (1)

1-1: LGTM!

Standard ServiceLoader configuration for test-time KeyWrapper implementation. This aligns with the PR's refactoring from KeyManagementService to KeyWrapper.

server/src/main/java/com/linecorp/centraldogma/server/command/CommandType.java (1)

40-41: LGTM!

The new command types are correctly defined with Void.class as the result type, and the naming clearly indicates their purpose for key rotation operations.

testing/junit4/src/main/java/com/linecorp/centraldogma/testing/junit4/CentralDogmaRule.java (1)

185-190: LGTM!

The new accessor method follows the existing pattern (e.g., projectManager()) and properly exposes the EncryptionStorageManager for test access. The implementation correctly delegates to the underlying delegate.

testing/junit/src/main/java/com/linecorp/centraldogma/testing/junit/CentralDogmaExtension.java (1)

199-204: LGTM!

The accessor method is consistent with the similar addition in CentralDogmaRule and follows the established pattern for exposing server components to tests.

server/src/test/java/com/linecorp/centraldogma/server/internal/admin/auth/AuthServerTest.java (2)

81-81: LGTM!

The constructor call correctly includes the new kekId parameter, aligning with the updated EncryptionAtRestConfig API that supports KEK-based encryption.


93-93: LGTM!

Consistent with the previous configuration, this correctly includes the kekId parameter.

server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionCookieAuthorizer.java (1)

84-112: Well-structured async refactor.

The migration to asynchronous session retrieval is properly implemented with appropriate null checks and error handling. The flow correctly handles both JWT-only (read-only) and session-based authorization paths, with CSRF validation in both cases.

server/src/main/java/com/linecorp/centraldogma/server/command/StandaloneCommandExecutor.java (5)

210-216: LGTM!

The command dispatch for the new rotation operations is properly integrated into the existing command handling flow.


245-270: LGTM!

The refactoring to use WrappedDekDetails is consistent and complete. The error handling properly cleans up the stored WDEK using the correct version on failure.


310-336: LGTM!

The changes mirror the createProject refactoring with consistent use of WrappedDekDetails and proper cleanup logic.


359-382: LGTM!

The migration command follows the same WrappedDekDetails pattern with proper error handling and version-based cleanup.


403-419: LGTM!

The rotation methods are properly implemented with appropriate precondition checks and clear error messages. The delegation to EncryptionStorageManager and return of completed futures is consistent with other void operations in this class.

server/src/main/java/com/linecorp/centraldogma/server/EncryptionAtRestConfig.java (1)

47-50: LGTM! Proper validation for KEK ID when encryption is enabled.

The validation correctly enforces that kekId must be provided when encryption at rest is enabled, while allowing it to be null when disabled.

server/src/main/java/com/linecorp/centraldogma/server/internal/storage/project/ProjectApiManager.java (1)

121-127: LGTM! Proper WrappedDekDetails construction.

The WDEK is correctly wrapped with version 1, KEK ID, and repository metadata before being passed to the command executor.

server/src/test/java/com/linecorp/centraldogma/server/internal/storage/repository/git/GitRepositoryTest.java (1)

110-115: LGTM! Test setup correctly updated for new WDEK handling.

The test properly constructs WrappedDekDetails with version 1 and stores it via the updated storeWdek API.

server/src/main/java/com/linecorp/centraldogma/server/storage/project/InternalProjectInitializer.java (1)

255-255: LGTM! Correct session master key generation with version 1.

The updated call properly passes version 1 for the initial session master key generation, aligning with the new API signature.

server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/KeyWrapper.java (1)

38-41: LGTM! Essential method for key rotation.

The rewrap method enables secure key rotation by allowing a WDEK to be rewrapped with a new KEK without exposing the plaintext DEK to the application layer.

server/src/main/java/com/linecorp/centraldogma/server/internal/api/sysadmin/SessionMasterKeyDetails.java (1)

29-79: LGTM! Well-designed immutable metadata class.

The class correctly:

  • Validates version > 0
  • Enforces non-null KEK ID and creation timestamp
  • Exposes only non-sensitive metadata
  • Provides JSON serialization support
server/src/test/java/com/linecorp/centraldogma/server/auth/SessionKeyTest.java (1)

36-38: LGTM! Test correctly updated for new API.

The test properly:

  • Passes kekId to EncryptionStorageManager.of()
  • Provides version 1 to generateSessionMasterKey()
  • Decodes the Base64-encoded salt before validating its length
server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/git/rocksdb/EncryptionGitStorage.java (1)

100-100: No performance concerns; internal DEK caching is properly implemented.

The RepositoryEncryptionStorage class has internal caching via two ConcurrentHashMap fields (currentDeks and dekWithVersion) that cache DEKs after first retrieval. Both getDek() and getCurrentDek() use computeIfAbsent() for thread-safe lazy initialization and caching. Cache entries are properly invalidated when keys are rotated or removed. The on-demand retrieval pattern is correct—write operations use getCurrentDek() to get the latest key, while read operations use getDek(version) to retrieve the versioned key used at encryption time. This ensures correctness after key rotation without performance degradation.

server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/RocksDBStorage.java (1)

128-175: LGTM!

The CRUD operations and resource management methods are correctly implemented with appropriate error handling and defensive checks.

server/src/test/java/com/linecorp/centraldogma/server/internal/storage/repository/git/EncryptedGitRepositoryRemoveTest.java (2)

66-77: LGTM!

The test correctly updates to use the new WrappedDekDetails API, properly constructing the details with version, kekId, project, and repository names.


125-130: LGTM!

Consistent usage of WrappedDekDetails for the second repository creation.

server/src/test/java/com/linecorp/centraldogma/server/storage/encryption/DefaultEncryptionStorageManagerTest.java (2)

69-79: LGTM!

The test correctly validates the Base64-encoded wrapped DEK structure and length.


82-111: LGTM!

The test correctly validates WDEK storage, retrieval, and versioned removal with proper error handling.

server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/AuthSessionService.java (3)

45-69: LGTM!

The dynamic session key management via volatile field and listener pattern correctly enables key rotation. The volatile keyword ensures visibility across threads, and the initialization ensures a valid key is available before use.


74-96: LGTM!

The read-only mode login correctly requires encryption and uses the current session key for signing and encrypting JWTs.


98-116: LGTM!

The writable mode login correctly handles both encrypted and non-encrypted sessions, using the current session key when encryption is enabled.

server/src/main/java/com/linecorp/centraldogma/server/command/RotateSessionMasterKeyCommand.java (1)

31-73: LGTM!

The command implementation follows standard patterns with proper field validation, equality semantics, and toString representation. The null timestamp and author in the constructor are appropriate for system-initiated rotation commands.

server/src/test/java/com/linecorp/centraldogma/server/storage/encryption/TestKeyWrapper.java (1)

49-52: LGTM!

The rewrap implementation correctly chains unwrap and wrap operations for key rotation.

server/src/main/java/com/linecorp/centraldogma/server/command/MigrateToEncryptedRepositoryCommand.java (1)

32-94: LGTM!

The migration to WrappedDekDetails provides better encapsulation and context compared to raw byte arrays, with correctly updated equality and hashing semantics.

server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionUtil.java (2)

152-182: LGTM!

The signature verification using the SessionKey is correctly implemented, validating the JWT signature before parsing claims. The error handling appropriately logs and returns null for invalid signatures.


226-239: LGTM!

The visibility change to public is appropriate for external callers needing to access session cookies, with proper null handling maintained.

server/src/main/java/com/linecorp/centraldogma/server/command/RotateWdekCommand.java (1)

31-75: RotateWdekCommand structure and JSON binding look consistent

Command type, JSON creator, non-null wdekDetails, equality, hash code, and redacted toStringHelper usage are all coherent with the existing command pattern and avoid leaking the wrapped key.

server/src/main/java/com/linecorp/centraldogma/server/internal/api/RepositoryServiceV1.java (1)

333-342: WrappedDekDetails integration in migration flow is coherent

generateWdek()WrappedDekDetails construction (with version 1, kekId, project, and repo) → migrate(...) wiring preserves the previous control flow while enriching it with the metadata needed for rotation; error handling and status transitions remain unchanged.

Also applies to: 404-413

server/src/test/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionUtilTest.java (1)

64-69: SessionUtilTest correctly exercises the new SessionKey-based JWT/JWE flow

The setup using EncryptionStorageManager and SessionMasterKey, and the round-trip via SessionKey (signer, encrypter, decrypter, and verifier passed into getJwtClaimsSetFromSignedJwt) is consistent and keeps the original behavioral assertions intact.

Also applies to: 79-107

server/src/test/java/com/linecorp/centraldogma/server/internal/api/sysadmin/KeyManagementServiceTest.java (1)

114-166: End‑to‑end WDEK rotation coverage looks solid

This test does a nice job validating the full behavior: initial WDEK listing, DEK version used by commits, rotation via the admin API, and that subsequent commits pick up the new version by inspecting GitObjectMetadata. This gives good confidence that the wiring between KeyManagementService, EncryptionGitStorage, and RocksDB metadata is correct.

server/src/main/java/com/linecorp/centraldogma/server/internal/api/sysadmin/KeyManagementService.java (1)

61-83: Rotation endpoints line up well with storage semantics

The WDEK and session master key rotation endpoints correctly:

  • Guard on encryption/session‑cookie enablement.
  • Use the current version from EncryptionStorageManager and compute +1 for the new version.
  • Rely on the storage layer’s “version must be exactly one greater” checks to handle concurrent rotations safely.

Given these are admin‑only endpoints, throwing on unsupported reencrypt=true and on disabled session cookie encryption is reasonable for now and matches the surrounding style.

Also applies to: 101-116

server/src/main/java/com/linecorp/centraldogma/server/auth/SessionKey.java (1)

47-64: ---

SessionKey derivation from SessionMasterKey is correct

The refactor to derive SessionKey from SessionMasterKey is sound:

  • Salt is Base64‑decoded from sessionMasterKey.salt(), matching its documented encoding.
  • HKDF uses distinct info strings for signing vs encryption keys, with 32‑byte outputs for HMAC‑SHA256 and AES, respectively.
  • The version is taken directly from sessionMasterKey.version(), so key IDs and cookie headers stay aligned.
  • MACSigner, MACVerifier, DirectEncrypter, and DirectDecrypter are explicitly documented as thread-safe in Nimbus JOSE+JWT, so caching them as instance fields is both safe and efficient.
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/EncryptionStorageManager.java (1)

84-87: KEK ID accessor looks consistent with configuration

Exposing kekId() on EncryptionStorageManager matches the new constructor wiring and EncryptionAtRestConfig.kekId(). The method and Javadoc are clear and align with the rest of the API.

server/src/main/java/com/linecorp/centraldogma/server/command/Command.java (1)

79-90: WDEK-aware factory overloads are consistent with existing patterns

The new overloads:

  • createProject(Author author, String name, WrappedDekDetails wdekDetails)
  • createRepository(Author author, String projectName, String repositoryName, WrappedDekDetails wdekDetails)
  • migrateToEncryptedRepository(@Nullable Long timestamp, Author author, String projectName, String repositoryName, WrappedDekDetails wdekDetails)

mirror the existing command style:

  • They reuse existing command implementations (CreateProjectCommand, CreateRepositoryCommand, MigrateToEncryptedRepositoryCommand).
  • They validate author and wdekDetails where appropriate.

The signatures and Javadocs look coherent with the rest of the API surface.

Also applies to: 190-203, 296-305

server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/DefaultEncryptionStorageManager.java (2)

45-67: Constructor wiring and KeyWrapper loading look reasonable

The new constructor:

  • Validates rocksDbPath and kekId.
  • Uses ServiceLoader with a strict keyWrappers.size() == 1 check.
  • Wires RocksDBStorage, SessionKeyStorage, and RepositoryEncryptionStorage around the shared KeyWrapper.

Assuming the runtime packaging provides exactly one KeyWrapper implementation, this is a clear and fail-fast setup for encryption at rest.


80-198: Delegations to SessionKeyStorage/RepositoryEncryptionStorage look correct

All updated methods in this class now behave as thin facades:

  • Session master key and session key lifecycle:
    • kekId(), generateSessionMasterKey(int), storeSessionMasterKey(...),
      getCurrentSessionMasterKey(), getCurrentSessionKey(), getSessionKey(int),
      rotateSessionMasterKey(...), and addSessionKeyListener(...) all delegate to sessionKeyStorage.
  • WDEK and DEK operations:
    • generateWdek(), wdeks(), getDek(...), getCurrentDek(...),
      storeWdek(...), rotateWdek(...), removeWdek(...) all delegate to repositoryEncryptionStorage.
  • Low-level object/metadata APIs and repository cleanup:
    • getObject(...), getObjectId(...), getMetadata(...), putObject(...),
      putObjectId(...), containsMetadata(...), deleteObjectId(...),
      deleteRepositoryData(...), getAllData() are all forwarded to repositoryEncryptionStorage.
  • close() correctly closes the shared rocksDbStorage.

The delegations appear 1:1 with no parameter reordering or obvious mistakes, which keeps this class focused on wiring rather than logic.

server/src/main/java/com/linecorp/centraldogma/server/command/CreateRepositoryCommand.java (1)

40-101: WrappedDekDetails integration and logging look good

The migration from byte[] wdek to WrappedDekDetails wdekDetails is consistent across constructor, accessor, equals, hashCode, and toString. Given that WrappedDekDetails.toString() already masks the wrapped key, adding wdekDetails directly to the ToStringHelper does not leak key material, while still exposing useful metadata. No issues from a correctness or security perspective here.

server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/SessionKeyStorage.java (4)

63-78: Session master key generation is sound and HKDF‑compatible

generateSessionMasterKey(int version) correctly:

  • Generates a 256‑bit random master key and wraps it via KeyWrapper.
  • Generates an independent 256‑bit random salt (non‑secret but high entropy) consistent with HKDF recommendations.
  • Populates SessionMasterKey with wrapped key, version, base64 salt, KEK ID, and ISO‑8601 creation timestamp.

This is aligned with SessionMasterKey’s API and HKDF usage in SessionKey.of(...).


133-166: Current session master key retrieval and deserialization

getCurrentSessionMasterKey() and getSessionMasterKey(int version) correctly:

  • Read the current version pointer, fail fast if it doesn’t exist, and
  • Fetch and deserialize the corresponding SessionMasterKey JSON, wrapping RocksDB and Jackson errors in EncryptionStorageException.

This is straightforward and consistent with the storage format; no issues.


203-209: Per‑version SessionKey cache is reasonable

getSessionKey(int version) uses a ConcurrentHashMap of CompletableFuture<SessionKey> keyed by version, lazily populating entries from the stored SessionMasterKey. This is a good fit for infrequently‑rotated session keys, and ensures:

  • At most one unwrap/derivation per version, and
  • Shared futures across concurrent callers.

Given the expected small number of key versions, the unbounded map is acceptable. No changes needed.


223-234: Listener registration behavior is clear

addSessionKeyListener:

  • Enforces non‑null listeners.
  • Registers under the same lock used for updates.
  • Immediately invokes the listener with the current key if present, which simplifies consumers.

This matches the semantics implied by SessionCookieHandler. Just ensure listener implementations stay lightweight since they run under the internal lock.

server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionCookieHandler.java (2)

76-100: Key‑version aware session lookup is well‑designed

The async getSessionInfo(ServiceRequestContext) method:

  • Short‑circuits to null when no cookie is present or the key version in the JWE header is invalid.
  • Uses the cached sessionKey when the version matches, avoiding an extra lookup.
  • When the version differs, fetches the appropriate SessionKey via encryptionStorageManager.getSessionKey(version) and re‑decodes with that key.
  • Falls back to plain session ID cookies when encryption is disabled.

This provides a clean migration path across key rotations while honoring per‑version tokens. Behavior on exceptions from getSessionKey() is to propagate an exceptionally completed future, which is appropriate for callers to handle. No changes needed here.


102-129: JWT parsing and read‑only fallback are consistent with SessionUtil

The private getSessionInfo(ctx, sessionKey):

  • Decrypts the JWE cookie, verifies the inner JWS signature with the SessionKey verifier, and validates claims via DefaultJWTClaimsVerifier.
  • Preferentially uses a "sessionId" claim when present.
  • In read‑only mode (sessionPropagatorWritableChecker.getAsBoolean() is false), falls back to authenticating purely by sub (username) plus a CSRF token derived from the JWS signature.

This matches the utilities in SessionUtil and maintains redaction and verification of all cryptographic material. Logic is clear and correct.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/src/main/java/com/linecorp/centraldogma/server/internal/admin/service/DefaultLogoutService.java (1)

48-67: Move null check before first usage.

The requireNonNull validation on line 56 occurs after encryptionStorageManager is already used on lines 54-55 and line 60. If null, an NPE will be thrown before the validation check, defeating its purpose.

Apply this diff to move the validation to the correct position:

 public DefaultLogoutService(Function<String, CompletableFuture<Void>> logoutSessionPropagator,
                             BooleanSupplier sessionPropagatorWritableChecker,
                             SessionManager sessionManager, boolean tlsEnabled,
                             EncryptionStorageManager encryptionStorageManager) {
     this.logoutSessionPropagator = requireNonNull(logoutSessionPropagator, "logoutSessionPropagator");
     this.sessionManager = requireNonNull(sessionManager, "sessionManager");
+    requireNonNull(encryptionStorageManager, "encryptionStorageManager");
     sessionCookieHandler = new SessionCookieHandler(
             sessionPropagatorWritableChecker, tlsEnabled, encryptionStorageManager);
-    requireNonNull(encryptionStorageManager, "encryptionStorageManager");
♻️ Duplicate comments (1)
server/src/main/java/com/linecorp/centraldogma/server/command/CreateProjectCommand.java (1)

44-57: Re-check JSON backward compatibility for wdekwdekDetails (equality semantics now OK)

The switch to WrappedDekDetails wdekDetails and the Objects.equals/Objects.hashCode usage looks correct now that WrappedDekDetails implements structural equals/hashCode — this addresses the earlier equality semantics concern.

However, the JSON field has been renamed from "wdek" (previous byte[] field) to "wdekDetails" with a different shape (object instead of the old representation). If any CreateProjectCommand instances with a wdek field have been serialized (e.g., in logs, snapshots, or replicated command streams), newer servers will not be able to deserialize them into this constructor:

@JsonCreator
CreateProjectCommand(...,
                     @JsonProperty("projectName") String projectName,
                     @JsonProperty("wdekDetails") @Nullable WrappedDekDetails wdekDetails)

Please confirm one of the following:

  • Either no historical commands exist that include the old "wdek" field (e.g., encryption-at-rest was not previously enabled for project creation), or
  • You have a migration/deserialization strategy in place (e.g., a custom deserializer or an alternate @JsonCreator that can translate the legacy wdek representation into a WrappedDekDetails with reasonable defaults).

If the latter is needed, simple @JsonAlias("wdek") on this parameter will not be enough by itself because the JSON shape changed (string/bytes vs object). A conversion step from the legacy representation to WrappedDekDetails would still be required.

Also applies to: 88-95

🧹 Nitpick comments (5)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/NoopEncryptionStorageManager.java (1)

54-66: Empty kekId() and null futures are acceptable but could be more explicit.

Returning an empty string for kekId() and null from generateWdek() / generateSessionMasterKey() futures is fine for a noop implementation, assuming callers always check enabled() before using these values. If you expect these methods to be surfaced in debugging or metrics, consider returning a sentinel ID such as "noop" instead of "" for clarity.

server/src/main/java/com/linecorp/centraldogma/server/command/StandaloneCommandExecutor.java (2)

245-269: WDEK handling on create project/repository looks correct; minor reuse improvement.

Storing the WDEK before creation and removing it on failure using dekVersion() gives you a clear lifecycle and versioned cleanup for both projects and repositories. One small improvement would be to reuse the already captured wdekDetails instead of calling c.wdekDetails() again in the error paths, e.g.:

-                        encryptionStorageManager.removeWdek(c.projectName(), Project.REPO_DOGMA,
-                                                            c.wdekDetails().dekVersion());
+                        encryptionStorageManager.removeWdek(c.projectName(), Project.REPO_DOGMA,
+                                                            wdekDetails.dekVersion());

and similarly in createRepository. This avoids any accidental divergence if the command implementation ever changes and makes the code slightly easier to follow.

Also applies to: 310-336


403-419: Rotation helpers mirror existing patterns; consider harmonizing error messages.

The rotateWdek and rotateSessionMasterKey helpers delegate synchronously to EncryptionStorageManager and use guards (enabled() vs encryptSessionCookie()) that match their respective concerns, with completed futures for the command pipeline. For consistency with other guards (e.g., MigrateToEncryptedRepository and CreateSessionMasterKeyCommand), you might want to include the command in the IllegalStateException message for rotateSessionMasterKey as well:

-        if (!encryptionStorageManager.encryptSessionCookie()) {
-            throw new IllegalStateException("Session cookie encryption is disabled.");
-        }
+        if (!encryptionStorageManager.encryptSessionCookie()) {
+            throw new IllegalStateException("Session cookie encryption is disabled. command: " + c);
+        }
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/RocksDBStorage.java (1)

71-83: Close ColumnFamilyOptions on the normal path to avoid native leaks.

ColumnFamilyOptions objects are created per column family in the constructor and are closed only in the early-error paths. On the normal path, they outlive the constructor but are never closed in close(), unlike bloomFilter, rocksDb, and dbOptions. Since ColumnFamilyOptions is a RocksDB native resource, it should also be released:

  • Store the cfNameToOptions map (or its values) in a field.
  • In close(), iterate and close those options alongside the other resources.

Example sketch:

-    private final DBOptions dbOptions;
+    private final DBOptions dbOptions;
+    private final Map<String, ColumnFamilyOptions> columnFamilyOptionsMap;
@@
-        final Map<String, ColumnFamilyOptions> cfNameToOptions = new HashMap<>();
+        final Map<String, ColumnFamilyOptions> cfNameToOptions = new HashMap<>();
@@
-        columnFamilyHandlesMap = handlesMapBuilder.build();
+        columnFamilyHandlesMap = handlesMapBuilder.build();
+        columnFamilyOptionsMap = ImmutableMap.copyOf(cfNameToOptions);
@@
     void close() {
-        bloomFilter.close();
-        columnFamilyHandlesMap.values().forEach(RocksDBStorage::closeSilently);
-        closeSilently(rocksDb);
-        closeSilently(dbOptions);
+        bloomFilter.close();
+        columnFamilyHandlesMap.values().forEach(RocksDBStorage::closeSilently);
+        if (columnFamilyOptionsMap != null) {
+            columnFamilyOptionsMap.values().forEach(RocksDBStorage::closeSilently);
+        }
+        closeSilently(rocksDb);
+        closeSilently(dbOptions);
     }

Adjust visibility and nullability as needed, but the key point is to ensure the options are eventually closed.

Also applies to: 163-168

server/src/test/java/com/linecorp/centraldogma/server/storage/encryption/DefaultEncryptionStorageManagerTest.java (1)

52-79: Tests exercise WDEK generation, storage, and versioned removal well.

The updated tests correctly:

  • Pass a kekId into DefaultEncryptionStorageManager.
  • Use WrappedDekDetails built from generateWdek().join() and kekId().
  • Decode the wrapped DEK to assert the expected prefix and AES-256 key length.
  • Verify first-time store, duplicate-store failure, successful retrieval, and versioned removal, plus the no-op behavior when removing a non-existent WDEK.

You might optionally extract a small helper like newWdekDetails() to remove the duplicated new WrappedDekDetails(...) construction, but functionally the tests look strong.

Also applies to: 83-116

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 841a90d and 2840b13.

📒 Files selected for processing (14)
  • server/src/main/java/com/linecorp/centraldogma/server/command/CreateProjectCommand.java (5 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/command/CreateRepositoryCommand.java (5 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/command/StandaloneCommandExecutor.java (7 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionUtil.java (4 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/internal/admin/service/DefaultLogoutService.java (1 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/DefaultEncryptionStorageManager.java (2 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/EncryptionStorageManager.java (6 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/NoopEncryptionStorageManager.java (4 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/RepositoryEncryptionStorage.java (1 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/RocksDBStorage.java (1 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1 hunks)
  • server/src/test/java/com/linecorp/centraldogma/server/internal/storage/repository/git/EncryptedGitRepositoryRemoveTest.java (3 hunks)
  • server/src/test/java/com/linecorp/centraldogma/server/internal/storage/repository/git/GitRepositoryTest.java (2 hunks)
  • server/src/test/java/com/linecorp/centraldogma/server/storage/encryption/DefaultEncryptionStorageManagerTest.java (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • server/src/test/java/com/linecorp/centraldogma/server/internal/storage/repository/git/EncryptedGitRepositoryRemoveTest.java
  • server/src/test/java/com/linecorp/centraldogma/server/internal/storage/repository/git/GitRepositoryTest.java
  • server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/RepositoryEncryptionStorage.java
🧰 Additional context used
🧬 Code graph analysis (5)
server/src/main/java/com/linecorp/centraldogma/server/command/StandaloneCommandExecutor.java (1)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)
  • WrappedDekDetails (30-153)
server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionUtil.java (1)
server/src/main/java/com/linecorp/centraldogma/server/auth/SessionKey.java (1)
  • SessionKey (41-136)
server/src/main/java/com/linecorp/centraldogma/server/command/CreateProjectCommand.java (1)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)
  • WrappedDekDetails (30-153)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/DefaultEncryptionStorageManager.java (2)
server/src/main/java/com/linecorp/centraldogma/server/auth/SessionKey.java (1)
  • SessionKey (41-136)
server/src/main/java/com/linecorp/centraldogma/server/auth/SessionMasterKey.java (1)
  • SessionMasterKey (28-107)
server/src/main/java/com/linecorp/centraldogma/server/command/CreateRepositoryCommand.java (1)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)
  • WrappedDekDetails (30-153)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: build-ubuntu-latest-jdk-17-min-java-11
  • GitHub Check: build-ubuntu-latest-jdk-17-coverage
  • GitHub Check: flaky-tests
  • GitHub Check: build-ubuntu-latest-jdk-11
  • GitHub Check: build-ubuntu-latest-jdk-21-snapshot
  • GitHub Check: docker-build
  • GitHub Check: build-windows-latest-jdk-21
  • GitHub Check: build-macos-latest-jdk-21
  • GitHub Check: lint
🔇 Additional comments (12)
server/src/main/java/com/linecorp/centraldogma/server/internal/admin/service/DefaultLogoutService.java (1)

71-111: Async composition is now correct.

The previous critical issues have been properly addressed:

  • Line 71 correctly uses thenCompose instead of thenApply
  • Lines 81-85 use proper runtime null checks instead of assertions
  • All async branches consistently return CompletionStage<HttpResponse> via either UnmodifiableFuture.completedFuture() or nested composition chains

The logout flow correctly handles three scenarios: (1) no session, (2) JWT-based session without sessionId, and (3) server-side session with sessionId.

server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionUtil.java (2)

154-164: LGTM! Critical security improvement.

The explicit signature verification using sessionKey.verifier() before extracting claims is essential for preventing signature bypass vulnerabilities. The error handling appropriately returns null for both verification failures and exceptions.


225-225: LGTM!

Making findSessionCookie public allows external components to access session cookies, which aligns with the key rotation functionality introduced in this PR.

server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/NoopEncryptionStorageManager.java (2)

18-27: No-op WDEK and session-key surface matches the interface.

The added imports and the wdeks(), storeWdek(...), rotateWdek(...), removeWdek(...), and addSessionKeyListener(...) methods are implemented as true no-ops/empty results, which is appropriate for a disabled EncryptionStorageManager and keeps the implementation aligned with the interface evolution.

Also applies to: 94-97, 112-121, 169-172


73-77: Nullable session master key and session key methods rely on upstream guards.

getCurrentSessionMasterKey() and getSessionKey(int) returning null/completed null is consistent with enabled() == false and encryptSessionCookie() == false here. Just ensure all call sites continue to gate on those flags before dereferencing the values so the noop manager can never cause NPEs at runtime.

Also applies to: 84-92

server/src/main/java/com/linecorp/centraldogma/server/command/StandaloneCommandExecutor.java (3)

39-40: WrappedDekDetails import is consistent with new encryption API.

The added import cleanly reflects the move to the richer WDEK metadata type and matches its usage below.


210-216: New rotation commands are correctly dispatched.

RotateWdekCommand and RotateSessionMasterKeyCommand are integrated into doExecute0 in the same style as existing commands, delegating to dedicated helpers and returning CompletableFuture<Void>, so the replication/execution model stays consistent.


359-382: Migration WDEK storage/cleanup is aligned with versioned keys.

In migrateToEncryptedRepository, storing the WDEK via c.wdekDetails() and removing it with the specific dekVersion() on failure mirrors the project/repository creation flow and is safely guarded by the earlier enabled() check in doExecute0. The error handling and logging behavior look appropriate.

server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/RocksDBStorage.java (1)

51-61: Column family naming and handle validation are solid.

The constants for column family names, the single ALL_COLUMN_FAMILY_NAMES list, and the post-open validation that every expected name has a handle give good safety around the RocksDB schema. Building the handle map by decoding handle.getName() and failing fast if any are missing is a robust pattern.

Also applies to: 96-117

server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)

30-152: Immutable WDEK details model with redacted logging looks good.

WrappedDekDetails is a well-structured immutable value object: constructors validate inputs (including positive dekVersion), JSON properties are explicitly annotated, equality/hashCode cover all fields, and toString() redacts the sensitive wrappedDek while still exposing useful metadata. This fits the new WDEK-centric APIs cleanly.

server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/DefaultEncryptionStorageManager.java (1)

38-60: Constructor wiring and delegation to storage components look solid

The new constructor wiring (kekId, single KeyWrapper, shared RocksDBStorage) and the added methods that delegate to SessionKeyStorage / RepositoryEncryptionStorage are cohesive and keep DefaultEncryptionStorageManager as a thin coordinator. I don’t see functional or lifecycle issues in these changes from this class’s perspective.

Also applies to: 77-196

server/src/main/java/com/linecorp/centraldogma/server/command/CreateRepositoryCommand.java (1)

43-60: Confirm backward compatibility strategy for wdekDetails property change

The invariant checks and equality semantics are correct. However, the search found no @JsonAlias, @JsonAnySetter, or custom deserializer in the Command classes, and no evidence of a deserialization bridge for the wdekwdekDetails property name/shape change.

If serialized CreateRepositoryCommand or CreateProjectCommand instances with the old "wdek" property exist in any deployment, they will fail to deserialize with the current @JsonProperty("wdekDetails") constructor.

Confirm whether:

  • This is the first release with WDEK support (no legacy serialized commands in the wild), or
  • A migration layer or deserialization compatibility path exists for legacy payloads.

If the latter is needed, a simple annotation change won't suffice; custom logic will be required.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/EncryptionStorageManager.java (3)

88-91: Clarify what the returned String represents.

The Javadoc doesn't explain what the String in CompletableFuture<String> represents. Based on the PR context, this appears to be the version of the newly generated WDEK.

Consider updating the Javadoc:

 /**
- * Generates a new data encryption key (DEK) and wraps it.
+ * Generates a new data encryption key (DEK), wraps it, and returns the new WDEK version.
  */
 CompletableFuture<String> generateWdek();

93-96: Document the version parameter.

The Javadoc doesn't explain the purpose of the version parameter.

Consider updating the Javadoc:

 /**
- * Generates a new session master key.
+ * Generates a new session master key for the specified version.
+ *
+ * @param version the version number for the new session master key
  */
 CompletableFuture<SessionMasterKey> generateSessionMasterKey(int version);

151-154: Include the version parameter in the Javadoc.

The method signature includes a version parameter, but the Javadoc only mentions "project and repository". This was flagged in a previous review.

Consider updating the Javadoc:

 /**
- * Removes the wrapped data encryption key (WDEK) for the specified project and repository.
+ * Removes the wrapped data encryption key (WDEK) for the specified project, repository, and version.
  */
 void removeWdek(String projectName, String repoName, int version);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2840b13 and bc538dd.

📒 Files selected for processing (2)
  • server/src/main/java/com/linecorp/centraldogma/server/command/CreateProjectCommand.java (5 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/EncryptionStorageManager.java (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/src/main/java/com/linecorp/centraldogma/server/command/CreateProjectCommand.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: docker-build
  • GitHub Check: build-ubuntu-latest-jdk-17-min-java-11
  • GitHub Check: flaky-tests
  • GitHub Check: build-ubuntu-latest-jdk-11
  • GitHub Check: build-macos-latest-jdk-21
  • GitHub Check: build-ubuntu-latest-jdk-21-snapshot
  • GitHub Check: build-ubuntu-latest-jdk-17-coverage
  • GitHub Check: build-windows-latest-jdk-21
  • GitHub Check: lint
🔇 Additional comments (1)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/EncryptionStorageManager.java (1)

139-149: Great fix! The signature inconsistency has been addressed.

The previous review concern about storeWdek having duplicate parameters (explicit projectName, repoName alongside WrappedDekDetails) has been resolved. The signature now only takes WrappedDekDetails, eliminating the risk of mismatched metadata.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
server/src/test/java/com/linecorp/centraldogma/server/internal/storage/repository/git/EncryptedGitRepositoryRemoveTest.java (2)

125-135: Clarify the "current" key concept in comments.

The WDEK rotation logic is correct. However, the comment on line 132 references a "current" key without explanation. Consider clarifying that "current" is a pointer/metadata entry to the active DEK version, distinct from the versioned keys themselves.

For example:

-        wdekSize = 3; // version 1, version 2, and current
+        wdekSize = 3; // version 1, version 2, and current pointer (points to v2)

This helps future maintainers understand that the storage maintains both historical versions (for decrypting old data) and a current pointer (for encrypting new data).


145-145: Consider clarifying the "current" key breakdown.

Similar to the comment on line 132, consider clarifying what "current" represents here for improved readability:

-        wdekSize = 3 + 2; // first repo has 3 keys (v1, v2, current), second repo has 2 keys (v1, current)
+        wdekSize = 3 + 2; // first repo: v1, v2, current→v2; second repo: v1, current→v1
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc538dd and 99faab4.

📒 Files selected for processing (1)
  • server/src/test/java/com/linecorp/centraldogma/server/internal/storage/repository/git/EncryptedGitRepositoryRemoveTest.java (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
server/src/test/java/com/linecorp/centraldogma/server/internal/storage/repository/git/EncryptedGitRepositoryRemoveTest.java (1)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)
  • WrappedDekDetails (30-153)
🔇 Additional comments (4)
server/src/test/java/com/linecorp/centraldogma/server/internal/storage/repository/git/EncryptedGitRepositoryRemoveTest.java (4)

67-67: LGTM: KEK ID parameter added.

The test correctly initializes the EncryptionStorageManager with a kekId, which is then retrieved consistently via encryptionStorageManager.kekId() when constructing WrappedDekDetails instances.


73-77: LGTM: Initial WDEK setup correctly updated.

The test properly generates a WDEK and constructs a WrappedDekDetails object with version 1 for the initial key, consistent with the new API.


137-142: LGTM: Second repository correctly initialized.

The test correctly creates a second repository with its own WDEK starting at version 1, confirming that WDEK versions are per-repository rather than global.


109-175: LGTM: Comprehensive test coverage of WDEK lifecycle.

The test effectively validates:

  • Initial WDEK generation and storage
  • WDEK rotation with version increment
  • Per-repository WDEK management
  • Proper cleanup on repository deletion
  • Retention of historical WDEK versions (necessary for decrypting existing data)

The assertions correctly track the expected state at each stage of the test.

@minwoox minwoox added this to the 0.78.0 milestone Nov 17, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionCookieHandler.java (1)

57-71: Harden sessionKey lifecycle against unexpected nulls (follow-up on earlier review)

The encrypted-cookie path assumes sessionKey is never null:

  • In the constructor (Lines 63–67), sessionKey is set from encryptionStorageManager.getCurrentSessionKey().join() and updated via a listener, but neither result is checked for null.
  • In getSessionInfo (Lines 74–88), you assert sessionKey != null, but asserts are typically disabled in production; a null value here would lead to an NPE when calling sessionKey.version() or later when decrypting.

If getCurrentSessionKey() or the listener ever yield null (misconfiguration, partial initialization, or a future refactor), the failure would occur on the first request instead of failing fast at startup.

You can make this safer by enforcing non-null at initialization and in the listener, so any violation surfaces immediately:

     public SessionCookieHandler(BooleanSupplier sessionPropagatorWritableChecker, boolean tlsEnabled,
                                 EncryptionStorageManager encryptionStorageManager) {
         this.sessionPropagatorWritableChecker =
                 requireNonNull(sessionPropagatorWritableChecker, "sessionPropagatorWritableChecker");
         this.encryptionStorageManager = requireNonNull(encryptionStorageManager, "encryptionStorageManager");
         sessionCookieName = sessionCookieName(tlsEnabled, encryptionStorageManager.encryptSessionCookie());
         if (encryptionStorageManager.encryptSessionCookie()) {
-            sessionKey = encryptionStorageManager.getCurrentSessionKey().join();
-            encryptionStorageManager.addSessionKeyListener(sessionKey -> this.sessionKey = sessionKey);
+            sessionKey = requireNonNull(encryptionStorageManager.getCurrentSessionKey().join(),
+                                        "initial session key must not be null");
+            encryptionStorageManager.addSessionKeyListener(
+                    newSessionKey -> this.sessionKey = requireNonNull(newSessionKey, "sessionKey"));
             jwtClaimsVerifier = new DefaultJWTClaimsVerifier<>(new Builder().issuer("dogma").build(),
                                                                ImmutableSet.of("exp"));
         } else {
             sessionKey = null;
             jwtClaimsVerifier = null;
         }
     }

     public CompletableFuture<SessionInfo> getSessionInfo(ServiceRequestContext ctx) {
         if (encryptionStorageManager.encryptSessionCookie()) {
             final SessionKey sessionKey = this.sessionKey;
-            assert sessionKey != null;
+            assert sessionKey != null;
             final Cookie sessionCookie = findSessionCookie(ctx, sessionCookieName);
             if (sessionCookie == null) {
                 return UnmodifiableFuture.completedFuture(null);
             }
             final int sessionKeyVersion = getSessionKeyVersion(ctx, sessionCookie.value());
             if (sessionKeyVersion <= 0) {
                 return UnmodifiableFuture.completedFuture(null);
             }
             if (sessionKeyVersion == sessionKey.version()) {
                 return CompletableFuture.completedFuture(getSessionInfo(ctx, sessionKey));
             }
             return encryptionStorageManager.getSessionKey(sessionKeyVersion).thenApply(fetchedSessionKey -> {
                 return getSessionInfo(ctx, fetchedSessionKey);
             });
         }
         final String sessionId = getSessionIdFromCookie(ctx, sessionCookieName);
         return sessionId != null ? CompletableFuture.completedFuture(new SessionInfo(sessionId, null, null))
                                  : UnmodifiableFuture.completedFuture(null);
     }

With this change, any unexpected null session key is caught where it originates, and the existing assert stays as an additional sanity check.

Also applies to: 74-98

🧹 Nitpick comments (3)
server/src/main/java/com/linecorp/centraldogma/server/internal/api/sysadmin/KeyManagementService.java (2)

61-83: Clarify behavior for reencrypt=true and consider user-friendlier failure

The rotation path for reencrypt=true (Line 67) intentionally throws UnsupportedOperationException, which will surface as a 5xx. If this flag is exposed to clients, consider failing with a clearer client-facing error (e.g., 400/501) and message so sysadmins don’t misinterpret it as a server bug.

Example change:

-        if (reencrypt) {
-            // TODO(minwoox): Implement re-encryption logic.
-            throw new UnsupportedOperationException();
-        } else {
+        if (reencrypt) {
+            // TODO(minwoox): Implement re-encryption logic.
+            throw new IllegalArgumentException("reencrypt=true is not supported yet.");
+        } else {

The concurrency comment around newVersion = currentDek.version() + 1 aligns with the documented storage invariant.


104-116: Avoid blocking with join() in rotateSessionMasterKey for consistency

rotateSessionMasterKey() currently blocks on generateSessionMasterKey(...).join() (Lines 113–114) even though the method is already async and annotated @Blocking. For consistency with rotateWdek and cleaner error propagation, consider composing the returned CompletableFuture instead of joining:

-        final SessionMasterKey newSessionMasterKey =
-                encryptionStorageManager.generateSessionMasterKey(currentSessionMasterKey.version() + 1).join();
-        return execute(Command.rotateSessionMasterKey(newSessionMasterKey));
+        final int nextVersion = currentSessionMasterKey.version() + 1;
+        return encryptionStorageManager.generateSessionMasterKey(nextVersion)
+                                       .thenCompose(newSessionMasterKey ->
+                                               execute(Command.rotateSessionMasterKey(newSessionMasterKey)));

This keeps the method fully asynchronous and avoids wrapping errors in CompletionException via join().

server/src/test/java/com/linecorp/centraldogma/server/internal/api/sysadmin/KeyManagementServiceTest.java (1)

69-98: Strong end-to-end tests; a few minor robustness/readability tweaks

The extension setup and both tests provide good coverage of WDEK rotation and session master key rotation flows. A couple of minor, optional refinements could make failures easier to diagnose and the code clearer:

  1. Assert on repository creation response (Lines 105–112)
    createRepository(...) currently returns the aggregated response but doesn’t validate it. Adding an assertion ensures setup failures are caught early in the test:

     private static AggregatedHttpResponse createRepository(WebClient client, String repoName) {
         final RequestHeaders headers = RequestHeaders.of(HttpMethod.POST, PROJECTS_PREFIX + "/foo" + REPOS,
                                                          HttpHeaderNames.CONTENT_TYPE, MediaType.JSON);
         final String body = "{\"name\": \"" + repoName + "\"," +
                             " \"encrypt\": true}";
    
  •    return client.execute(headers, body).aggregate().join();
    
  •    final AggregatedHttpResponse response = client.execute(headers, body).aggregate().join();
    
  •    assertThat(response.status()).isEqualTo(HttpStatus.CREATED);
    
  •    return response;
    
    }
    
    (Adjust expected status if the API uses `200 OK` instead of `201 Created`.)
    
    
  1. Check creation timestamp ordering, not just inequality (Lines 172–214)
    In rotateSessionMasterKey, you assert the creation strings differ; asserting ordering as Instant makes the intent clearer:

  •    final Instant initialCreation = Instant.parse(initialDetails.creation());
       // Rotate the session master key
       final AggregatedHttpResponse rotateResponse =
               dogma.httpClient().execute(RequestHeaders.of(HttpMethod.POST,
                       API_V1_PATH_PREFIX + "/masterkeys/session/rotate"))
                    .aggregate().join();
       assertThat(rotateResponse.status()).isSameAs(HttpStatus.NO_CONTENT);
    
       // Get the session master key details after rotation
       final ResponseEntity<SessionMasterKeyDto> response2 =
               dogma.httpClient().blocking().prepare().get(API_V1_PATH_PREFIX + "/masterkeys/session")
                    .asJson(SessionMasterKeyDto.class)
                    .execute();
       assertThat(response2.status()).isSameAs(HttpStatus.OK);
       final SessionMasterKeyDto rotatedDetails = response2.content();
    
       // Verify the version has been incremented
       assertThat(rotatedDetails.version()).isEqualTo(2);
       assertThat(rotatedDetails.kekId()).isEqualTo("kekId");
       assertThat(rotatedDetails.creation()).isNotNull();
    
  •    // The creation timestamp should be different (later) for the new key
    
  •    assertThat(rotatedDetails.creation()).isNotEqualTo(initialDetails.creation());
    
  •    // The creation timestamp should be later for the new key
    
  •    final Instant rotatedCreation = Instant.parse(rotatedDetails.creation());
    
  •    assertThat(rotatedCreation).isAfter(initialCreation);
    
    
    You’d need `import java.time.Instant;` at the top.
    
    
  1. Clarify naming in WrappedDek helper (Lines 277–295)
    For readability, consider renaming pro to projectName:

  •    private final String pro;
    
  •    private final String projectName;
       private final String repoName;
       private final int dekVersion;
       private final String kekId;
    
  •    WrappedDek(String pro, String repoName, int dekVersion, String kekId) {
    
  •        this.pro = pro;
    
  •    WrappedDek(String projectName, String repoName, int dekVersion, String kekId) {
    
  •        this.projectName = projectName;
           this.repoName = repoName;
           this.dekVersion = dekVersion;
           this.kekId = kekId;
       }
    
       @Override
       public int hashCode() {
    
  •        return Objects.hash(pro, repoName, dekVersion, kekId);
    
  •        return Objects.hash(projectName, repoName, dekVersion, kekId);
       }
    
       @Override
       public boolean equals(Object obj) {
           if (this == obj) {
               return true;
           }
           if (obj == null || getClass() != obj.getClass()) {
               return false;
           }
           final WrappedDek other = (WrappedDek) obj;
    
  •        return pro.equals(other.pro) &&
    
  •        return projectName.equals(other.projectName) &&
                  repoName.equals(other.repoName) &&
                  dekVersion == other.dekVersion &&
                  kekId.equals(other.kekId);
       }
    
    
    

All of the above are optional; the existing tests are already valuable as-is.

Also applies to: 105-112, 168-248, 277-295

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 99faab4 and 8879076.

📒 Files selected for processing (5)
  • server/src/main/java/com/linecorp/centraldogma/server/command/StandaloneCommandExecutor.java (7 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionCookieHandler.java (1 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/internal/api/sysadmin/KeyManagementService.java (1 hunks)
  • server/src/main/java/com/linecorp/centraldogma/server/internal/api/sysadmin/SessionMasterKeyDto.java (1 hunks)
  • server/src/test/java/com/linecorp/centraldogma/server/internal/api/sysadmin/KeyManagementServiceTest.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
server/src/main/java/com/linecorp/centraldogma/server/command/StandaloneCommandExecutor.java (1)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)
  • WrappedDekDetails (30-153)
server/src/main/java/com/linecorp/centraldogma/server/internal/api/sysadmin/KeyManagementService.java (3)
server/src/main/java/com/linecorp/centraldogma/server/auth/SessionMasterKey.java (1)
  • SessionMasterKey (28-107)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/SecretKeyWithVersion.java (1)
  • SecretKeyWithVersion (28-63)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)
  • WrappedDekDetails (30-153)
server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionCookieHandler.java (2)
server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionUtil.java (1)
  • SessionUtil (59-313)
server/src/main/java/com/linecorp/centraldogma/server/auth/SessionKey.java (1)
  • SessionKey (41-136)
server/src/test/java/com/linecorp/centraldogma/server/internal/api/sysadmin/KeyManagementServiceTest.java (11)
common/src/main/java/com/linecorp/centraldogma/internal/api/v1/HttpApiV1Constants.java (1)
  • HttpApiV1Constants (22-48)
server/src/main/java/com/linecorp/centraldogma/server/internal/admin/auth/SessionUtil.java (1)
  • SessionUtil (59-313)
server/src/main/java/com/linecorp/centraldogma/server/internal/storage/MigratingMetaToDogmaRepositoryService.java (1)
  • MigratingMetaToDogmaRepositoryService (53-226)
testing-internal/src/main/java/com/linecorp/centraldogma/testing/internal/auth/TestAuthMessageUtil.java (1)
  • TestAuthMessageUtil (44-146)
server/src/main/java/com/linecorp/centraldogma/server/EncryptionAtRestConfig.java (1)
  • EncryptionAtRestConfig (30-86)
server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/git/rocksdb/EncryptionGitStorage.java (1)
  • EncryptionGitStorage (58-398)
server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/git/rocksdb/GitObjectMetadata.java (1)
  • GitObjectMetadata (29-101)
server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/git/rocksdb/RocksDbRepository.java (1)
  • RocksDbRepository (36-129)
server/src/main/java/com/linecorp/centraldogma/server/storage/encryption/WrappedDekDetails.java (1)
  • WrappedDekDetails (30-153)
server/src/main/java/com/linecorp/centraldogma/server/storage/project/InternalProjectInitializer.java (1)
  • InternalProjectInitializer (66-314)
testing/junit/src/main/java/com/linecorp/centraldogma/testing/junit/CentralDogmaExtension.java (1)
  • CentralDogmaExtension (60-278)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: build-ubuntu-latest-jdk-17-min-java-11
  • GitHub Check: docker-build
  • GitHub Check: build-ubuntu-latest-jdk-11
  • GitHub Check: build-windows-latest-jdk-21
  • GitHub Check: build-macos-latest-jdk-21
  • GitHub Check: build-ubuntu-latest-jdk-17-coverage
  • GitHub Check: build-ubuntu-latest-jdk-21-snapshot
  • GitHub Check: flaky-tests
  • GitHub Check: lint
🔇 Additional comments (7)
server/src/main/java/com/linecorp/centraldogma/server/command/StandaloneCommandExecutor.java (4)

39-39: LGTM!

The import of WrappedDekDetails supports the refactoring from raw byte arrays to a structured data model with versioning and KEK ID tracking.


210-217: LGTM!

The command dispatch logic follows the established pattern in this executor and properly routes the new rotation commands to their respective handler methods.


247-250: LGTM!

The refactoring from raw byte arrays to WrappedDekDetails provides a cleaner API with proper versioning and KEK ID tracking. The changes are applied consistently across project creation, repository creation, and encryption migration flows.

Also applies to: 312-316, 367-367


258-259: LGTM!

The cleanup paths correctly pass dekVersion to removeWdek, ensuring that the specific WDEK version stored during the operation is removed when failures occur. This maintains storage consistency.

Also applies to: 324-325, 372-373

server/src/main/java/com/linecorp/centraldogma/server/internal/api/sysadmin/SessionMasterKeyDto.java (1)

29-78: SessionMasterKeyDto validation and surface look solid

Constructor enforces positive version and non-null kekId/creation, and the DTO exposes only non-sensitive metadata. toString() is safe given there’s no key material or salt here. No changes needed.

server/src/main/java/com/linecorp/centraldogma/server/internal/api/sysadmin/KeyManagementService.java (2)

45-50: Constructor invariants are appropriate

Requiring a non-null, enabled EncryptionStorageManager at construction time is a good guardrail so this sysadmin API is only exposed when encryption-at-rest is configured.


89-99: Session master key metadata exposure is minimal and consistent

getSessionMasterKeyDetails() (Lines 89–99) returns only version, KEK ID, and creation timestamp, matching SessionMasterKeyDto and avoiding exposure of wrapped key material or salt. The encryptSessionCookie() guard is also appropriate. Looks good.

Copy link
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

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

Understood that with the current changes, the rotated key is used to encrypt new sessions/objects.

Conversely, each session/object is tagged with the master dek version and queries the appropriate dek when unencrypting.

/**
* Re-wraps the given wrapped data encryption key (WDEK).
*/
CompletableFuture<String> rewrap(String wdek, String oldKekId, String newKekId);
Copy link
Contributor

Choose a reason for hiding this comment

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

Note) No issue with this API, but it seems like it isn't called from anywhere

Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

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

👍👍

* @param wrappedMasterKey the wrapped session master key
* @param version the version of the session master key
* @param salt the salt used to derive session keys from the master key. It's encoded in base64.
* @param kekId the key encryption key (KEK) ID used to wrap the
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: an incomplete sentence?

private final int version;
private final String salt;
private final String kekId;
private final String creation;
Copy link
Contributor

Choose a reason for hiding this comment

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

How about using Instant for type-safety?

.add("projectName", projectName);
if (wdek != null) {
toStringHelper.add("wdek", "[***]");
if (wdekDetails != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: A null value can be set to .add()

final String csrfTokenFromSignedJwt = csrfTokenFromSignedJwt(signedJwt);
return new SessionInfo(null, subject, csrfTokenFromSignedJwt);

if (sessionKeyVersion == sessionKey.version()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I understand that a session issued by the new key may not be able to access servers whose key has not been updated yet while the new key is being replicated.

Technically, we could specify the exact time when the sessionMasterKey should take effect, but since key rotation rarely happens and replication completes within a short period, I don’t think it’s a significant issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants