zhaohai666 opened a new pull request, #4219:
URL: https://github.com/apache/rocketmq-dashboard/pull/4219
# feat(studio): seal cloud credential secrets with AES-GCM and gate reveal
behind re-auth
## Problem
Cloud provider credentials (AK/SK for Aliyun / Tencent instances) are stored
in `rmq_cloud_credential.secret_key` **base64-encoded**. Base64 is an encoding,
not confidentiality: anyone with read access to the table (a SQL dump, a
backup, a co-located process) recovers every provider secret key trivially. On
top of that, the reveal endpoint (`GET
/api/cloud-credentials/{id}/credentials`) trusts the admin session alone — a
replayed or stolen session cookie is enough to exfiltrate all stored keys, with
no audit trail beyond the access itself.
## Solution
Two focused hardening steps, deliberately small in surface:
### 1. AES-256-GCM encryption at rest
- New `CredentialCipher` seals secrets as `enc:v1:<base64(iv || ciphertext
|| tag)>`:
- a fresh random 96-bit IV per value, so identical secrets never collide
in the DB;
- GCM is authenticated — a tampered row **fails closed** (clean 500)
instead of returning corrupted key material.
- The key comes from `studio.credential.encryption-key`
(`STUDIO_CREDENTIAL_ENCRYPTION_KEY`), a Base64-encoded 32-byte AES key; invalid
keys fail fast at startup. When unset (local/dev convenience), a deterministic
development key is derived and a **loud WARNING** names the exact property to
set in production.
- `MybatisPlusCloudCredentialRepository` now encrypts on write and decrypts
on read. In-memory state stays plaintext; only the persisted column changes.
- **Backward compatibility**: rows written by the legacy base64 scheme (no
`enc:v1:` prefix) keep working through the tolerant decode path.
- **`CredentialEncryptionMigration`** (startup `ApplicationRunner`,
idempotent) re-seals every legacy row once, so an upgraded deployment ends up
fully encrypted without manual edits. Failures are logged, not thrown — a
hardening step must not brick startup.
### 2. Reveal requires a second authentication
`GET /api/cloud-credentials/{id}/credentials` now additionally requires the
operator's password in the `X-Studio-Reauth` header:
- The password is verified **against the session principal**
(`AuthenticatedUserContext`), never against a caller-supplied user name — it
cannot be abused to probe another account's password.
- Reveal is already admin-gated by `AuthInterceptor`; this adds a second
factor so a stolen admin session cookie alone cannot read provider keys.
- Every attempt is audited (`REVEAL_CLOUD_CREDENTIAL`, SUCCESS and FAILURE
with the denial reason).
- The password travels in a header, never in the URL/query string; the
response keeps `Cache-Control: no-store`.
**Out of scope by design**: gRPC channel auto-signing is untouched.
## Runtime verification (live dashboard + H2 file DB)
| Check | Result |
|---|---|
| DB content after create | `secret_key = enc:v1:Fic02WL…` (plaintext never
persisted) |
| Reveal without header | `403 Password confirmation is required…` |
| Reveal with wrong password | `403 Password confirmation failed` |
| Reveal with correct password | `200` + plaintext secret (decrypt path
works) |
| Audit trail | 3 rows: `REVEAL_CLOUD_CREDENTIAL` 2× FAILURE + 1× SUCCESS |
| Restart with same key | decrypts correctly across restarts |
## Testing
- New `CredentialCipherTest` (12): round-trip, IV freshness, tamper
rejection, wrong-key rejection, legacy base64/plain read, key validation,
fallback stability.
- New `CredentialEncryptionMigrationTest` (3): re-seals legacy rows only,
skips sealed rows, never propagates failure.
- `MybatisPlusCloudCredentialRepositoryTest`: real cipher, asserts sealed
ciphertext on write and decryption on read, legacy rows still readable.
- `CloudCredentialServiceTest` / `CloudCredentialControllerTest` /
`AuthServiceTest` / `AuthCredentialAuthorizationIntegrationTest`: updated for
the re-auth contract (fail-closed, audited denial, header forwarding).
- Focused suite **69/69 green**; full server suite shows no new failures
(only the 2 pre-existing `AuthCorsIntegrationTest` failures).
## Configuration
```yaml
studio:
credential:
# Base64-encoded 32-byte AES-256 key. Generate with: openssl rand
-base64 32
encryption-key: ${STUDIO_CREDENTIAL_ENCRYPTION_KEY:}
```
## Checklist
- [x] No dependency changes; AES-GCM is JDK built-in
- [x] Legacy base64 rows keep working and are migrated automatically
- [x] Reveal: admin gate + password re-auth + audit, fail-closed
- [x] gRPC channel auto-signing untouched
- [x] Unit tests + live runtime verification (ciphertext inspected directly
in the DB)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]