This is an automated email from the ASF dual-hosted git repository.
diqiu50 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new 8094542c01 [#13203] improvement(core): Register official cloud and
credential catalog properties in PropertiesMetadata (#13204)
8094542c01 is described below
commit 8094542c017f7f26285c930023f99bc77787ec91
Author: MaSai <[email protected]>
AuthorDate: Thu Sep 17 10:19:16 2026 +0800
[#13203] improvement(core): Register official cloud and credential catalog
properties in PropertiesMetadata (#13204)
### What changes were proposed in this pull request?
This PR registers official Gravitino cloud-storage and
credential-vending property keys in `PropertiesMetadata`, and applies
name-based fuzzy masking only to undeclared keys.
**A. Shared cloud metadata**
- Expand `S3PropertiesMetadata`, `OSSPropertiesMetadata`, and
`AzurePropertiesMetadata` with non-secret credential-config keys
(endpoint, region, role ARN, external ID, etc.).
- Add `COSPropertiesMetadata` for Tencent COS credential-config keys.
- Add `s3/oss/adls-token-expire-in-secs` to
`CredentialConfig.CREDENTIAL_PROPERTY_ENTRIES`.
**B. Catalog metadata merges**
- Merge `CredentialConfig.CREDENTIAL_PROPERTY_ENTRIES` into Iceberg,
Hive, and Paimon catalog properties metadata.
- Merge missing Azure/GCS shared metadata into Paimon.
**C. Fuzzy masking rule**
- Add `SecretPropertyUtils.isSensitivePropertyKey()` for credential-like
property names.
- Update `OperationDispatcher.getHiddenPropertyNames()` to mask
undeclared sensitive-named keys as `******`, but skip fuzzy matching
when `metadata.containsProperty(key)` is true.
- Declared non-secret keys such as `credential-providers`,
`azure-storage-account-name`, and `s3-token-expire-in-secs` remain
visible in `properties()` responses.
**Tests**
- Add `TestCloudPropertiesMetadata` in `core`.
- Extend `TestPaimonCatalogPropertiesMetadata` and
`TestSecretPropertyUtils`.
### Why are the changes needed?
Official property keys defined in constants/docs were missing from
`PropertiesMetadata`. Undeclared keys either bypass metadata-based
masking or get incorrectly masked by name-based fuzzy matching —
breaking the Web UI catalog edit flow for keys like
`credential-providers`, `azure-storage-account-name`, and
`s3-token-expire-in-secs`.
Fix: #13203
### Does this PR introduce _any_ user-facing change?
Yes, but only in masking/visibility behavior:
1. Official non-secret cloud/credential keys are now declared and remain
visible in `properties()` responses.
2. Undeclared properties whose names look credential-like are masked as
`******` on entity read paths.
3. Secret keys continue to be masked via the existing `hidden` flag or
secret URNs.
4. No property keys are added or removed from catalogs — only metadata
registration and masking rules.
### How was this patch tested?
```bash
./gradlew :core:test --tests
org.apache.gravitino.secret.TestSecretPropertyUtils \
--tests org.apache.gravitino.cloud.storage.TestCloudPropertiesMetadata
-PskipITs
./gradlew :catalogs:catalog-lakehouse-paimon:test \
--tests
org.apache.gravitino.catalog.lakehouse.paimon.TestPaimonCatalogPropertiesMetadata
-PskipITs
./gradlew spotlessApply
```
---------
Co-authored-by: Cursor <[email protected]>
---
.../apache/gravitino/secret/SupportsSecrets.java | 6 +-
.../CloudStorageCredentialPropertyKeys.java | 18 +-
.../TestCloudStorageCredentialPropertyKeys.java | 13 +-
.../fileset/FilesetCatalogPropertiesMetadata.java | 3 +-
.../catalog/fileset/FilesetPropertiesMetadata.java | 2 -
.../fileset/FilesetSchemaPropertiesMetadata.java | 2 -
.../fileset/TestFilesetCatalogCredential.java | 14 +-
.../TestFilesetCloudPropertiesMetadata.java | 10 +-
.../glue/GlueCatalogPropertiesMetadata.java | 2 +-
.../glue/TestGlueCatalogPropertiesMetadata.java | 5 +-
.../hive/HiveCatalogPropertiesMetadata.java | 2 +
.../catalog/hive/TestHiveCatalogOperations.java | 15 +-
.../iceberg/IcebergCatalogPropertiesMetadata.java | 2 +
.../TestIcebergCatalogPropertiesMetadata.java | 10 ++
.../paimon/PaimonCatalogPropertiesMetadata.java | 4 +-
.../TestPaimonCatalogPropertiesMetadata.java | 11 +-
.../hadoop/TestBaseGVFSOperationsSecrets.java | 48 ++++++
.../cloud/storage/AzurePropertiesMetadata.java | 16 ++
.../cloud/storage/COSPropertiesMetadata.java | 42 ++++-
.../cloud/storage/OSSPropertiesMetadata.java | 34 +++-
.../cloud/storage/S3PropertiesMetadata.java | 61 ++++++-
.../connector/BaseCatalogPropertiesMetadata.java | 17 +-
.../connector/BasePropertiesMetadata.java | 44 +++--
.../connector/HiddenPropertyMaskUtils.java | 2 +-
.../credential/config/CredentialConfig.java | 40 +++++
.../secret/FallbackPropertiesMetadata.java | 59 +++++++
.../secret/SecretPropertyOperationDispatcher.java | 189 ++++++++++++++-------
.../gravitino/secret/SecretPropertyUtils.java | 72 +++++++-
.../gravitino/TestBasePropertiesMetadata.java | 12 ++
.../cloud/storage/TestCloudPropertiesMetadata.java | 107 ++++++++++++
.../TestBaseCatalogPropertiesMetadata.java | 46 +++++
.../connector/TestHiddenPropertyMaskUtils.java | 16 +-
.../TestSecretPropertyOperationDispatcher.java | 28 +++
.../gravitino/secret/TestSecretPropertyUtils.java | 72 ++++++++
.../trino/connector/TestGravitinoConnector.java | 71 +++++---
35 files changed, 931 insertions(+), 164 deletions(-)
diff --git a/api/src/main/java/org/apache/gravitino/secret/SupportsSecrets.java
b/api/src/main/java/org/apache/gravitino/secret/SupportsSecrets.java
index af9563cf7d..1d0fd56856 100644
--- a/api/src/main/java/org/apache/gravitino/secret/SupportsSecrets.java
+++ b/api/src/main/java/org/apache/gravitino/secret/SupportsSecrets.java
@@ -30,8 +30,10 @@ import java.util.Map;
* also be delivered via {@link
org.apache.gravitino.credential.SupportsCredentials}).
* <li>Stored plaintext for property keys whose names look sensitive
(contain {@code secret},
* {@code password}, {@code token}, {@code credential}, {@code access},
or {@code account},
- * case-insensitive), so mistyped / undeclared credential properties
remain usable after API
- * responses mask them as {@code ******}.
+ * case-insensitive) when the key is undeclared or declared {@code
hidden}, so mistyped /
+ * undeclared credential properties remain usable after API responses
mask them as {@code
+ * ******}. Declared non-hidden configuration keys are not returned even
when the name
+ * matches.
* </ul>
*
* <p>Properties that are only declared {@code hidden} in property metadata,
and whose names do
diff --git
a/catalogs/catalog-common/src/main/java/org/apache/gravitino/storage/CloudStorageCredentialPropertyKeys.java
b/catalogs/catalog-common/src/main/java/org/apache/gravitino/storage/CloudStorageCredentialPropertyKeys.java
index a850e2c370..97bae3cef1 100644
---
a/catalogs/catalog-common/src/main/java/org/apache/gravitino/storage/CloudStorageCredentialPropertyKeys.java
+++
b/catalogs/catalog-common/src/main/java/org/apache/gravitino/storage/CloudStorageCredentialPropertyKeys.java
@@ -26,26 +26,30 @@ import java.util.Set;
import javax.annotation.Nullable;
/**
- * Gravitino property keys for cloud static credentials.
+ * Gravitino property keys for cloud static <em>secret</em> credentials.
*
* <p>GVFS must not consume these keys from REST catalog/schema/fileset {@code
properties()}
- * responses (which may be masked). Clients may still supply credentials via
local Hadoop {@code
- * Configuration} or {@code getCredentials()} when credential vending is
enabled.
+ * responses (which may be masked as {@code ******}). Secret plaintext is
recovered via {@code
+ * getSecrets()}. Non-secret identifiers such as {@code s3-access-key-id} /
{@code
+ * gcs-service-account-file} remain in {@code properties()} and are merged
normally. Clients may
+ * also supply credentials via local Hadoop {@code Configuration} or {@code
getCredentials()} when
+ * credential vending is enabled.
*/
public final class CloudStorageCredentialPropertyKeys {
/** Placeholder returned for masked hidden properties in REST responses. */
public static final String MASKED_PROPERTY_VALUE = "******";
+ /**
+ * Secret-bearing static credential keys only. Access key IDs and GCS
service-account file paths
+ * are intentionally excluded: they are declared non-hidden and must stay
available from {@code
+ * properties()}.
+ */
private static final Set<String> STATIC_CREDENTIAL_KEYS =
ImmutableSet.of(
- S3Properties.GRAVITINO_S3_ACCESS_KEY_ID,
S3Properties.GRAVITINO_S3_SECRET_ACCESS_KEY,
- OSSProperties.GRAVITINO_OSS_ACCESS_KEY_ID,
OSSProperties.GRAVITINO_OSS_ACCESS_KEY_SECRET,
AzureProperties.GRAVITINO_AZURE_STORAGE_ACCOUNT_KEY,
- GCSProperties.GRAVITINO_GCS_SERVICE_ACCOUNT_FILE,
- COSProperties.GRAVITINO_COS_ACCESS_KEY_ID,
COSProperties.GRAVITINO_COS_ACCESS_KEY_SECRET);
private CloudStorageCredentialPropertyKeys() {}
diff --git
a/catalogs/catalog-common/src/test/java/org/apache/gravitino/storage/TestCloudStorageCredentialPropertyKeys.java
b/catalogs/catalog-common/src/test/java/org/apache/gravitino/storage/TestCloudStorageCredentialPropertyKeys.java
index ee54fcf8ae..768890dd8f 100644
---
a/catalogs/catalog-common/src/test/java/org/apache/gravitino/storage/TestCloudStorageCredentialPropertyKeys.java
+++
b/catalogs/catalog-common/src/test/java/org/apache/gravitino/storage/TestCloudStorageCredentialPropertyKeys.java
@@ -34,19 +34,23 @@ public class TestCloudStorageCredentialPropertyKeys {
S3Properties.GRAVITINO_S3_ENDPOINT,
"https://s3.amazonaws.com",
S3Properties.GRAVITINO_S3_ACCESS_KEY_ID,
- CloudStorageCredentialPropertyKeys.MASKED_PROPERTY_VALUE,
+ "AKIATEST",
S3Properties.GRAVITINO_S3_SECRET_ACCESS_KEY,
"secret",
OSSProperties.GRAVITINO_OSS_REGION,
- "cn-hangzhou");
+ "cn-hangzhou",
+ "masked-any-key",
+ CloudStorageCredentialPropertyKeys.MASKED_PROPERTY_VALUE);
Map<String, String> filtered =
CloudStorageCredentialPropertyKeys.omitStaticCredentialProperties(input);
assertEquals("https://s3.amazonaws.com",
filtered.get(S3Properties.GRAVITINO_S3_ENDPOINT));
assertEquals("cn-hangzhou",
filtered.get(OSSProperties.GRAVITINO_OSS_REGION));
- assertFalse(filtered.containsKey(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID));
+ // Access key ID is non-hidden plaintext in properties(); GVFS keeps it.
+ assertEquals("AKIATEST",
filtered.get(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID));
assertFalse(filtered.containsKey(S3Properties.GRAVITINO_S3_SECRET_ACCESS_KEY));
+ assertFalse(filtered.containsKey("masked-any-key"));
}
@Test
@@ -54,6 +58,9 @@ public class TestCloudStorageCredentialPropertyKeys {
assertTrue(
CloudStorageCredentialPropertyKeys.isStaticCredentialKey(
COSProperties.GRAVITINO_COS_ACCESS_KEY_SECRET));
+ assertFalse(
+ CloudStorageCredentialPropertyKeys.isStaticCredentialKey(
+ COSProperties.GRAVITINO_COS_ACCESS_KEY_ID));
assertFalse(
CloudStorageCredentialPropertyKeys.isStaticCredentialKey(
COSProperties.GRAVITINO_COS_REGION));
diff --git
a/catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetCatalogPropertiesMetadata.java
b/catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetCatalogPropertiesMetadata.java
index 141e085a3f..b71a3a34ac 100644
---
a/catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetCatalogPropertiesMetadata.java
+++
b/catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetCatalogPropertiesMetadata.java
@@ -40,7 +40,6 @@ import
org.apache.gravitino.cloud.storage.OSSPropertiesMetadata;
import org.apache.gravitino.cloud.storage.S3PropertiesMetadata;
import org.apache.gravitino.connector.BaseCatalogPropertiesMetadata;
import org.apache.gravitino.connector.PropertyEntry;
-import org.apache.gravitino.credential.config.CredentialConfig;
public class FilesetCatalogPropertiesMetadata extends
BaseCatalogPropertiesMetadata {
@@ -228,7 +227,7 @@ public class FilesetCatalogPropertiesMetadata extends
BaseCatalogPropertiesMetad
// The following two are about authentication.
.putAll(KERBEROS_PROPERTY_ENTRIES)
.putAll(AUTHENTICATION_PROPERTY_ENTRIES)
- .putAll(CredentialConfig.CREDENTIAL_PROPERTY_ENTRIES)
+ // CREDENTIAL_PROPERTY_ENTRIES are registered in
BaseCatalogPropertiesMetadata.
.putAll(S3PropertiesMetadata.PROPERTY_ENTRIES)
.putAll(OSSPropertiesMetadata.PROPERTY_ENTRIES)
.putAll(AzurePropertiesMetadata.PROPERTY_ENTRIES)
diff --git
a/catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetPropertiesMetadata.java
b/catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetPropertiesMetadata.java
index d75abd29ef..bc722f5060 100644
---
a/catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetPropertiesMetadata.java
+++
b/catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetPropertiesMetadata.java
@@ -35,7 +35,6 @@ import
org.apache.gravitino.cloud.storage.OSSPropertiesMetadata;
import org.apache.gravitino.cloud.storage.S3PropertiesMetadata;
import org.apache.gravitino.connector.BasePropertiesMetadata;
import org.apache.gravitino.connector.PropertyEntry;
-import org.apache.gravitino.credential.config.CredentialConfig;
public class FilesetPropertiesMetadata extends BasePropertiesMetadata {
@@ -80,7 +79,6 @@ public class FilesetPropertiesMetadata extends
BasePropertiesMetadata {
false /* hidden */))
.putAll(KERBEROS_PROPERTY_ENTRIES)
.putAll(AUTHENTICATION_PROPERTY_ENTRIES)
- .putAll(CredentialConfig.CREDENTIAL_PROPERTY_ENTRIES)
.putAll(S3PropertiesMetadata.PROPERTY_ENTRIES)
.putAll(OSSPropertiesMetadata.PROPERTY_ENTRIES)
.putAll(AzurePropertiesMetadata.PROPERTY_ENTRIES)
diff --git
a/catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetSchemaPropertiesMetadata.java
b/catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetSchemaPropertiesMetadata.java
index f31f7b34f5..7515d90556 100644
---
a/catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetSchemaPropertiesMetadata.java
+++
b/catalogs/catalog-fileset/src/main/java/org/apache/gravitino/catalog/fileset/FilesetSchemaPropertiesMetadata.java
@@ -32,7 +32,6 @@ import
org.apache.gravitino.cloud.storage.OSSPropertiesMetadata;
import org.apache.gravitino.cloud.storage.S3PropertiesMetadata;
import org.apache.gravitino.connector.BasePropertiesMetadata;
import org.apache.gravitino.connector.PropertyEntry;
-import org.apache.gravitino.credential.config.CredentialConfig;
public class FilesetSchemaPropertiesMetadata extends BasePropertiesMetadata {
@@ -73,7 +72,6 @@ public class FilesetSchemaPropertiesMetadata extends
BasePropertiesMetadata {
false /* reserved */))
.putAll(KERBEROS_PROPERTY_ENTRIES)
.putAll(AUTHENTICATION_PROPERTY_ENTRIES)
- .putAll(CredentialConfig.CREDENTIAL_PROPERTY_ENTRIES)
.putAll(S3PropertiesMetadata.PROPERTY_ENTRIES)
.putAll(OSSPropertiesMetadata.PROPERTY_ENTRIES)
.putAll(AzurePropertiesMetadata.PROPERTY_ENTRIES)
diff --git
a/catalogs/catalog-fileset/src/test/java/org/apache/gravitino/catalog/fileset/TestFilesetCatalogCredential.java
b/catalogs/catalog-fileset/src/test/java/org/apache/gravitino/catalog/fileset/TestFilesetCatalogCredential.java
index 1a1800d0b7..72f9326c52 100644
---
a/catalogs/catalog-fileset/src/test/java/org/apache/gravitino/catalog/fileset/TestFilesetCatalogCredential.java
+++
b/catalogs/catalog-fileset/src/test/java/org/apache/gravitino/catalog/fileset/TestFilesetCatalogCredential.java
@@ -97,26 +97,20 @@ public class TestFilesetCatalogCredential {
FilesetCatalogImpl catalog = newCatalog(properties);
Map<String, String> masked = catalog.properties();
- Assertions.assertEquals(
- HiddenPropertyMaskUtils.MASKED_VALUE,
masked.get(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID));
+ Assertions.assertEquals("s3-ak",
masked.get(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID));
Assertions.assertEquals(
HiddenPropertyMaskUtils.MASKED_VALUE,
masked.get(S3Properties.GRAVITINO_S3_SECRET_ACCESS_KEY));
- Assertions.assertEquals(
- HiddenPropertyMaskUtils.MASKED_VALUE,
- masked.get(OSSProperties.GRAVITINO_OSS_ACCESS_KEY_ID));
+ Assertions.assertEquals("oss-ak",
masked.get(OSSProperties.GRAVITINO_OSS_ACCESS_KEY_ID));
Assertions.assertEquals(
HiddenPropertyMaskUtils.MASKED_VALUE,
masked.get(OSSProperties.GRAVITINO_OSS_ACCESS_KEY_SECRET));
Assertions.assertEquals(
- HiddenPropertyMaskUtils.MASKED_VALUE,
- masked.get(AzureProperties.GRAVITINO_AZURE_STORAGE_ACCOUNT_NAME));
+ "abs-account",
masked.get(AzureProperties.GRAVITINO_AZURE_STORAGE_ACCOUNT_NAME));
Assertions.assertEquals(
HiddenPropertyMaskUtils.MASKED_VALUE,
masked.get(AzureProperties.GRAVITINO_AZURE_STORAGE_ACCOUNT_KEY));
- Assertions.assertEquals(
- HiddenPropertyMaskUtils.MASKED_VALUE,
- masked.get(COSProperties.GRAVITINO_COS_ACCESS_KEY_ID));
+ Assertions.assertEquals("cos-ak",
masked.get(COSProperties.GRAVITINO_COS_ACCESS_KEY_ID));
Assertions.assertEquals(
HiddenPropertyMaskUtils.MASKED_VALUE,
masked.get(COSProperties.GRAVITINO_COS_ACCESS_KEY_SECRET));
diff --git
a/catalogs/catalog-fileset/src/test/java/org/apache/gravitino/catalog/fileset/TestFilesetCloudPropertiesMetadata.java
b/catalogs/catalog-fileset/src/test/java/org/apache/gravitino/catalog/fileset/TestFilesetCloudPropertiesMetadata.java
index 4e121c3b04..515d6849ae 100644
---
a/catalogs/catalog-fileset/src/test/java/org/apache/gravitino/catalog/fileset/TestFilesetCloudPropertiesMetadata.java
+++
b/catalogs/catalog-fileset/src/test/java/org/apache/gravitino/catalog/fileset/TestFilesetCloudPropertiesMetadata.java
@@ -52,12 +52,12 @@ public class TestFilesetCloudPropertiesMetadata {
@ParameterizedTest
@MethodSource("filesetPropertiesMetadata")
void testCloudCredentialsAreHidden(PropertiesMetadata metadata) {
-
assertTrue(metadata.isHiddenProperty(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID));
+
assertFalse(metadata.isHiddenProperty(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID));
assertTrue(metadata.isHiddenProperty(S3Properties.GRAVITINO_S3_SECRET_ACCESS_KEY));
-
assertTrue(metadata.isHiddenProperty(OSSProperties.GRAVITINO_OSS_ACCESS_KEY_ID));
+
assertFalse(metadata.isHiddenProperty(OSSProperties.GRAVITINO_OSS_ACCESS_KEY_ID));
assertTrue(metadata.isHiddenProperty(OSSProperties.GRAVITINO_OSS_ACCESS_KEY_SECRET));
assertTrue(metadata.isHiddenProperty(AzureProperties.GRAVITINO_AZURE_STORAGE_ACCOUNT_KEY));
-
assertTrue(metadata.isHiddenProperty(COSProperties.GRAVITINO_COS_ACCESS_KEY_ID));
+
assertFalse(metadata.isHiddenProperty(COSProperties.GRAVITINO_COS_ACCESS_KEY_ID));
assertTrue(metadata.isHiddenProperty(COSProperties.GRAVITINO_COS_ACCESS_KEY_SECRET));
}
@@ -84,9 +84,7 @@ public class TestFilesetCloudPropertiesMetadata {
HiddenPropertyMaskUtils.maskHiddenProperties(properties, metadata);
assertEquals("s3a://bucket/path",
response.get(FilesetCatalogPropertiesMetadata.LOCATION));
- assertEquals(
- HiddenPropertyMaskUtils.MASKED_VALUE,
- response.get(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID));
+ assertEquals("AKIATEST",
response.get(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID));
assertEquals(
HiddenPropertyMaskUtils.MASKED_VALUE,
response.get(S3Properties.GRAVITINO_S3_SECRET_ACCESS_KEY));
diff --git
a/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueCatalogPropertiesMetadata.java
b/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueCatalogPropertiesMetadata.java
index 7275861ab7..1b485374ee 100644
---
a/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueCatalogPropertiesMetadata.java
+++
b/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueCatalogPropertiesMetadata.java
@@ -65,7 +65,7 @@ public class GlueCatalogPropertiesMetadata extends
BaseCatalogPropertiesMetadata
+ " When omitted the default credential chain is used.",
false /* immutable */,
null /* defaultValue */,
- true /* hidden */))
+ false /* hidden */))
.put(
AWS_SECRET_ACCESS_KEY,
stringOptionalPropertyEntry(
diff --git
a/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueCatalogPropertiesMetadata.java
b/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueCatalogPropertiesMetadata.java
index e2091542c9..328b02bb0e 100644
---
a/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueCatalogPropertiesMetadata.java
+++
b/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueCatalogPropertiesMetadata.java
@@ -70,9 +70,12 @@ class TestGlueCatalogPropertiesMetadata {
}
@Test
- void testCredentialsAreOptional() {
+ void testCredentialsAreOptionalAndAccessKeyIdIsVisible() {
assertFalse(metadata.isRequiredProperty(AWS_ACCESS_KEY_ID));
assertFalse(metadata.isRequiredProperty(AWS_SECRET_ACCESS_KEY));
+ // Access key ID is an identifier, same as s3-access-key-id; only the
secret is hidden.
+ assertFalse(metadata.isHiddenProperty(AWS_ACCESS_KEY_ID));
+ assertTrue(metadata.isHiddenProperty(AWS_SECRET_ACCESS_KEY));
}
@Test
diff --git
a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveCatalogPropertiesMetadata.java
b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveCatalogPropertiesMetadata.java
index 754da95f78..889e450ea1 100644
---
a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveCatalogPropertiesMetadata.java
+++
b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveCatalogPropertiesMetadata.java
@@ -24,6 +24,7 @@ import static
org.apache.gravitino.catalog.hive.HiveConstants.HIVE_DEFAULT_CATAL
import com.google.common.collect.ImmutableMap;
import java.util.Map;
import org.apache.gravitino.cloud.storage.AzurePropertiesMetadata;
+import org.apache.gravitino.cloud.storage.COSPropertiesMetadata;
import org.apache.gravitino.cloud.storage.GCSPropertiesMetadata;
import org.apache.gravitino.cloud.storage.OSSPropertiesMetadata;
import org.apache.gravitino.cloud.storage.S3PropertiesMetadata;
@@ -131,6 +132,7 @@ public class HiveCatalogPropertiesMetadata extends
BaseCatalogPropertiesMetadata
.putAll(OSSPropertiesMetadata.PROPERTY_ENTRIES)
.putAll(AzurePropertiesMetadata.PROPERTY_ENTRIES)
.putAll(GCSPropertiesMetadata.PROPERTY_ENTRIES)
+ .putAll(COSPropertiesMetadata.PROPERTY_ENTRIES)
.putAll(CLIENT_PROPERTIES_METADATA.propertyEntries())
.build();
diff --git
a/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestHiveCatalogOperations.java
b/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestHiveCatalogOperations.java
index ac18ac92e9..6bcf0814fb 100644
---
a/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestHiveCatalogOperations.java
+++
b/catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/TestHiveCatalogOperations.java
@@ -79,6 +79,8 @@ import
org.apache.gravitino.rel.expressions.transforms.Transform;
import org.apache.gravitino.rel.indexes.Index;
import org.apache.gravitino.rel.types.Types;
import org.apache.gravitino.storage.AzureProperties;
+import org.apache.gravitino.storage.COSProperties;
+import org.apache.gravitino.storage.S3Properties;
import org.apache.gravitino.utils.ClientPool;
import org.apache.hadoop.hive.conf.HiveConf.ConfVars;
import org.apache.thrift.TException;
@@ -92,7 +94,6 @@ class TestHiveCatalogOperations {
Map<String, PropertyEntry<?>> propertyEntryMap =
HIVE_PROPERTIES_METADATA.catalogPropertiesMetadata().propertyEntries();
- Assertions.assertEquals(26, propertyEntryMap.size());
Assertions.assertTrue(propertyEntryMap.containsKey(METASTORE_URIS));
Assertions.assertTrue(propertyEntryMap.containsKey(Catalog.PROPERTY_PACKAGE));
Assertions.assertTrue(propertyEntryMap.containsKey(BaseCatalog.CATALOG_OPERATION_IMPL));
@@ -102,6 +103,18 @@ class TestHiveCatalogOperations {
Assertions.assertTrue(propertyEntryMap.containsKey(IMPERSONATION_ENABLE));
Assertions.assertTrue(propertyEntryMap.containsKey(LIST_ALL_TABLES));
Assertions.assertTrue(propertyEntryMap.containsKey(DEFAULT_CATALOG));
+ Assertions.assertTrue(
+ propertyEntryMap.containsKey(
+
org.apache.gravitino.credential.CredentialConstants.CREDENTIAL_PROVIDERS));
+ Assertions.assertTrue(
+ propertyEntryMap.containsKey(
+
org.apache.gravitino.credential.CredentialConstants.COS_TOKEN_EXPIRE_IN_SECS));
+
Assertions.assertTrue(propertyEntryMap.containsKey(S3Properties.GRAVITINO_S3_ROLE_ARN));
+
Assertions.assertTrue(propertyEntryMap.containsKey(COSProperties.GRAVITINO_COS_ACCESS_KEY_ID));
+ Assertions.assertFalse(
+
propertyEntryMap.get(COSProperties.GRAVITINO_COS_ACCESS_KEY_ID).isHidden());
+ Assertions.assertTrue(
+
propertyEntryMap.get(COSProperties.GRAVITINO_COS_ACCESS_KEY_SECRET).isHidden());
Assertions.assertTrue(
propertyEntryMap.get(AzureProperties.GRAVITINO_AZURE_CLIENT_SECRET).isHidden());
Assertions.assertTrue(propertyEntryMap.get(METASTORE_URIS).isRequired());
diff --git
a/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergCatalogPropertiesMetadata.java
b/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergCatalogPropertiesMetadata.java
index 8defa29fbd..4383b8672c 100644
---
a/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergCatalogPropertiesMetadata.java
+++
b/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergCatalogPropertiesMetadata.java
@@ -30,6 +30,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.gravitino.cloud.storage.AzurePropertiesMetadata;
+import org.apache.gravitino.cloud.storage.COSPropertiesMetadata;
import org.apache.gravitino.cloud.storage.GCSPropertiesMetadata;
import org.apache.gravitino.cloud.storage.OSSPropertiesMetadata;
import org.apache.gravitino.cloud.storage.S3PropertiesMetadata;
@@ -143,6 +144,7 @@ public class IcebergCatalogPropertiesMetadata extends
BaseCatalogPropertiesMetad
result.putAll(OSSPropertiesMetadata.PROPERTY_ENTRIES);
result.putAll(AzurePropertiesMetadata.PROPERTY_ENTRIES);
result.putAll(GCSPropertiesMetadata.PROPERTY_ENTRIES);
+ result.putAll(COSPropertiesMetadata.PROPERTY_ENTRIES);
result.putAll(KerberosConfig.KERBEROS_PROPERTY_ENTRIES);
result.putAll(AuthenticationConfig.AUTHENTICATION_PROPERTY_ENTRIES);
PROPERTIES_METADATA = ImmutableMap.copyOf(result);
diff --git
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergCatalogPropertiesMetadata.java
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergCatalogPropertiesMetadata.java
index 8ca4c3d85a..33921b1c54 100644
---
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergCatalogPropertiesMetadata.java
+++
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergCatalogPropertiesMetadata.java
@@ -22,6 +22,8 @@ import com.google.common.collect.ImmutableMap;
import java.util.Map;
import org.apache.gravitino.iceberg.common.IcebergConfig;
import org.apache.gravitino.iceberg.common.cache.LocalTableMetadataCache;
+import org.apache.gravitino.storage.COSProperties;
+import org.apache.gravitino.storage.S3Properties;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -43,6 +45,14 @@ public class TestIcebergCatalogPropertiesMetadata {
metadata.isHiddenProperty(IcebergCatalogPropertiesMetadata.GRAVITINO_JDBC_PASSWORD));
}
+ @Test
+ void testCloudAccessKeyIdsAreVisibleAndSecretsAreHidden() {
+
Assertions.assertFalse(metadata.isHiddenProperty(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID));
+
Assertions.assertTrue(metadata.isHiddenProperty(S3Properties.GRAVITINO_S3_SECRET_ACCESS_KEY));
+
Assertions.assertFalse(metadata.isHiddenProperty(COSProperties.GRAVITINO_COS_ACCESS_KEY_ID));
+
Assertions.assertTrue(metadata.isHiddenProperty(COSProperties.GRAVITINO_COS_ACCESS_KEY_SECRET));
+ }
+
@Test
void testTableMetadataCacheImplDefaultValue() {
Assertions.assertEquals(
diff --git
a/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/PaimonCatalogPropertiesMetadata.java
b/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/PaimonCatalogPropertiesMetadata.java
index deeb86d1da..5398d71d3b 100644
---
a/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/PaimonCatalogPropertiesMetadata.java
+++
b/catalogs/catalog-lakehouse-paimon/src/main/java/org/apache/gravitino/catalog/lakehouse/paimon/PaimonCatalogPropertiesMetadata.java
@@ -33,6 +33,7 @@ import
org.apache.gravitino.catalog.lakehouse.paimon.authentication.Authenticati
import
org.apache.gravitino.catalog.lakehouse.paimon.authentication.kerberos.KerberosConfig;
import org.apache.gravitino.catalog.lakehouse.paimon.utils.CatalogUtils;
import org.apache.gravitino.cloud.storage.AzurePropertiesMetadata;
+import org.apache.gravitino.cloud.storage.COSPropertiesMetadata;
import org.apache.gravitino.cloud.storage.GCSPropertiesMetadata;
import org.apache.gravitino.cloud.storage.OSSPropertiesMetadata;
import org.apache.gravitino.cloud.storage.S3PropertiesMetadata;
@@ -136,7 +137,7 @@ public class PaimonCatalogPropertiesMetadata extends
BaseCatalogPropertiesMetada
"The access key ID for Aliyun DLF",
false /* immutable */,
null /* defaultValue */,
- true /* hidden */))
+ false /* hidden */))
.put(
PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_SECRET,
stringOptionalPropertyEntry(
@@ -220,6 +221,7 @@ public class PaimonCatalogPropertiesMetadata extends
BaseCatalogPropertiesMetada
result.putAll(OSSPropertiesMetadata.PROPERTY_ENTRIES);
result.putAll(AzurePropertiesMetadata.PROPERTY_ENTRIES);
result.putAll(GCSPropertiesMetadata.PROPERTY_ENTRIES);
+ result.putAll(COSPropertiesMetadata.PROPERTY_ENTRIES);
result.putAll(REST_PROPERTY_ENTRIES);
PROPERTIES_METADATA = ImmutableMap.copyOf(result);
}
diff --git
a/catalogs/catalog-lakehouse-paimon/src/test/java/org/apache/gravitino/catalog/lakehouse/paimon/TestPaimonCatalogPropertiesMetadata.java
b/catalogs/catalog-lakehouse-paimon/src/test/java/org/apache/gravitino/catalog/lakehouse/paimon/TestPaimonCatalogPropertiesMetadata.java
index 433ece5279..cd8d37ddcb 100644
---
a/catalogs/catalog-lakehouse-paimon/src/test/java/org/apache/gravitino/catalog/lakehouse/paimon/TestPaimonCatalogPropertiesMetadata.java
+++
b/catalogs/catalog-lakehouse-paimon/src/test/java/org/apache/gravitino/catalog/lakehouse/paimon/TestPaimonCatalogPropertiesMetadata.java
@@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.apache.gravitino.storage.AzureProperties;
+import org.apache.gravitino.storage.COSProperties;
import org.apache.gravitino.storage.GCSProperties;
import org.apache.gravitino.storage.OSSProperties;
import org.apache.gravitino.storage.S3Properties;
@@ -34,14 +35,16 @@ public class TestPaimonCatalogPropertiesMetadata {
@Test
void testSensitivePropertiesAreHidden() {
assertTrue(metadata.isHiddenProperty(PaimonCatalogPropertiesMetadata.GRAVITINO_JDBC_PASSWORD));
-
assertTrue(metadata.isHiddenProperty(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID));
+
assertFalse(metadata.isHiddenProperty(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID));
assertTrue(metadata.isHiddenProperty(S3Properties.GRAVITINO_S3_SECRET_ACCESS_KEY));
-
assertTrue(metadata.isHiddenProperty(OSSProperties.GRAVITINO_OSS_ACCESS_KEY_ID));
+
assertFalse(metadata.isHiddenProperty(OSSProperties.GRAVITINO_OSS_ACCESS_KEY_ID));
assertTrue(metadata.isHiddenProperty(OSSProperties.GRAVITINO_OSS_ACCESS_KEY_SECRET));
assertTrue(metadata.isHiddenProperty(AzureProperties.GRAVITINO_AZURE_STORAGE_ACCOUNT_KEY));
- // REST bearer token and DLF credentials share the same hidden requirement
+
assertFalse(metadata.isHiddenProperty(COSProperties.GRAVITINO_COS_ACCESS_KEY_ID));
+
assertTrue(metadata.isHiddenProperty(COSProperties.GRAVITINO_COS_ACCESS_KEY_SECRET));
+ // REST bearer token and DLF secrets are hidden; DLF access-key-id matches
OSS/S3 (visible).
assertTrue(metadata.isHiddenProperty(PaimonConstants.TOKEN));
-
assertTrue(metadata.isHiddenProperty(PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_ID));
+
assertFalse(metadata.isHiddenProperty(PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_ID));
assertTrue(metadata.isHiddenProperty(PaimonConstants.GRAVITINO_DLF_ACCESS_KEY_SECRET));
assertTrue(metadata.isHiddenProperty(PaimonConstants.GRAVITINO_DLF_SECURITY_TOKEN));
}
diff --git
a/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestBaseGVFSOperationsSecrets.java
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestBaseGVFSOperationsSecrets.java
index 5253d867fc..8b43487571 100644
---
a/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestBaseGVFSOperationsSecrets.java
+++
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/TestBaseGVFSOperationsSecrets.java
@@ -224,6 +224,54 @@ public class TestBaseGVFSOperationsSecrets {
assertFalse("******".equals(all.get(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID)));
}
+ @Test
+ public void testKeepsPlaintextAccessKeyIdFromRestProperties() throws
Exception {
+ Configuration conf = new Configuration();
+
conf.set(GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_METALAKE_KEY,
"ml");
+ conf.set(
+ GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_SERVER_URI_KEY,
+ "http://localhost:8090");
+
+ Catalog catalog = mock(Catalog.class);
+ Schema schema = mock(Schema.class);
+ Fileset fileset = mock(Fileset.class);
+ SupportsSchemas schemas = mock(SupportsSchemas.class);
+ FilesetCatalog filesetCatalog = mock(FilesetCatalog.class);
+ SupportsSecrets catalogSecrets = mock(SupportsSecrets.class);
+
+ when(catalog.properties())
+ .thenReturn(
+ Map.of(
+ S3Properties.GRAVITINO_S3_ACCESS_KEY_ID,
+ "AKIATEST",
+ S3Properties.GRAVITINO_S3_SECRET_ACCESS_KEY,
+ "******",
+ "s3-endpoint",
+ "http://s3.example.com"));
+ when(catalog.supportsSecrets()).thenReturn(catalogSecrets);
+ when(catalogSecrets.getSecrets())
+ .thenReturn(Map.of(S3Properties.GRAVITINO_S3_SECRET_ACCESS_KEY,
"secret-from-getSecrets"));
+ when(catalog.asSchemas()).thenReturn(schemas);
+ when(schemas.loadSchema("schema")).thenReturn(schema);
+ when(schema.properties()).thenReturn(Map.of());
+ when(schema.supportsSecrets()).thenReturn(null);
+ when(catalog.asFilesetCatalog()).thenReturn(filesetCatalog);
+ when(filesetCatalog.loadFileset(NameIdentifier.of("schema",
"fs"))).thenReturn(fileset);
+ when(fileset.properties()).thenReturn(Map.of());
+ when(fileset.supportsSecrets()).thenReturn(null);
+
+ GravitinoClient client = mock(GravitinoClient.class);
+ when(client.loadCatalog("catalog")).thenReturn(catalog);
+
+ TestOps ops = new TestOps(conf, client);
+ Map<String, String> all =
+ ops.getAllProperties(NameIdentifier.of("ml", "catalog", "schema",
"fs"));
+
+ assertEquals("AKIATEST", all.get(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID));
+ assertEquals("secret-from-getSecrets",
all.get(S3Properties.GRAVITINO_S3_SECRET_ACCESS_KEY));
+ assertEquals("http://s3.example.com", all.get("s3-endpoint"));
+ }
+
private static final class TestOps extends BaseGVFSOperations {
private final GravitinoClient client;
diff --git
a/core/src/main/java/org/apache/gravitino/cloud/storage/AzurePropertiesMetadata.java
b/core/src/main/java/org/apache/gravitino/cloud/storage/AzurePropertiesMetadata.java
index 3f1598b7c6..a4c3827191 100644
---
a/core/src/main/java/org/apache/gravitino/cloud/storage/AzurePropertiesMetadata.java
+++
b/core/src/main/java/org/apache/gravitino/cloud/storage/AzurePropertiesMetadata.java
@@ -46,6 +46,22 @@ public class AzurePropertiesMetadata {
false /* immutable */,
null /* defaultValue */,
true /* hidden */))
+ .put(
+ AzureProperties.GRAVITINO_AZURE_TENANT_ID,
+ stringOptionalPropertyEntry(
+ AzureProperties.GRAVITINO_AZURE_TENANT_ID,
+ "Azure Active Directory tenant ID",
+ false /* immutable */,
+ null /* defaultValue */,
+ false /* hidden */))
+ .put(
+ AzureProperties.GRAVITINO_AZURE_CLIENT_ID,
+ stringOptionalPropertyEntry(
+ AzureProperties.GRAVITINO_AZURE_CLIENT_ID,
+ "Azure Active Directory client ID",
+ false /* immutable */,
+ null /* defaultValue */,
+ false /* hidden */))
.put(
AzureProperties.GRAVITINO_AZURE_CLIENT_SECRET,
stringOptionalPropertyEntry(
diff --git
a/core/src/main/java/org/apache/gravitino/cloud/storage/COSPropertiesMetadata.java
b/core/src/main/java/org/apache/gravitino/cloud/storage/COSPropertiesMetadata.java
index 9f9378fdf5..06156b2550 100644
---
a/core/src/main/java/org/apache/gravitino/cloud/storage/COSPropertiesMetadata.java
+++
b/core/src/main/java/org/apache/gravitino/cloud/storage/COSPropertiesMetadata.java
@@ -39,7 +39,7 @@ public class COSPropertiesMetadata {
"COS access key ID (Tencent Cloud SecretId)",
false /* immutable */,
null /* defaultValue */,
- true /* hidden */))
+ false /* hidden */))
.put(
COSProperties.GRAVITINO_COS_ACCESS_KEY_SECRET,
stringOptionalPropertyEntry(
@@ -48,6 +48,46 @@ public class COSPropertiesMetadata {
false /* immutable */,
null /* defaultValue */,
true /* hidden */))
+ .put(
+ COSProperties.GRAVITINO_COS_REGION,
+ stringOptionalPropertyEntry(
+ COSProperties.GRAVITINO_COS_REGION,
+ "Tencent Cloud COS region",
+ false /* immutable */,
+ null /* defaultValue */,
+ false /* hidden */))
+ .put(
+ COSProperties.GRAVITINO_COS_ENDPOINT,
+ stringOptionalPropertyEntry(
+ COSProperties.GRAVITINO_COS_ENDPOINT,
+ "Tencent Cloud COS endpoint",
+ false /* immutable */,
+ null /* defaultValue */,
+ false /* hidden */))
+ .put(
+ COSProperties.GRAVITINO_COS_ROLE_ARN,
+ stringOptionalPropertyEntry(
+ COSProperties.GRAVITINO_COS_ROLE_ARN,
+ "Tencent Cloud COS role ARN for STS credential vending",
+ false /* immutable */,
+ null /* defaultValue */,
+ false /* hidden */))
+ .put(
+ COSProperties.GRAVITINO_COS_EXTERNAL_ID,
+ stringOptionalPropertyEntry(
+ COSProperties.GRAVITINO_COS_EXTERNAL_ID,
+ "Tencent Cloud COS external ID for cross-account AssumeRole",
+ false /* immutable */,
+ null /* defaultValue */,
+ false /* hidden */))
+ .put(
+ COSProperties.GRAVITINO_COS_APP_ID,
+ stringOptionalPropertyEntry(
+ COSProperties.GRAVITINO_COS_APP_ID,
+ "Tencent Cloud COS application ID",
+ false /* immutable */,
+ null /* defaultValue */,
+ false /* hidden */))
.build();
private COSPropertiesMetadata() {}
diff --git
a/core/src/main/java/org/apache/gravitino/cloud/storage/OSSPropertiesMetadata.java
b/core/src/main/java/org/apache/gravitino/cloud/storage/OSSPropertiesMetadata.java
index 43e1897786..09585f3b92 100644
---
a/core/src/main/java/org/apache/gravitino/cloud/storage/OSSPropertiesMetadata.java
+++
b/core/src/main/java/org/apache/gravitino/cloud/storage/OSSPropertiesMetadata.java
@@ -37,7 +37,7 @@ public class OSSPropertiesMetadata {
"OSS access key ID",
false /* immutable */,
null /* defaultValue */,
- true /* hidden */))
+ false /* hidden */))
.put(
OSSProperties.GRAVITINO_OSS_ACCESS_KEY_SECRET,
stringOptionalPropertyEntry(
@@ -46,6 +46,38 @@ public class OSSPropertiesMetadata {
false /* immutable */,
null /* defaultValue */,
true /* hidden */))
+ .put(
+ OSSProperties.GRAVITINO_OSS_REGION,
+ stringOptionalPropertyEntry(
+ OSSProperties.GRAVITINO_OSS_REGION,
+ "OSS service region",
+ false /* immutable */,
+ null /* defaultValue */,
+ false /* hidden */))
+ .put(
+ OSSProperties.GRAVITINO_OSS_ENDPOINT,
+ stringOptionalPropertyEntry(
+ OSSProperties.GRAVITINO_OSS_ENDPOINT,
+ "OSS service endpoint",
+ false /* immutable */,
+ null /* defaultValue */,
+ false /* hidden */))
+ .put(
+ OSSProperties.GRAVITINO_OSS_ROLE_ARN,
+ stringOptionalPropertyEntry(
+ OSSProperties.GRAVITINO_OSS_ROLE_ARN,
+ "OSS role ARN for STS credential vending",
+ false /* immutable */,
+ null /* defaultValue */,
+ false /* hidden */))
+ .put(
+ OSSProperties.GRAVITINO_OSS_EXTERNAL_ID,
+ stringOptionalPropertyEntry(
+ OSSProperties.GRAVITINO_OSS_EXTERNAL_ID,
+ "OSS external ID for cross-account AssumeRole",
+ false /* immutable */,
+ null /* defaultValue */,
+ false /* hidden */))
.build();
private OSSPropertiesMetadata() {}
diff --git
a/core/src/main/java/org/apache/gravitino/cloud/storage/S3PropertiesMetadata.java
b/core/src/main/java/org/apache/gravitino/cloud/storage/S3PropertiesMetadata.java
index 8879d4b551..f6771fe7ec 100644
---
a/core/src/main/java/org/apache/gravitino/cloud/storage/S3PropertiesMetadata.java
+++
b/core/src/main/java/org/apache/gravitino/cloud/storage/S3PropertiesMetadata.java
@@ -18,6 +18,7 @@
*/
package org.apache.gravitino.cloud.storage;
+import static
org.apache.gravitino.connector.PropertyEntry.booleanPropertyEntry;
import static
org.apache.gravitino.connector.PropertyEntry.stringOptionalPropertyEntry;
import com.google.common.collect.ImmutableMap;
@@ -37,7 +38,7 @@ public class S3PropertiesMetadata {
"S3 access key ID",
false /* immutable */,
null /* defaultValue */,
- true /* hidden */))
+ false /* hidden */))
.put(
S3Properties.GRAVITINO_S3_SECRET_ACCESS_KEY,
stringOptionalPropertyEntry(
@@ -46,6 +47,64 @@ public class S3PropertiesMetadata {
false /* immutable */,
null /* defaultValue */,
true /* hidden */))
+ .put(
+ S3Properties.GRAVITINO_S3_ENDPOINT,
+ stringOptionalPropertyEntry(
+ S3Properties.GRAVITINO_S3_ENDPOINT,
+ "Alternative S3 service endpoint",
+ false /* immutable */,
+ null /* defaultValue */,
+ false /* hidden */))
+ .put(
+ S3Properties.GRAVITINO_S3_REGION,
+ stringOptionalPropertyEntry(
+ S3Properties.GRAVITINO_S3_REGION,
+ "S3 service region",
+ false /* immutable */,
+ null /* defaultValue */,
+ false /* hidden */))
+ .put(
+ S3Properties.GRAVITINO_S3_ROLE_ARN,
+ stringOptionalPropertyEntry(
+ S3Properties.GRAVITINO_S3_ROLE_ARN,
+ "S3 IAM role ARN for STS credential vending",
+ false /* immutable */,
+ null /* defaultValue */,
+ false /* hidden */))
+ .put(
+ S3Properties.GRAVITINO_S3_EXTERNAL_ID,
+ stringOptionalPropertyEntry(
+ S3Properties.GRAVITINO_S3_EXTERNAL_ID,
+ "S3 external ID for cross-account AssumeRole",
+ false /* immutable */,
+ null /* defaultValue */,
+ false /* hidden */))
+ .put(
+ S3Properties.GRAVITINO_S3_STS_ENDPOINT,
+ stringOptionalPropertyEntry(
+ S3Properties.GRAVITINO_S3_STS_ENDPOINT,
+ "Alternative STS endpoint for S3-compatible storage",
+ false /* immutable */,
+ null /* defaultValue */,
+ false /* hidden */))
+ .put(
+ S3Properties.GRAVITINO_S3_PATH_STYLE_ACCESS,
+ booleanPropertyEntry(
+ S3Properties.GRAVITINO_S3_PATH_STYLE_ACCESS,
+ "Whether to use path-style S3 access",
+ false /* required */,
+ false /* immutable */,
+ null /* defaultValue */,
+ false /* hidden */,
+ false /* reserved */))
+ .put(
+ S3Properties.GRAVITINO_S3_CREDS_PROVIDER,
+ stringOptionalPropertyEntry(
+ S3Properties.GRAVITINO_S3_CREDS_PROVIDER,
+ "S3 credentials provider class name",
+ false /* immutable */,
+ null /* defaultValue */,
+ false /* hidden */))
.build();
private S3PropertiesMetadata() {}
diff --git
a/core/src/main/java/org/apache/gravitino/connector/BaseCatalogPropertiesMetadata.java
b/core/src/main/java/org/apache/gravitino/connector/BaseCatalogPropertiesMetadata.java
index d304c57b77..5e7e660504 100644
---
a/core/src/main/java/org/apache/gravitino/connector/BaseCatalogPropertiesMetadata.java
+++
b/core/src/main/java/org/apache/gravitino/connector/BaseCatalogPropertiesMetadata.java
@@ -107,23 +107,16 @@ public abstract class BaseCatalogPropertiesMetadata
extends BasePropertiesMetada
if (propertyEntries == null) {
synchronized (this) {
if (propertyEntries == null) {
+ // Reuse BasePropertiesMetadata (specific + BASIC +
CredentialConfig), then add
+ // catalog-only entries.
+ Map<String, PropertyEntry<?>> base = buildBasePropertyEntries();
ImmutableMap.Builder<String, PropertyEntry<?>> builder =
ImmutableMap.builder();
- Map<String, PropertyEntry<?>> properties = specificPropertyEntries();
- builder.putAll(properties);
+ builder.putAll(base);
- // put the basic property entries
- BASIC_PROPERTY_ENTRIES.forEach(
- (name, entry) -> {
- Preconditions.checkArgument(
- !properties.containsKey(name), "Property metadata already
exists: " + name);
- builder.put(name, entry);
- });
-
- // put the basic catalog property entries
BASIC_CATALOG_PROPERTY_ENTRIES.forEach(
(name, entry) -> {
Preconditions.checkArgument(
- !properties.containsKey(name), "Property metadata already
exists: " + name);
+ !base.containsKey(name), "Property metadata already
exists: " + name);
builder.put(name, entry);
});
propertyEntries = builder.build();
diff --git
a/core/src/main/java/org/apache/gravitino/connector/BasePropertiesMetadata.java
b/core/src/main/java/org/apache/gravitino/connector/BasePropertiesMetadata.java
index 79c8c742ae..a169f2c641 100644
---
a/core/src/main/java/org/apache/gravitino/connector/BasePropertiesMetadata.java
+++
b/core/src/main/java/org/apache/gravitino/connector/BasePropertiesMetadata.java
@@ -27,6 +27,7 @@ import com.google.common.collect.Maps;
import java.util.List;
import java.util.Map;
import org.apache.gravitino.annotation.Evolving;
+import org.apache.gravitino.credential.config.CredentialConfig;
/**
* An abstract class representing a base properties metadata for entities.
Developers should extend
@@ -62,24 +63,43 @@ public abstract class BasePropertiesMetadata implements
PropertiesMetadata {
if (propertyEntries == null) {
synchronized (this) {
if (propertyEntries == null) {
- ImmutableMap.Builder<String, PropertyEntry<?>> builder =
ImmutableMap.builder();
- Map<String, PropertyEntry<?>> properties = specificPropertyEntries();
- builder.putAll(properties);
-
- BASIC_PROPERTY_ENTRIES.forEach(
- (name, entry) -> {
- Preconditions.checkArgument(
- !properties.containsKey(name), "Property metadata already
exists: " + name);
- builder.put(name, entry);
- });
-
- propertyEntries = builder.build();
+ propertyEntries = buildBasePropertyEntries();
}
}
}
return propertyEntries;
}
+ /**
+ * Builds specific + shared base property entries (including {@link
CredentialConfig}) without
+ * caching. Catalog metadata subclasses add catalog-only entries on top of
this map.
+ *
+ * @return an immutable property entry map
+ */
+ protected final Map<String, PropertyEntry<?>> buildBasePropertyEntries() {
+ ImmutableMap.Builder<String, PropertyEntry<?>> builder =
ImmutableMap.builder();
+ Map<String, PropertyEntry<?>> properties = specificPropertyEntries();
+ builder.putAll(properties);
+
+ BASIC_PROPERTY_ENTRIES.forEach(
+ (name, entry) -> {
+ Preconditions.checkArgument(
+ !properties.containsKey(name), "Property metadata already
exists: " + name);
+ builder.put(name, entry);
+ });
+
+ // Credential vending keys (e.g. credential-providers) are valid on schema
/ fileset / table as
+ // well as catalog. Register once so official non-hidden keys are not
fuzzy-masked.
+ CredentialConfig.CREDENTIAL_PROPERTY_ENTRIES.forEach(
+ (name, entry) -> {
+ Preconditions.checkArgument(
+ !properties.containsKey(name), "Property metadata already
exists: " + name);
+ builder.put(name, entry);
+ });
+
+ return builder.build();
+ }
+
/**
* Returns the specific property entries for the entity. Developers should
override this method to
* provide the specific property entries for their entities.
diff --git
a/core/src/main/java/org/apache/gravitino/connector/HiddenPropertyMaskUtils.java
b/core/src/main/java/org/apache/gravitino/connector/HiddenPropertyMaskUtils.java
index 9b174a9838..9788966b95 100644
---
a/core/src/main/java/org/apache/gravitino/connector/HiddenPropertyMaskUtils.java
+++
b/core/src/main/java/org/apache/gravitino/connector/HiddenPropertyMaskUtils.java
@@ -111,7 +111,7 @@ public final class HiddenPropertyMaskUtils {
keysToOmit.add(key);
} else if (hidden
|| SecretPropertyUtils.isSecretProperty(key, value)
- || SecretPropertyUtils.isSensitivePropertyKey(key)) {
+ || (!metadata.containsProperty(key) &&
SecretPropertyUtils.isSensitivePropertyKey(key))) {
keysToMask.add(key);
}
}
diff --git
a/core/src/main/java/org/apache/gravitino/credential/config/CredentialConfig.java
b/core/src/main/java/org/apache/gravitino/credential/config/CredentialConfig.java
index b08bda8569..2234b648d0 100644
---
a/core/src/main/java/org/apache/gravitino/credential/config/CredentialConfig.java
+++
b/core/src/main/java/org/apache/gravitino/credential/config/CredentialConfig.java
@@ -67,6 +67,46 @@ public class CredentialConfig extends Config {
DEFAULT_CREDENTIAL_CACHE_MAX_SIZE /* default value */,
false /* hidden */,
false /* reserved */))
+ .put(
+ CredentialConstants.S3_TOKEN_EXPIRE_IN_SECS,
+ PropertyEntry.integerPropertyEntry(
+ CredentialConstants.S3_TOKEN_EXPIRE_IN_SECS,
+ "S3 STS token expire time in seconds.",
+ false /* required */,
+ false /* immutable */,
+ null /* default value */,
+ false /* hidden */,
+ false /* reserved */))
+ .put(
+ CredentialConstants.OSS_TOKEN_EXPIRE_IN_SECS,
+ PropertyEntry.integerPropertyEntry(
+ CredentialConstants.OSS_TOKEN_EXPIRE_IN_SECS,
+ "OSS security token expire time in seconds.",
+ false /* required */,
+ false /* immutable */,
+ null /* default value */,
+ false /* hidden */,
+ false /* reserved */))
+ .put(
+ CredentialConstants.ADLS_TOKEN_EXPIRE_IN_SECS,
+ PropertyEntry.integerPropertyEntry(
+ CredentialConstants.ADLS_TOKEN_EXPIRE_IN_SECS,
+ "ADLS SAS token expire time in seconds.",
+ false /* required */,
+ false /* immutable */,
+ null /* default value */,
+ false /* hidden */,
+ false /* reserved */))
+ .put(
+ CredentialConstants.COS_TOKEN_EXPIRE_IN_SECS,
+ PropertyEntry.integerPropertyEntry(
+ CredentialConstants.COS_TOKEN_EXPIRE_IN_SECS,
+ "COS STS token expire time in seconds.",
+ false /* required */,
+ false /* immutable */,
+ null /* default value */,
+ false /* hidden */,
+ false /* reserved */))
.build();
public static final ConfigEntry<List<String>> CREDENTIAL_PROVIDERS =
diff --git
a/core/src/main/java/org/apache/gravitino/secret/FallbackPropertiesMetadata.java
b/core/src/main/java/org/apache/gravitino/secret/FallbackPropertiesMetadata.java
new file mode 100644
index 0000000000..ba76bdd687
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/secret/FallbackPropertiesMetadata.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.secret;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import org.apache.gravitino.cloud.storage.AzurePropertiesMetadata;
+import org.apache.gravitino.cloud.storage.COSPropertiesMetadata;
+import org.apache.gravitino.cloud.storage.GCSPropertiesMetadata;
+import org.apache.gravitino.cloud.storage.OSSPropertiesMetadata;
+import org.apache.gravitino.cloud.storage.S3PropertiesMetadata;
+import org.apache.gravitino.connector.BasePropertiesMetadata;
+import org.apache.gravitino.connector.PropertyEntry;
+
+/**
+ * Fallback {@link org.apache.gravitino.connector.PropertiesMetadata} when a
catalog does not
+ * support properties metadata for an entity type ({@link
UnsupportedOperationException}).
+ *
+ * <p>Registers the shared base + credential-vending + cloud-storage property
entries so officially
+ * non-hidden keys (for example {@code credential-providers}, {@code
s3-access-key-id}) are not
+ * fuzzy-recovered into {@code getSecrets}. Undeclared sensitive-named keys
still use fuzzy
+ * recovery; declared hidden secrets (for example {@code
s3-secret-access-key}) remain recoverable.
+ */
+final class FallbackPropertiesMetadata extends BasePropertiesMetadata {
+
+ static final FallbackPropertiesMetadata INSTANCE = new
FallbackPropertiesMetadata();
+
+ private static final Map<String, PropertyEntry<?>> CLOUD_PROPERTY_ENTRIES =
+ ImmutableMap.<String, PropertyEntry<?>>builder()
+ .putAll(S3PropertiesMetadata.PROPERTY_ENTRIES)
+ .putAll(OSSPropertiesMetadata.PROPERTY_ENTRIES)
+ .putAll(AzurePropertiesMetadata.PROPERTY_ENTRIES)
+ .putAll(GCSPropertiesMetadata.PROPERTY_ENTRIES)
+ .putAll(COSPropertiesMetadata.PROPERTY_ENTRIES)
+ .build();
+
+ private FallbackPropertiesMetadata() {}
+
+ @Override
+ protected Map<String, PropertyEntry<?>> specificPropertyEntries() {
+ return CLOUD_PROPERTY_ENTRIES;
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/secret/SecretPropertyOperationDispatcher.java
b/core/src/main/java/org/apache/gravitino/secret/SecretPropertyOperationDispatcher.java
index d766eabc9f..8b22dc8516 100644
---
a/core/src/main/java/org/apache/gravitino/secret/SecretPropertyOperationDispatcher.java
+++
b/core/src/main/java/org/apache/gravitino/secret/SecretPropertyOperationDispatcher.java
@@ -20,12 +20,15 @@ package org.apache.gravitino.secret;
import java.io.IOException;
import java.util.Map;
+import javax.annotation.Nullable;
import javax.ws.rs.NotSupportedException;
import org.apache.gravitino.Entity;
import org.apache.gravitino.EntityStore;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.catalog.CatalogManager;
import org.apache.gravitino.catalog.OperationDispatcher;
+import org.apache.gravitino.connector.HasPropertyMetadata;
+import org.apache.gravitino.connector.PropertiesMetadata;
import org.apache.gravitino.exceptions.NoSuchCatalogException;
import org.apache.gravitino.exceptions.NoSuchEntityException;
import org.apache.gravitino.exceptions.NoSuchFilesetException;
@@ -40,12 +43,14 @@ import org.apache.gravitino.messaging.Topic;
import org.apache.gravitino.meta.BaseMetalake;
import org.apache.gravitino.meta.FilesetEntity;
import org.apache.gravitino.meta.SchemaEntity;
+import org.apache.gravitino.metalake.MetalakePropertiesMetadata;
import org.apache.gravitino.model.Model;
import org.apache.gravitino.model.ModelVersion;
import org.apache.gravitino.rel.Table;
import org.apache.gravitino.rel.View;
import org.apache.gravitino.storage.IdGenerator;
import org.apache.gravitino.utils.NameIdentifierUtil;
+import org.apache.gravitino.utils.ThrowableFunction;
/**
* Dispatches secrets requests for metalake, catalog, schema, fileset, table,
topic, view, model,
@@ -82,70 +87,70 @@ public class SecretPropertyOperationDispatcher extends
OperationDispatcher {
* @return secret plaintext properties; never null
*/
public Map<String, String> getSecrets(NameIdentifier identifier,
Entity.EntityType entityType) {
- Map<String, String> rawProperties = loadRawProperties(identifier,
entityType);
- return SecretPropertyUtils.buildSecrets(secretManager, rawProperties);
+ RawPropertiesAndMetadata loaded = loadRawPropertiesAndMetadata(identifier,
entityType);
+ return SecretPropertyUtils.buildSecrets(secretManager,
loaded.rawProperties, loaded.metadata);
}
- private Map<String, String> loadRawProperties(
+ /**
+ * Loads raw properties and matching properties metadata in one catalog
lease when possible.
+ *
+ * <p>If the catalog does not expose properties metadata for the entity type
({@link
+ * UnsupportedOperationException}), uses {@link FallbackPropertiesMetadata}
which still registers
+ * shared base / credential / cloud entries so official non-hidden keys are
not fuzzy-recovered,
+ * while undeclared sensitive-named keys keep fuzzy recovery.
+ */
+ private RawPropertiesAndMetadata loadRawPropertiesAndMetadata(
NameIdentifier identifier, Entity.EntityType entityType) {
switch (entityType) {
case METALAKE:
- return loadMetalakeRawProperties(identifier);
+ return new RawPropertiesAndMetadata(
+ loadMetalakeRawProperties(identifier), new
MetalakePropertiesMetadata());
case CATALOG:
- return loadCatalogRawProperties(identifier);
+ return doWithCatalog(
+ identifier,
+ wrapper -> {
+ wrapper.catalog().checkMetalakeInUse();
+ return new RawPropertiesAndMetadata(
+ wrapper.catalog().entity().getProperties(),
+ resolvePropertiesMetadata(
+ wrapper,
HasPropertyMetadata::catalogPropertiesMetadata));
+ },
+ NoSuchCatalogException.class);
case SCHEMA:
- return loadSchemaRawProperties(identifier);
+ return loadSchemaRawPropertiesAndMetadata(identifier);
case FILESET:
- return loadFilesetRawProperties(identifier);
+ return loadFilesetRawPropertiesAndMetadata(identifier);
case TABLE:
- return loadTableRawProperties(identifier);
+ return loadTableRawPropertiesAndMetadata(identifier);
case TOPIC:
- return loadTopicRawProperties(identifier);
+ return loadTopicRawPropertiesAndMetadata(identifier);
case VIEW:
- return loadViewRawProperties(identifier);
+ return loadViewRawPropertiesAndMetadata(identifier);
case MODEL:
- return loadModelRawProperties(identifier);
+ return loadModelRawPropertiesAndMetadata(identifier);
case MODEL_VERSION:
- return loadModelVersionRawProperties(identifier);
+ return loadModelVersionRawPropertiesAndMetadata(identifier);
default:
throw new NotSupportedException(
"Doesn't support secret property operations for entity type: " +
entityType);
}
}
- private Map<String, String> loadMetalakeRawProperties(NameIdentifier
identifier) {
- try {
- BaseMetalake entity = store.get(identifier, Entity.EntityType.METALAKE,
BaseMetalake.class);
- return entity.properties() == null ? Map.of() : entity.properties();
- } catch (NoSuchEntityException e) {
- throw new NoSuchMetalakeException(e, "Metalake %s does not exist",
identifier);
- } catch (IOException e) {
- throw new RuntimeException("Failed to load metalake entity " +
identifier, e);
- }
- }
-
- private Map<String, String> loadCatalogRawProperties(NameIdentifier
identifier) {
- return doWithCatalog(
- identifier,
- wrapper -> {
- wrapper.catalog().checkMetalakeInUse();
- return wrapper.catalog().entity().getProperties();
- },
- NoSuchCatalogException.class);
- }
-
- private Map<String, String> loadSchemaRawProperties(NameIdentifier
identifier) {
+ private RawPropertiesAndMetadata
loadSchemaRawPropertiesAndMetadata(NameIdentifier identifier) {
NameIdentifier catalogIdent =
NameIdentifierUtil.getCatalogIdentifier(identifier);
- doWithCatalog(
- catalogIdent,
- wrapper -> {
- wrapper.catalog().checkMetalakeInUse();
- return null;
- },
- NoSuchCatalogException.class);
+ PropertiesMetadata metadata =
+ doWithCatalog(
+ catalogIdent,
+ wrapper -> {
+ wrapper.catalog().checkMetalakeInUse();
+ return resolvePropertiesMetadata(
+ wrapper, HasPropertyMetadata::schemaPropertiesMetadata);
+ },
+ NoSuchCatalogException.class);
try {
SchemaEntity entity = store.get(identifier, Entity.EntityType.SCHEMA,
SchemaEntity.class);
- return entity.properties() == null ? Map.of() : entity.properties();
+ return new RawPropertiesAndMetadata(
+ entity.properties() == null ? Map.of() : entity.properties(),
metadata);
} catch (NoSuchEntityException e) {
throw new NoSuchSchemaException(e, "Schema %s does not exist",
identifier);
} catch (IOException e) {
@@ -153,18 +158,21 @@ public class SecretPropertyOperationDispatcher extends
OperationDispatcher {
}
}
- private Map<String, String> loadFilesetRawProperties(NameIdentifier
identifier) {
+ private RawPropertiesAndMetadata
loadFilesetRawPropertiesAndMetadata(NameIdentifier identifier) {
NameIdentifier catalogIdent =
NameIdentifierUtil.getCatalogIdentifier(identifier);
- doWithCatalog(
- catalogIdent,
- wrapper -> {
- wrapper.catalog().checkMetalakeInUse();
- return null;
- },
- NoSuchCatalogException.class);
+ PropertiesMetadata metadata =
+ doWithCatalog(
+ catalogIdent,
+ wrapper -> {
+ wrapper.catalog().checkMetalakeInUse();
+ return resolvePropertiesMetadata(
+ wrapper, HasPropertyMetadata::filesetPropertiesMetadata);
+ },
+ NoSuchCatalogException.class);
try {
FilesetEntity entity = store.get(identifier, Entity.EntityType.FILESET,
FilesetEntity.class);
- return entity.properties() == null ? Map.of() : entity.properties();
+ return new RawPropertiesAndMetadata(
+ entity.properties() == null ? Map.of() : entity.properties(),
metadata);
} catch (NoSuchEntityException e) {
throw new NoSuchFilesetException(e, "Fileset %s does not exist",
identifier);
} catch (IOException e) {
@@ -172,7 +180,7 @@ public class SecretPropertyOperationDispatcher extends
OperationDispatcher {
}
}
- private Map<String, String> loadTableRawProperties(NameIdentifier
identifier) {
+ private RawPropertiesAndMetadata
loadTableRawPropertiesAndMetadata(NameIdentifier identifier) {
NameIdentifier catalogIdent =
NameIdentifierUtil.getCatalogIdentifier(identifier);
return doWithCatalog(
catalogIdent,
@@ -181,52 +189,66 @@ public class SecretPropertyOperationDispatcher extends
OperationDispatcher {
// Load from the connector so we get the same raw property map that
EntityCombinedTable
// masks for API responses (including Flink connector options like
flink.password).
Table table = wrapper.doWithTableOps(ops ->
ops.loadTable(identifier));
- return table.properties() == null ? Map.of() : table.properties();
+ Map<String, String> raw = table.properties() == null ? Map.of() :
table.properties();
+ return new RawPropertiesAndMetadata(
+ raw,
+ resolvePropertiesMetadata(wrapper,
HasPropertyMetadata::tablePropertiesMetadata));
},
NoSuchCatalogException.class,
NoSuchTableException.class);
}
- private Map<String, String> loadTopicRawProperties(NameIdentifier
identifier) {
+ private RawPropertiesAndMetadata
loadTopicRawPropertiesAndMetadata(NameIdentifier identifier) {
NameIdentifier catalogIdent =
NameIdentifierUtil.getCatalogIdentifier(identifier);
return doWithCatalog(
catalogIdent,
wrapper -> {
wrapper.catalog().checkMetalakeInUse();
Topic topic = wrapper.doWithTopicOps(ops ->
ops.loadTopic(identifier));
- return topic.properties() == null ? Map.of() : topic.properties();
+ Map<String, String> raw = topic.properties() == null ? Map.of() :
topic.properties();
+ return new RawPropertiesAndMetadata(
+ raw,
+ resolvePropertiesMetadata(wrapper,
HasPropertyMetadata::topicPropertiesMetadata));
},
NoSuchCatalogException.class,
NoSuchTopicException.class);
}
- private Map<String, String> loadViewRawProperties(NameIdentifier identifier)
{
+ private RawPropertiesAndMetadata
loadViewRawPropertiesAndMetadata(NameIdentifier identifier) {
NameIdentifier catalogIdent =
NameIdentifierUtil.getCatalogIdentifier(identifier);
return doWithCatalog(
catalogIdent,
wrapper -> {
wrapper.catalog().checkMetalakeInUse();
View view = wrapper.doWithViewOps(ops -> ops.loadView(identifier));
- return view.properties() == null ? Map.of() : view.properties();
+ Map<String, String> raw = view.properties() == null ? Map.of() :
view.properties();
+ // View masking uses table properties metadata elsewhere in
OperationDispatcher.
+ return new RawPropertiesAndMetadata(
+ raw,
+ resolvePropertiesMetadata(wrapper,
HasPropertyMetadata::tablePropertiesMetadata));
},
NoSuchCatalogException.class,
NoSuchViewException.class);
}
- private Map<String, String> loadModelRawProperties(NameIdentifier
identifier) {
+ private RawPropertiesAndMetadata
loadModelRawPropertiesAndMetadata(NameIdentifier identifier) {
NameIdentifier catalogIdent =
NameIdentifierUtil.getCatalogIdentifier(identifier);
return doWithCatalog(
catalogIdent,
wrapper -> {
wrapper.catalog().checkMetalakeInUse();
Model model = wrapper.doWithModelOps(ops ->
ops.getModel(identifier));
- return model.properties() == null ? Map.of() : model.properties();
+ Map<String, String> raw = model.properties() == null ? Map.of() :
model.properties();
+ return new RawPropertiesAndMetadata(
+ raw,
+ resolvePropertiesMetadata(wrapper,
HasPropertyMetadata::modelPropertiesMetadata));
},
NoSuchCatalogException.class,
NoSuchModelException.class);
}
- private Map<String, String> loadModelVersionRawProperties(NameIdentifier
identifier) {
+ private RawPropertiesAndMetadata loadModelVersionRawPropertiesAndMetadata(
+ NameIdentifier identifier) {
NameIdentifier catalogIdent =
NameIdentifierUtil.getCatalogIdentifier(identifier);
NameIdentifier modelIdent =
NameIdentifier.of(identifier.namespace().levels());
String versionName = identifier.name();
@@ -242,9 +264,54 @@ public class SecretPropertyOperationDispatcher extends
OperationDispatcher {
modelVersion =
wrapper.doWithModelOps(ops -> ops.getModelVersion(modelIdent,
versionName));
}
- return modelVersion.properties() == null ? Map.of() :
modelVersion.properties();
+ Map<String, String> raw =
+ modelVersion.properties() == null ? Map.of() :
modelVersion.properties();
+ return new RawPropertiesAndMetadata(
+ raw,
+ resolvePropertiesMetadata(
+ wrapper,
HasPropertyMetadata::modelVersionPropertiesMetadata));
},
NoSuchCatalogException.class,
NoSuchModelVersionException.class);
}
+
+ private Map<String, String> loadMetalakeRawProperties(NameIdentifier
identifier) {
+ try {
+ BaseMetalake entity = store.get(identifier, Entity.EntityType.METALAKE,
BaseMetalake.class);
+ return entity.properties() == null ? Map.of() : entity.properties();
+ } catch (NoSuchEntityException e) {
+ throw new NoSuchMetalakeException(e, "Metalake %s does not exist",
identifier);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to load metalake entity " +
identifier, e);
+ }
+ }
+
+ /**
+ * Returns properties metadata under the catalog connector classloader, or
{@link
+ * FallbackPropertiesMetadata#INSTANCE} when the catalog does not support it
for this entity type.
+ */
+ static PropertiesMetadata resolvePropertiesMetadata(
+ CatalogManager.CatalogWrapper wrapper,
+ ThrowableFunction<HasPropertyMetadata, PropertiesMetadata> getter) {
+ try {
+ return wrapper.doWithPropertiesMeta(getter);
+ } catch (UnsupportedOperationException e) {
+ return FallbackPropertiesMetadata.INSTANCE;
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static final class RawPropertiesAndMetadata {
+ private final Map<String, String> rawProperties;
+ private final PropertiesMetadata metadata;
+
+ private RawPropertiesAndMetadata(
+ @Nullable Map<String, String> rawProperties, PropertiesMetadata
metadata) {
+ this.rawProperties = rawProperties == null ? Map.of() : rawProperties;
+ this.metadata = metadata;
+ }
+ }
}
diff --git
a/core/src/main/java/org/apache/gravitino/secret/SecretPropertyUtils.java
b/core/src/main/java/org/apache/gravitino/secret/SecretPropertyUtils.java
index 7e87473a5f..19f0b89507 100644
--- a/core/src/main/java/org/apache/gravitino/secret/SecretPropertyUtils.java
+++ b/core/src/main/java/org/apache/gravitino/secret/SecretPropertyUtils.java
@@ -28,6 +28,8 @@ import java.util.Map;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.connector.PropertiesMetadata;
+import org.apache.gravitino.connector.PropertyEntry;
/**
* Helpers for secret-related entity property handling and request validation.
@@ -45,6 +47,15 @@ public final class SecretPropertyUtils {
private static final Pattern SENSITIVE_PROPERTY_KEY_PATTERN =
Pattern.compile(".*(secret|password|token|credential|access|account).*");
+ /** Empty metadata: every property key is undeclared (used for historical
fuzzy recovery). */
+ private static final PropertiesMetadata EMPTY_PROPERTIES_METADATA =
+ new PropertiesMetadata() {
+ @Override
+ public Map<String, PropertyEntry<?>> propertyEntries() {
+ return Map.of();
+ }
+ };
+
private SecretPropertyUtils() {}
/**
@@ -65,6 +76,38 @@ public final class SecretPropertyUtils {
return
SENSITIVE_PROPERTY_KEY_PATTERN.matcher(key.toLowerCase(Locale.ROOT)).matches();
}
+ /**
+ * Returns whether a sensitive-named inline property should be recovered via
{@code getSecrets}.
+ *
+ * <p>Secret-manager URNs are always recovered separately. For inline
plaintext:
+ *
+ * <ul>
+ * <li>{@code metadata == null}: do <strong>not</strong> recover
(URN-only). Used when the
+ * catalog does not expose properties metadata for the entity type.
+ * <li>otherwise: recover only undeclared keys or declared {@code hidden}
keys. Declared
+ * non-hidden configuration (for example {@code credential-providers},
{@code
+ * s3-access-key-id}) stays in {@code properties()} and is excluded
here.
+ * </ul>
+ *
+ * <p>Callers that need historical fuzzy recovery without real metadata
should pass an empty
+ * {@link PropertiesMetadata} (every key is undeclared) — see the
two-argument {@link
+ * #buildSecrets(SecretManager, Map)}.
+ *
+ * @param key the property key
+ * @param metadata entity properties metadata, or null when unavailable
+ * @return true when the inline plaintext should be included in {@code
getSecrets}
+ */
+ public static boolean shouldRecoverSensitiveNamedSecret(
+ String key, @Nullable PropertiesMetadata metadata) {
+ if (!isSensitivePropertyKey(key)) {
+ return false;
+ }
+ if (metadata == null) {
+ return false;
+ }
+ return !metadata.containsProperty(key) || metadata.isHiddenProperty(key);
+ }
+
/**
* Returns whether a property value is a Gravitino secret URN for the given
key.
*
@@ -76,6 +119,23 @@ public final class SecretPropertyUtils {
return key != null && value != null && value.startsWith(URN_PREFIX) &&
value.endsWith(key);
}
+ /**
+ * Builds a map of plaintext secret properties for {@code getSecrets} with
historical fuzzy
+ * recovery for sensitive-named keys.
+ *
+ * <p>Delegates to {@link #buildSecrets(SecretManager, Map,
PropertiesMetadata)} with an empty
+ * properties metadata so every key is treated as undeclared. Prefer the
three-argument overload
+ * when real entity metadata is available.
+ *
+ * @param secretManager secret manager used to resolve URNs
+ * @param rawProperties raw entity properties (may be null)
+ * @return a new secret plaintext property map; never null
+ */
+ public static Map<String, String> buildSecrets(
+ SecretManager secretManager, @Nullable Map<String, String>
rawProperties) {
+ return buildSecrets(secretManager, rawProperties,
EMPTY_PROPERTIES_METADATA);
+ }
+
/**
* Builds a map of plaintext secret properties for {@code getSecrets}.
*
@@ -85,7 +145,10 @@ public final class SecretPropertyUtils {
* <li>Include every entry where {@link #isSecretProperty} is true,
resolving the secret URN via
* {@link SecretManager#readSecret}.
* <li>Include every entry whose key matches {@link
#isSensitivePropertyKey} and whose value is
- * not a secret URN, returning the stored plaintext.
+ * not a secret URN, when {@link #shouldRecoverSensitiveNamedSecret}
is true (undeclared or
+ * declared hidden). Declared non-hidden keys are excluded even when
the name looks
+ * sensitive. When {@code metadata} is {@code null}, sensitive-named
plaintext is not
+ * recovered (URN-only).
* </ol>
*
* <p>Declared {@code hidden} properties are <strong>not</strong> included
merely because they are
@@ -99,10 +162,13 @@ public final class SecretPropertyUtils {
*
* @param secretManager secret manager used to resolve URNs
* @param rawProperties raw entity properties (may be null)
+ * @param metadata entity properties metadata, or null when unavailable
(URN-only recovery)
* @return a new secret plaintext property map; never null
*/
public static Map<String, String> buildSecrets(
- SecretManager secretManager, @Nullable Map<String, String>
rawProperties) {
+ SecretManager secretManager,
+ @Nullable Map<String, String> rawProperties,
+ @Nullable PropertiesMetadata metadata) {
Preconditions.checkArgument(secretManager != null, "secretManager must not
be null");
if (rawProperties == null || rawProperties.isEmpty()) {
return Map.of();
@@ -116,7 +182,7 @@ public final class SecretPropertyUtils {
}
if (isSecretProperty(key, value)) {
secrets.put(key, secretManager.readSecret(SecretUrn.parse(value)));
- } else if (isSensitivePropertyKey(key)) {
+ } else if (shouldRecoverSensitiveNamedSecret(key, metadata)) {
secrets.put(key, value);
}
}
diff --git
a/core/src/test/java/org/apache/gravitino/TestBasePropertiesMetadata.java
b/core/src/test/java/org/apache/gravitino/TestBasePropertiesMetadata.java
index 44281d7067..c96e2637d9 100644
--- a/core/src/test/java/org/apache/gravitino/TestBasePropertiesMetadata.java
+++ b/core/src/test/java/org/apache/gravitino/TestBasePropertiesMetadata.java
@@ -19,6 +19,7 @@
package org.apache.gravitino;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -29,6 +30,7 @@ import java.util.List;
import java.util.Map;
import org.apache.gravitino.connector.BasePropertiesMetadata;
import org.apache.gravitino.connector.PropertyEntry;
+import org.apache.gravitino.credential.CredentialConstants;
import org.junit.jupiter.api.Test;
public class TestBasePropertiesMetadata extends BasePropertiesMetadata {
@@ -93,4 +95,14 @@ public class TestBasePropertiesMetadata extends
BasePropertiesMetadata {
assertThrows(IllegalArgumentException.class, () ->
metadata.getPropertyEntry(""));
assertTrue(emptyException.getMessage().contains("Property is not
defined"));
}
+
+ @Test
+ public void testCredentialPropertyEntriesAreDeclaredForAllEntities() {
+ TestBasePropertiesMetadata metadata = new TestBasePropertiesMetadata();
+
+
assertTrue(metadata.containsProperty(CredentialConstants.CREDENTIAL_PROVIDERS));
+
assertTrue(metadata.containsProperty(CredentialConstants.S3_TOKEN_EXPIRE_IN_SECS));
+
assertFalse(metadata.isHiddenProperty(CredentialConstants.CREDENTIAL_PROVIDERS));
+
assertFalse(metadata.isHiddenProperty(CredentialConstants.S3_TOKEN_EXPIRE_IN_SECS));
+ }
}
diff --git
a/core/src/test/java/org/apache/gravitino/cloud/storage/TestCloudPropertiesMetadata.java
b/core/src/test/java/org/apache/gravitino/cloud/storage/TestCloudPropertiesMetadata.java
new file mode 100644
index 0000000000..7704948ae7
--- /dev/null
+++
b/core/src/test/java/org/apache/gravitino/cloud/storage/TestCloudPropertiesMetadata.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.cloud.storage;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.gravitino.credential.CredentialConstants;
+import org.apache.gravitino.credential.config.CredentialConfig;
+import org.apache.gravitino.secret.SecretPropertyUtils;
+import org.apache.gravitino.storage.AzureProperties;
+import org.apache.gravitino.storage.COSProperties;
+import org.apache.gravitino.storage.OSSProperties;
+import org.apache.gravitino.storage.S3Properties;
+import org.junit.jupiter.api.Test;
+
+public class TestCloudPropertiesMetadata {
+
+ @Test
+ void testS3CredentialConfigPropertiesAreDeclared() {
+ var metadata = S3PropertiesMetadata.PROPERTY_ENTRIES;
+ assertTrue(metadata.containsKey(S3Properties.GRAVITINO_S3_ENDPOINT));
+ assertTrue(metadata.containsKey(S3Properties.GRAVITINO_S3_REGION));
+ assertTrue(metadata.containsKey(S3Properties.GRAVITINO_S3_ROLE_ARN));
+ assertFalse(metadata.get(S3Properties.GRAVITINO_S3_ENDPOINT).isHidden());
+
assertFalse(metadata.get(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID).isHidden());
+
assertTrue(metadata.get(S3Properties.GRAVITINO_S3_SECRET_ACCESS_KEY).isHidden());
+ }
+
+ @Test
+ void testOssCredentialConfigPropertiesAreDeclared() {
+ var metadata = OSSPropertiesMetadata.PROPERTY_ENTRIES;
+ assertTrue(metadata.containsKey(OSSProperties.GRAVITINO_OSS_ENDPOINT));
+ assertTrue(metadata.containsKey(OSSProperties.GRAVITINO_OSS_ROLE_ARN));
+ assertFalse(metadata.get(OSSProperties.GRAVITINO_OSS_REGION).isHidden());
+ }
+
+ @Test
+ void testAzureAdlsPropertiesAreDeclared() {
+ var metadata = AzurePropertiesMetadata.PROPERTY_ENTRIES;
+
assertTrue(metadata.containsKey(AzureProperties.GRAVITINO_AZURE_TENANT_ID));
+
assertTrue(metadata.containsKey(AzureProperties.GRAVITINO_AZURE_CLIENT_ID));
+
assertFalse(metadata.get(AzureProperties.GRAVITINO_AZURE_STORAGE_ACCOUNT_NAME).isHidden());
+
assertTrue(metadata.get(AzureProperties.GRAVITINO_AZURE_CLIENT_SECRET).isHidden());
+ }
+
+ @Test
+ void testCosCredentialConfigPropertiesAreDeclared() {
+ var metadata = COSPropertiesMetadata.PROPERTY_ENTRIES;
+ assertTrue(metadata.containsKey(COSProperties.GRAVITINO_COS_REGION));
+ assertTrue(metadata.containsKey(COSProperties.GRAVITINO_COS_ENDPOINT));
+ assertTrue(metadata.containsKey(COSProperties.GRAVITINO_COS_ROLE_ARN));
+ assertTrue(metadata.containsKey(COSProperties.GRAVITINO_COS_EXTERNAL_ID));
+ assertTrue(metadata.containsKey(COSProperties.GRAVITINO_COS_APP_ID));
+
assertFalse(metadata.get(COSProperties.GRAVITINO_COS_ACCESS_KEY_ID).isHidden());
+
assertTrue(metadata.get(COSProperties.GRAVITINO_COS_ACCESS_KEY_SECRET).isHidden());
+ }
+
+ @Test
+ void testCredentialPropertyEntriesIncludeTokenExpireKeys() {
+ var metadata = CredentialConfig.CREDENTIAL_PROPERTY_ENTRIES;
+ assertTrue(metadata.containsKey(CredentialConstants.CREDENTIAL_PROVIDERS));
+
assertTrue(metadata.containsKey(CredentialConstants.S3_TOKEN_EXPIRE_IN_SECS));
+
assertTrue(metadata.containsKey(CredentialConstants.ADLS_TOKEN_EXPIRE_IN_SECS));
+
assertTrue(metadata.containsKey(CredentialConstants.COS_TOKEN_EXPIRE_IN_SECS));
+
assertFalse(metadata.get(CredentialConstants.S3_TOKEN_EXPIRE_IN_SECS).isHidden());
+ }
+
+ @Test
+ void testDeclaredSensitiveNamedCredentialKeysAreNonHidden() {
+ assertFalse(
+ CredentialConfig.CREDENTIAL_PROPERTY_ENTRIES
+ .get(CredentialConstants.CREDENTIAL_PROVIDERS)
+ .isHidden());
+ assertTrue(
+
SecretPropertyUtils.isSensitivePropertyKey(CredentialConstants.CREDENTIAL_PROVIDERS));
+ assertFalse(
+ CredentialConfig.CREDENTIAL_PROPERTY_ENTRIES
+ .get(CredentialConstants.S3_TOKEN_EXPIRE_IN_SECS)
+ .isHidden());
+ assertTrue(
+
SecretPropertyUtils.isSensitivePropertyKey(CredentialConstants.S3_TOKEN_EXPIRE_IN_SECS));
+ assertFalse(
+ AzurePropertiesMetadata.PROPERTY_ENTRIES
+ .get(AzureProperties.GRAVITINO_AZURE_STORAGE_ACCOUNT_NAME)
+ .isHidden());
+ assertTrue(
+ SecretPropertyUtils.isSensitivePropertyKey(
+ AzureProperties.GRAVITINO_AZURE_STORAGE_ACCOUNT_NAME));
+ }
+}
diff --git
a/core/src/test/java/org/apache/gravitino/connector/TestBaseCatalogPropertiesMetadata.java
b/core/src/test/java/org/apache/gravitino/connector/TestBaseCatalogPropertiesMetadata.java
new file mode 100644
index 0000000000..3e7c801e2f
--- /dev/null
+++
b/core/src/test/java/org/apache/gravitino/connector/TestBaseCatalogPropertiesMetadata.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.connector;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Collections;
+import java.util.Map;
+import org.apache.gravitino.credential.CredentialConstants;
+import org.junit.jupiter.api.Test;
+
+public class TestBaseCatalogPropertiesMetadata {
+
+ private final PropertiesMetadata metadata =
+ new BaseCatalogPropertiesMetadata() {
+ @Override
+ protected Map<String, PropertyEntry<?>> specificPropertyEntries() {
+ return Collections.emptyMap();
+ }
+ };
+
+ @Test
+ void testCredentialPropertyEntriesAreDeclaredForAllCatalogs() {
+
assertTrue(metadata.containsProperty(CredentialConstants.CREDENTIAL_PROVIDERS));
+
assertTrue(metadata.containsProperty(CredentialConstants.S3_TOKEN_EXPIRE_IN_SECS));
+
assertFalse(metadata.isHiddenProperty(CredentialConstants.CREDENTIAL_PROVIDERS));
+
assertFalse(metadata.isHiddenProperty(CredentialConstants.S3_TOKEN_EXPIRE_IN_SECS));
+ }
+}
diff --git
a/core/src/test/java/org/apache/gravitino/connector/TestHiddenPropertyMaskUtils.java
b/core/src/test/java/org/apache/gravitino/connector/TestHiddenPropertyMaskUtils.java
index 27e323bb73..0a04e119ff 100644
---
a/core/src/test/java/org/apache/gravitino/connector/TestHiddenPropertyMaskUtils.java
+++
b/core/src/test/java/org/apache/gravitino/connector/TestHiddenPropertyMaskUtils.java
@@ -129,7 +129,13 @@ public class TestHiddenPropertyMaskUtils {
return ImmutableMap.of(
"aws-region",
PropertyEntry.stringOptionalPropertyEntry(
- "aws-region", "region", false, null, false));
+ "aws-region", "region", false, null, false),
+ "azure-storage-account-name",
+ PropertyEntry.stringOptionalPropertyEntry(
+ "azure-storage-account-name", "account", false, null,
false),
+ "credential-providers",
+ PropertyEntry.stringOptionalPropertyEntry(
+ "credential-providers", "providers", false, null, false));
}
};
@@ -148,18 +154,20 @@ public class TestHiddenPropertyMaskUtils {
"azure-storage-account-key",
"abs-key",
"azure-storage-account-name",
- "abs-account");
+ "abs-account",
+ "credential-providers",
+ "s3-token");
Map<String, String> masked =
HiddenPropertyMaskUtils.maskHiddenProperties(properties, metadata);
Assertions.assertEquals("us-east-2", masked.get("aws-region"));
Assertions.assertEquals("s3://bucket/prefix", masked.get("warehouse"));
+ Assertions.assertEquals("abs-account",
masked.get("azure-storage-account-name"));
+ Assertions.assertEquals("s3-token", masked.get("credential-providers"));
Assertions.assertEquals(HiddenPropertyMaskUtils.MASKED_VALUE,
masked.get("s3-access-key-id"));
Assertions.assertEquals(
HiddenPropertyMaskUtils.MASKED_VALUE,
masked.get("s3-secret-access-key"));
Assertions.assertEquals(HiddenPropertyMaskUtils.MASKED_VALUE,
masked.get("custom-token"));
Assertions.assertEquals(
HiddenPropertyMaskUtils.MASKED_VALUE,
masked.get("azure-storage-account-key"));
- Assertions.assertEquals(
- HiddenPropertyMaskUtils.MASKED_VALUE,
masked.get("azure-storage-account-name"));
}
}
diff --git
a/core/src/test/java/org/apache/gravitino/secret/TestSecretPropertyOperationDispatcher.java
b/core/src/test/java/org/apache/gravitino/secret/TestSecretPropertyOperationDispatcher.java
index 49fd8ad039..be0d093cb0 100644
---
a/core/src/test/java/org/apache/gravitino/secret/TestSecretPropertyOperationDispatcher.java
+++
b/core/src/test/java/org/apache/gravitino/secret/TestSecretPropertyOperationDispatcher.java
@@ -20,6 +20,7 @@ package org.apache.gravitino.secret;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
@@ -32,6 +33,7 @@ import org.apache.gravitino.Configs;
import org.apache.gravitino.Entity;
import org.apache.gravitino.GravitinoEnv;
import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.catalog.CatalogManager;
import org.apache.gravitino.catalog.ModelOperationDispatcher;
import org.apache.gravitino.catalog.SchemaDispatcher;
import org.apache.gravitino.catalog.SchemaOperationDispatcher;
@@ -39,6 +41,7 @@ import org.apache.gravitino.catalog.TableOperationDispatcher;
import org.apache.gravitino.catalog.TestOperationDispatcher;
import org.apache.gravitino.catalog.TopicOperationDispatcher;
import org.apache.gravitino.catalog.ViewOperationDispatcher;
+import org.apache.gravitino.connector.HasPropertyMetadata;
import org.apache.gravitino.lock.LockManager;
import org.apache.gravitino.meta.AuditInfo;
import org.apache.gravitino.meta.BaseMetalake;
@@ -170,4 +173,29 @@ public class TestSecretPropertyOperationDispatcher extends
TestOperationDispatch
Assertions.assertEquals("s3cr3t", secrets.get("jdbc-password"));
Assertions.assertFalse(secrets.containsKey("visible"));
}
+
+ @Test
+ public void testResolvePropertiesMetadataFallsBackOnUnsupportedOperation() {
+ CatalogManager.CatalogWrapper wrapper =
mock(CatalogManager.CatalogWrapper.class);
+ try {
+ when(wrapper.doWithPropertiesMeta(org.mockito.ArgumentMatchers.any()))
+ .thenThrow(new UnsupportedOperationException("no metadata"));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ org.apache.gravitino.connector.PropertiesMetadata metadata =
+ SecretPropertyOperationDispatcher.resolvePropertiesMetadata(
+ wrapper, HasPropertyMetadata::catalogPropertiesMetadata);
+ Assertions.assertSame(FallbackPropertiesMetadata.INSTANCE, metadata);
+ // Official non-hidden keys stay out of getSecrets.
+ Assertions.assertFalse(
+
SecretPropertyUtils.shouldRecoverSensitiveNamedSecret("credential-providers",
metadata));
+ Assertions.assertFalse(
+
SecretPropertyUtils.shouldRecoverSensitiveNamedSecret("s3-access-key-id",
metadata));
+ // Declared hidden secrets and undeclared sensitive names still
fuzzy-recover.
+ Assertions.assertTrue(
+
SecretPropertyUtils.shouldRecoverSensitiveNamedSecret("s3-secret-access-key",
metadata));
+ Assertions.assertTrue(
+ SecretPropertyUtils.shouldRecoverSensitiveNamedSecret("custom-token",
metadata));
+ }
}
diff --git
a/core/src/test/java/org/apache/gravitino/secret/TestSecretPropertyUtils.java
b/core/src/test/java/org/apache/gravitino/secret/TestSecretPropertyUtils.java
index 7201aa3aa0..b0c9da8080 100644
---
a/core/src/test/java/org/apache/gravitino/secret/TestSecretPropertyUtils.java
+++
b/core/src/test/java/org/apache/gravitino/secret/TestSecretPropertyUtils.java
@@ -18,11 +18,14 @@
*/
package org.apache.gravitino.secret;
+import com.google.common.collect.ImmutableMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.gravitino.Config;
+import org.apache.gravitino.connector.PropertiesMetadata;
+import org.apache.gravitino.connector.PropertyEntry;
import org.apache.gravitino.secret.memory.InMemorySecretsProvider;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -159,11 +162,80 @@ public class TestSecretPropertyUtils {
}
}
+ @Test
+ void testBuildSecretsNullMetadataIsUrnOnly() {
+ try (SecretManager sm = memorySecretManager()) {
+ Map<String, String> entityProps = new HashMap<>();
+ entityProps.put("s3-access-key-id", "AKIA");
+ entityProps.put("jdbc-password", "inline-secret");
+ Map<String, SecretBinding> bindings =
+ Map.of("custom-secret", new SecretBinding("memory", "custom-value"));
+ List<SecretMaterial> writes =
+ sm.assembleSecretMaterials(Map.of(), entityProps, "catalog", 42L,
bindings, Map.of());
+ sm.writeSecrets(writes);
+
+ Map<String, String> secrets = SecretPropertyUtils.buildSecrets(sm,
entityProps, null);
+ Assertions.assertEquals("custom-value", secrets.get("custom-secret"));
+ Assertions.assertFalse(secrets.containsKey("s3-access-key-id"));
+ Assertions.assertFalse(secrets.containsKey("jdbc-password"));
+ }
+ }
+
@Test
void testBuildSecretsNullAndEmpty() {
try (SecretManager sm = memorySecretManager()) {
Assertions.assertTrue(SecretPropertyUtils.buildSecrets(sm,
null).isEmpty());
Assertions.assertTrue(SecretPropertyUtils.buildSecrets(sm,
Map.of()).isEmpty());
+ Assertions.assertTrue(SecretPropertyUtils.buildSecrets(sm, null,
null).isEmpty());
+ }
+ }
+
+ @Test
+ void testBuildSecretsExcludesDeclaredNonHiddenSensitiveKeys() {
+ try (SecretManager sm = memorySecretManager()) {
+ PropertiesMetadata metadata =
+ new PropertiesMetadata() {
+ @Override
+ public Map<String, PropertyEntry<?>> propertyEntries() {
+ return ImmutableMap.of(
+ "credential-providers",
+ PropertyEntry.stringOptionalPropertyEntry(
+ "credential-providers", "providers", false, null, false),
+ "azure-storage-account-name",
+ PropertyEntry.stringOptionalPropertyEntry(
+ "azure-storage-account-name", "account", false, null,
false),
+ "s3-access-key-id",
+ PropertyEntry.stringOptionalPropertyEntry(
+ "s3-access-key-id", "ak", false, null, false),
+ "jdbc-password",
+ PropertyEntry.stringOptionalPropertyEntry(
+ "jdbc-password", "password", false, null, true),
+ "s3-secret-access-key",
+ PropertyEntry.stringOptionalPropertyEntry(
+ "s3-secret-access-key", "sk", false, null, true));
+ }
+ };
+ Map<String, String> entityProps =
+ Map.of(
+ "credential-providers",
+ "s3-token",
+ "azure-storage-account-name",
+ "abs-account",
+ "s3-access-key-id",
+ "AKIA",
+ "jdbc-password",
+ "inline-secret",
+ "s3-secret-access-key",
+ "super-secret",
+ "custom-token",
+ "tok");
+ Map<String, String> secrets = SecretPropertyUtils.buildSecrets(sm,
entityProps, metadata);
+ Assertions.assertFalse(secrets.containsKey("credential-providers"));
+
Assertions.assertFalse(secrets.containsKey("azure-storage-account-name"));
+ Assertions.assertFalse(secrets.containsKey("s3-access-key-id"));
+ Assertions.assertEquals("inline-secret", secrets.get("jdbc-password"));
+ Assertions.assertEquals("super-secret",
secrets.get("s3-secret-access-key"));
+ Assertions.assertEquals("tok", secrets.get("custom-token"));
}
}
diff --git
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoConnector.java
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoConnector.java
index e0a7cf4a79..9d889c5dc3 100644
---
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoConnector.java
+++
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoConnector.java
@@ -288,6 +288,11 @@ public abstract class TestGravitinoConnector extends
AbstractGravitinoConnectorT
"(?s)Create catalog failed. Create catalog failed due to the loading
process fails\\."
+ ".*unknown-direct-key.*");
assertThat(computeActual("show
catalogs").getOnlyColumnAsSet()).doesNotContain("memory1");
+ // Failed registration still leaves a Gravitino catalog entry visible to
catalog_status /
+ // system.catalog; drop it so later tests that assume only the seed
"memory" catalog stay
+ // deterministic on the shared QueryRunner.
+ assertUpdate(
+ "call gravitino.system.drop_catalog(catalog => 'memory1',
ignore_not_exist => true)");
assertUpdate(
"call gravitino.system.create_catalog("
@@ -301,7 +306,10 @@ public abstract class TestGravitinoConnector extends
AbstractGravitinoConnectorT
@Test
public void testSystemTable() throws Exception {
- MaterializedResult expectedResult = computeActual("select * from
gravitino.system.catalog");
+ // Scope to the seed catalog: other tests may briefly leave FAILED
registrations in the shared
+ // QueryRunner (see testCreateCatalog /
testCatalogStatusReportsARegistrationFailure).
+ MaterializedResult expectedResult =
+ computeActual("select * from gravitino.system.catalog where name =
'memory'");
assertEquals(expectedResult.getRowCount(), 1);
List<MaterializedRow> expectedRows = expectedResult.getMaterializedRows();
MaterializedRow row = expectedRows.get(0);
@@ -315,7 +323,8 @@ public abstract class TestGravitinoConnector extends
AbstractGravitinoConnectorT
MaterializedResult result =
computeActual(
"select metalake, catalog_name, trino_catalog_name, provider,
status, last_error,"
- + " failure_count from gravitino.system.catalog_status");
+ + " failure_count from gravitino.system.catalog_status"
+ + " where catalog_name = 'memory'");
assertEquals(result.getRowCount(), 1);
MaterializedRow row = result.getMaterializedRows().get(0);
assertEquals(row.getField(1), "memory");
@@ -331,7 +340,9 @@ public abstract class TestGravitinoConnector extends
AbstractGravitinoConnectorT
// page.getColumns() honors the requested order; a projection that sorted
or de-duplicated
// channels would return correct looking data in the wrong columns.
MaterializedResult result =
- computeActual("select status, catalog_name, status from
gravitino.system.catalog_status");
+ computeActual(
+ "select status, catalog_name, status from
gravitino.system.catalog_status"
+ + " where catalog_name = 'memory'");
assertEquals(result.getRowCount(), 1);
MaterializedRow row = result.getMaterializedRows().get(0);
assertEquals(row.getField(0), "REGISTERED");
@@ -344,9 +355,14 @@ public abstract class TestGravitinoConnector extends
AbstractGravitinoConnectorT
// count(*) projects no column at all, so the page is built with zero
channels and only its
// position count carries the answer. A table that sized its page from the
projected columns
// would report no rows here while returning them for every other query.
- assertEquals(computeScalar("select count(*) from
gravitino.system.catalog_status"), 1L);
+ // Filter to the seed catalog so leftover FAILED registrations from other
tests do not flake.
+ assertEquals(
+ computeScalar(
+ "select count(*) from gravitino.system.catalog_status where
catalog_name = 'memory'"),
+ 1L);
assertEquals(computeScalar("select count(*) from
gravitino.system.load_status"), 1L);
- assertEquals(computeScalar("select count(*) from
gravitino.system.catalog"), 1L);
+ assertEquals(
+ computeScalar("select count(*) from gravitino.system.catalog where
name = 'memory'"), 1L);
}
@Test
@@ -354,27 +370,30 @@ public abstract class TestGravitinoConnector extends
AbstractGravitinoConnectorT
// An unknown bypass key makes the inner connector reject its
configuration, so the catalog
// exists in Gravitino but its CREATE CATALOG in Trino fails: a real
registration failure
// rather than a simulated state.
- assertQueryFails(
- "call gravitino.system.create_catalog("
- + "catalog=>'memory_failed', provider=>'memory',"
- + " properties => Map(array['trino.bypass.unknown-direct-key'],
array['10']))",
- "(?s)Create catalog failed. Create catalog failed due to the loading
process fails\\..*");
- assertThat(computeActual("show
catalogs").getOnlyColumnAsSet()).doesNotContain("memory_failed");
-
- MaterializedResult result =
- computeActual(
- "select status, last_error, failure_count from
gravitino.system.catalog_status"
- + " where catalog_name = 'memory_failed'");
- assertEquals(result.getRowCount(), 1);
- MaterializedRow row = result.getMaterializedRows().get(0);
- assertEquals(row.getField(0), "FAILED");
- assertThat((String) row.getField(1)).contains("unknown-direct-key");
- assertEquals(row.getField(2), 1L);
-
- // Leave the shared query runner as it was found, or the load loop keeps
retrying this catalog
- // and the other status table tests see an extra row.
- assertUpdate(
- "call gravitino.system.drop_catalog(catalog => 'memory_failed',
ignore_not_exist => true)");
+ try {
+ assertQueryFails(
+ "call gravitino.system.create_catalog("
+ + "catalog=>'memory_failed', provider=>'memory',"
+ + " properties => Map(array['trino.bypass.unknown-direct-key'],
array['10']))",
+ "(?s)Create catalog failed. Create catalog failed due to the loading
process fails\\..*");
+ assertThat(computeActual("show catalogs").getOnlyColumnAsSet())
+ .doesNotContain("memory_failed");
+
+ MaterializedResult result =
+ computeActual(
+ "select status, last_error, failure_count from
gravitino.system.catalog_status"
+ + " where catalog_name = 'memory_failed'");
+ assertEquals(result.getRowCount(), 1);
+ MaterializedRow row = result.getMaterializedRows().get(0);
+ assertEquals(row.getField(0), "FAILED");
+ assertThat((String) row.getField(1)).contains("unknown-direct-key");
+ assertEquals(row.getField(2), 1L);
+ } finally {
+ // Leave the shared query runner as it was found, or the load loop keeps
retrying this catalog
+ // and the other status table tests see an extra row.
+ assertUpdate(
+ "call gravitino.system.drop_catalog(catalog => 'memory_failed',
ignore_not_exist => true)");
+ }
assertEquals(
computeActual(
"select 1 from gravitino.system.catalog_status"