Summary
GeoServer 3.0.0’s parallel data directory loader does not decrypt encrypted fields belonging to CoverageStoreInfo objects after loading the catalog. This leaves COG coverage-store passwords represented as crypt2: strings in the live catalog after every restart.
The affected stores then fail REST serialization and cannot authenticate when reading the remote COG.
Environment
- GeoServer 3.0.0
- Java 21
- Tomcat 11
- COG extension
- File-based GeoServer data directory
- Reproduced with COG GeoTIFF coverage stores using HTTP Basic authentication
- Initially observed while migrating an existing GeoServer 2.x data directory to GeoServer 3.0.0
- The security directory and encryption keystore were copied intact
Steps to reproduce
-
Configure a COG CoverageStoreInfo with an encrypted connection parameter supplied by an EncryptedFieldsProvider. For the COG extension, this is the “password” entry in the coverage store connection parameters.
-
Confirm that coveragestore.xml contains an encrypted value beginning with crypt2:.
-
Confirm that the COG can be rendered before restarting GeoServer.
-
Restart GeoServer 3.0.0.
-
Request the coverage store through REST, for example:
GET /geoserver/rest/workspaces/{workspace}/coveragestores/{store}.json
-
Attempt to render the COG through WMS.
Actual result
The REST request returns HTTP 500:
Cannot encode a password with prefix: crypt2:
The live CoverageStoreInfo still contains the encrypted crypt2: representation. When the COG reader uses that value as the HTTP password, the remote range request fails authentication and GeoServer reports errors such as:
java.lang.RuntimeException: Unable to read header for https://example.test/path/image.tif
at it.geosolutions.imageioimpl.plugins.cog.HttpRangeReader.readHeader(…)
Updating the store password through REST makes the layer work in the live catalog, but without the loader fix the problem returns after restart.
Expected result
All StoreInfo encrypted fields registered through EncryptedFieldsProvider should be decrypted on the main thread after the parallel catalog load. Coverage stores should work before and after restart, and their REST endpoints should return HTTP 200.
Root cause
XStreamLoader deliberately disables password-field decryption during parallel deserialization and documents that password decryption for StoreInfo objects is deferred until all stores have been loaded.
However, DataDirectoryGeoServerLoader.decryptStorePasswords() currently processes only:
- HTTPStoreInfo
- DataStoreInfo
It does not process CoverageStoreInfo.
Consequently, encrypted COG connection parameters selected by CogEncryptedFieldsProvider are never decrypted after catalog loading.
Relevant source:
Proposed fix
Process coverage stores through the existing ConfigurationPasswordEncryptionHelper after catalog loading:
catalog.getCoverageStores().stream()
.map(ModificationProxy::unwrap)
.forEach(this::decodePassword);
Generalize decodePassword(DataStoreInfo) to accept StoreInfo, and preload EncryptedFieldsProvider with the other catalog-loading extensions to avoid extension initialization during the loading process.
Validation
We applied this change to GeoServer 3.0.0 and added a regression test that:
- Registers an EncryptedFieldsProvider for a CoverageStoreInfo connection parameter.
- Persists a coverage store and verifies that its XML contains an encrypted value.
- Loads the catalog through DataDirectoryGeoServerLoader.
- Verifies that the in-memory connection parameter contains the original plaintext value.
The focused DataDirectoryGeoServerLoaderTest suite passed: 25 tests, 0 failures.
Runtime validation after applying the fix:
- Coverage-store REST GET: HTTP 200
- COG WMS rendering: HTTP 200, image/png
- After another GeoServer restart:
- Coverage-store REST GET remained HTTP 200
- COG WMS rendering remained successful
The encrypted value remains protected when returned or persisted; the fix only restores the expected in-memory decryption during catalog initialization.