This is an automated email from the ASF dual-hosted git repository. jshao 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 26a8b3751 [#5778] feat(aliyun-bundles)support OSS secret key credential (#5814) 26a8b3751 is described below commit 26a8b3751bef2925ae53443382b2a3bf58e6d069 Author: Xiaojian Sun <sunxiaojian...@163.com> AuthorDate: Wed Dec 11 17:03:51 2024 +0800 [#5778] feat(aliyun-bundles)support OSS secret key credential (#5814) ### What changes were proposed in this pull request? Support OSS secret key credential ### Why are the changes needed? Fix: [# (5778)](https://github.com/apache/gravitino/issues/5778) ### How was this patch tested? IcebergRESTOSSSecretIT --- .../credential/OSSSecretKeyCredential.java | 120 ++++++++++++++++++++ .../org.apache.gravitino.credential.Credential | 1 + .../oss/credential/OSSSecretKeyProvider.java | 54 +++++++++ ....apache.gravitino.credential.CredentialProvider | 3 +- .../credential/CredentialPropertyUtils.java | 2 +- .../credential/TestCredentialFactory.java | 25 +++++ docs/iceberg-rest-service.md | 21 ++-- .../iceberg/common/ops/IcebergCatalogWrapper.java | 4 +- .../integration/test/IcebergRESTOSSSecretIT.java | 121 +++++++++++++++++++++ 9 files changed, 336 insertions(+), 15 deletions(-) diff --git a/api/src/main/java/org/apache/gravitino/credential/OSSSecretKeyCredential.java b/api/src/main/java/org/apache/gravitino/credential/OSSSecretKeyCredential.java new file mode 100644 index 000000000..fd98a3bfd --- /dev/null +++ b/api/src/main/java/org/apache/gravitino/credential/OSSSecretKeyCredential.java @@ -0,0 +1,120 @@ +/* + * 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.credential; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import org.apache.commons.lang3.StringUtils; + +/** OSS secret key credential. */ +public class OSSSecretKeyCredential implements Credential { + + /** OSS secret key credential type. */ + public static final String OSS_SECRET_KEY_CREDENTIAL_TYPE = "oss-secret-key"; + /** The static access key ID used to access OSS data. */ + public static final String GRAVITINO_OSS_STATIC_ACCESS_KEY_ID = "oss-access-key-id"; + /** The static secret access key used to access OSS data. */ + public static final String GRAVITINO_OSS_STATIC_SECRET_ACCESS_KEY = "oss-secret-access-key"; + + private String accessKeyId; + private String secretAccessKey; + + /** + * Constructs an instance of {@link OSSSecretKeyCredential} with the static OSS access key ID and + * secret access key. + * + * @param accessKeyId The OSS static access key ID. + * @param secretAccessKey The OSS static secret access key. + */ + public OSSSecretKeyCredential(String accessKeyId, String secretAccessKey) { + validate(accessKeyId, secretAccessKey, 0); + this.accessKeyId = accessKeyId; + this.secretAccessKey = secretAccessKey; + } + + /** + * This is the constructor that is used by credential factory to create an instance of credential + * according to the credential information. + */ + public OSSSecretKeyCredential() {} + + @Override + public String credentialType() { + return OSS_SECRET_KEY_CREDENTIAL_TYPE; + } + + @Override + public long expireTimeInMs() { + return 0; + } + + @Override + public Map<String, String> credentialInfo() { + return (new ImmutableMap.Builder<String, String>()) + .put(GRAVITINO_OSS_STATIC_ACCESS_KEY_ID, accessKeyId) + .put(GRAVITINO_OSS_STATIC_SECRET_ACCESS_KEY, secretAccessKey) + .build(); + } + + /** + * Initialize the credential with the credential information. + * + * <p>This method is invoked to deserialize the credential in client side. + * + * @param credentialInfo The credential information from {@link #credentialInfo}. + * @param expireTimeInMs The expire-time from {@link #expireTimeInMs()}. + */ + @Override + public void initialize(Map<String, String> credentialInfo, long expireTimeInMs) { + String accessKeyId = credentialInfo.get(GRAVITINO_OSS_STATIC_ACCESS_KEY_ID); + String secretAccessKey = credentialInfo.get(GRAVITINO_OSS_STATIC_SECRET_ACCESS_KEY); + validate(accessKeyId, secretAccessKey, expireTimeInMs); + this.accessKeyId = accessKeyId; + this.secretAccessKey = secretAccessKey; + } + + /** + * Get OSS static access key ID. + * + * @return The OSS access key ID. + */ + public String accessKeyId() { + return accessKeyId; + } + + /** + * Get OSS static secret access key. + * + * @return The OSS secret access key. + */ + public String secretAccessKey() { + return secretAccessKey; + } + + private void validate(String accessKeyId, String secretAccessKey, long expireTimeInMs) { + Preconditions.checkArgument( + StringUtils.isNotBlank(accessKeyId), "OSS access key Id should not empty"); + Preconditions.checkArgument( + StringUtils.isNotBlank(secretAccessKey), "OSS secret access key should not empty"); + Preconditions.checkArgument( + expireTimeInMs == 0, "The expire time of OSSSecretKeyCredential is not 0"); + } +} diff --git a/api/src/main/resources/META-INF/services/org.apache.gravitino.credential.Credential b/api/src/main/resources/META-INF/services/org.apache.gravitino.credential.Credential index 91d061be7..b6d2dd028 100644 --- a/api/src/main/resources/META-INF/services/org.apache.gravitino.credential.Credential +++ b/api/src/main/resources/META-INF/services/org.apache.gravitino.credential.Credential @@ -21,3 +21,4 @@ org.apache.gravitino.credential.S3TokenCredential org.apache.gravitino.credential.S3SecretKeyCredential org.apache.gravitino.credential.GCSTokenCredential org.apache.gravitino.credential.OSSTokenCredential +org.apache.gravitino.credential.OSSSecretKeyCredential diff --git a/bundles/aliyun-bundle/src/main/java/org/apache/gravitino/oss/credential/OSSSecretKeyProvider.java b/bundles/aliyun-bundle/src/main/java/org/apache/gravitino/oss/credential/OSSSecretKeyProvider.java new file mode 100644 index 000000000..3ee69ce88 --- /dev/null +++ b/bundles/aliyun-bundle/src/main/java/org/apache/gravitino/oss/credential/OSSSecretKeyProvider.java @@ -0,0 +1,54 @@ +/* + * 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.oss.credential; + +import java.util.Map; +import org.apache.gravitino.credential.Credential; +import org.apache.gravitino.credential.CredentialContext; +import org.apache.gravitino.credential.CredentialProvider; +import org.apache.gravitino.credential.OSSSecretKeyCredential; +import org.apache.gravitino.credential.config.OSSCredentialConfig; + +/** Generate OSS access key and secret key to access OSS data. */ +public class OSSSecretKeyProvider implements CredentialProvider { + + private String accessKey; + private String secretKey; + + @Override + public void initialize(Map<String, String> properties) { + OSSCredentialConfig ossCredentialConfig = new OSSCredentialConfig(properties); + this.accessKey = ossCredentialConfig.accessKeyID(); + this.secretKey = ossCredentialConfig.secretAccessKey(); + } + + @Override + public void close() {} + + @Override + public String credentialType() { + return OSSSecretKeyCredential.OSS_SECRET_KEY_CREDENTIAL_TYPE; + } + + @Override + public Credential getCredential(CredentialContext context) { + return new OSSSecretKeyCredential(accessKey, secretKey); + } +} diff --git a/bundles/aliyun-bundle/src/main/resources/META-INF/services/org.apache.gravitino.credential.CredentialProvider b/bundles/aliyun-bundle/src/main/resources/META-INF/services/org.apache.gravitino.credential.CredentialProvider index d2f4be51b..5f76e66bd 100644 --- a/bundles/aliyun-bundle/src/main/resources/META-INF/services/org.apache.gravitino.credential.CredentialProvider +++ b/bundles/aliyun-bundle/src/main/resources/META-INF/services/org.apache.gravitino.credential.CredentialProvider @@ -16,4 +16,5 @@ # specific language governing permissions and limitations # under the License. # -org.apache.gravitino.oss.credential.OSSTokenProvider \ No newline at end of file +org.apache.gravitino.oss.credential.OSSTokenProvider +org.apache.gravitino.oss.credential.OSSSecretKeyProvider \ No newline at end of file diff --git a/common/src/main/java/org/apache/gravitino/credential/CredentialPropertyUtils.java b/common/src/main/java/org/apache/gravitino/credential/CredentialPropertyUtils.java index 8f56f802d..d9fb74903 100644 --- a/common/src/main/java/org/apache/gravitino/credential/CredentialPropertyUtils.java +++ b/common/src/main/java/org/apache/gravitino/credential/CredentialPropertyUtils.java @@ -72,7 +72,7 @@ public class CredentialPropertyUtils { if (credential instanceof S3TokenCredential || credential instanceof S3SecretKeyCredential) { return transformProperties(credential.credentialInfo(), icebergCredentialPropertyMap); } - if (credential instanceof OSSTokenCredential) { + if (credential instanceof OSSTokenCredential || credential instanceof OSSSecretKeyCredential) { return transformProperties(credential.credentialInfo(), icebergCredentialPropertyMap); } return credential.toProperties(); diff --git a/common/src/test/java/org/apache/gravitino/credential/TestCredentialFactory.java b/common/src/test/java/org/apache/gravitino/credential/TestCredentialFactory.java index f5a83e0d3..b873c0afa 100644 --- a/common/src/test/java/org/apache/gravitino/credential/TestCredentialFactory.java +++ b/common/src/test/java/org/apache/gravitino/credential/TestCredentialFactory.java @@ -113,4 +113,29 @@ public class TestCredentialFactory { Assertions.assertEquals("token", ossTokenCredential1.securityToken()); Assertions.assertEquals(expireTime, ossTokenCredential1.expireTimeInMs()); } + + @Test + void testOSSSecretKeyTokenCredential() { + Map<String, String> ossSecretKeyCredentialInfo = + ImmutableMap.of( + OSSSecretKeyCredential.GRAVITINO_OSS_STATIC_ACCESS_KEY_ID, + "accessKeyId", + OSSSecretKeyCredential.GRAVITINO_OSS_STATIC_SECRET_ACCESS_KEY, + "secretAccessKey"); + long expireTime = 0; + Credential ossSecretKeyCredential = + CredentialFactory.create( + OSSSecretKeyCredential.OSS_SECRET_KEY_CREDENTIAL_TYPE, + ossSecretKeyCredentialInfo, + expireTime); + Assertions.assertEquals( + OSSSecretKeyCredential.OSS_SECRET_KEY_CREDENTIAL_TYPE, + ossSecretKeyCredential.credentialType()); + Assertions.assertTrue(ossSecretKeyCredential instanceof OSSSecretKeyCredential); + OSSSecretKeyCredential ossSecretKeyCredential1 = + (OSSSecretKeyCredential) ossSecretKeyCredential; + Assertions.assertEquals("accessKeyId", ossSecretKeyCredential1.accessKeyId()); + Assertions.assertEquals("secretAccessKey", ossSecretKeyCredential1.secretAccessKey()); + Assertions.assertEquals(expireTime, ossSecretKeyCredential1.expireTimeInMs()); + } } diff --git a/docs/iceberg-rest-service.md b/docs/iceberg-rest-service.md index 4f626d314..733ace659 100644 --- a/docs/iceberg-rest-service.md +++ b/docs/iceberg-rest-service.md @@ -130,16 +130,17 @@ To configure the JDBC catalog backend, set the `gravitino.iceberg-rest.warehouse Gravitino Iceberg REST service supports using static access-key-id and secret-access-key or generating temporary token to access OSS data. -| Configuration item | Description | Default value | Required | Since Version | -|---------------------------------------------------|------------------------------------------------------------------------------------------------------------------|-----------------|----------|------------------| -| `gravitino.iceberg-rest.io-impl` | The IO implementation for `FileIO` in Iceberg, use `org.apache.iceberg.aliyun.oss.OSSFileIO` for OSS. | (none) | No | 0.6.0-incubating | -| `gravitino.iceberg-rest.oss-access-key-id` | The static access key ID used to access OSS data. | (none) | No | 0.7.0-incubating | -| `gravitino.iceberg-rest.oss-secret-access-key` | The static secret access key used to access OSS data. | (none) | No | 0.7.0-incubating | -| `gravitino.iceberg-rest.oss-endpoint` | The endpoint of Aliyun OSS service. | (none) | No | 0.7.0-incubating | -| `gravitino.iceberg-rest.oss-region` | The region of the OSS service, like `oss-cn-hangzhou`, only used when `credential-provider-type` is `oss-token`. | (none) | No | 0.8.0-incubating | -| `gravitino.iceberg-rest.oss-role-arn` | The ARN of the role to access the OSS data, only used when `credential-provider-type` is `oss-token`. | (none) | No | 0.8.0-incubating | -| `gravitino.iceberg-rest.oss-external-id` | The OSS external id to generate token, only used when `credential-provider-type` is `oss-token`. | (none) | No | 0.8.0-incubating | -| `gravitino.iceberg-rest.oss-token-expire-in-secs` | The OSS security token expire time in secs, only used when `credential-provider-type` is `oss-token`. | 3600 | No | 0.8.0-incubating | +| Configuration item | Description | Default value | Required | Since Version | +|---------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------|------------------------------------------------------|------------------| +| `gravitino.iceberg-rest.io-impl` | The IO implementation for `FileIO` in Iceberg, use `org.apache.iceberg.aliyun.oss.OSSFileIO` for OSS. | (none) | No | 0.6.0-incubating | +| `gravitino.iceberg-rest.credential-provider-type` | Supports `oss-token` and `oss-secret-key` for OSS. `oss-token` generates a temporary token according to the query data path while `oss-secret-key` using the oss secret access key to access S3 data. | (none) | No | 0.7.0-incubating | +| `gravitino.iceberg-rest.oss-access-key-id` | The static access key ID used to access OSS data. | (none) | No | 0.7.0-incubating | +| `gravitino.iceberg-rest.oss-secret-access-key` | The static secret access key used to access OSS data. | (none) | No | 0.7.0-incubating | +| `gravitino.iceberg-rest.oss-endpoint` | The endpoint of Aliyun OSS service. | (none) | No | 0.7.0-incubating | +| `gravitino.iceberg-rest.oss-region` | The region of the OSS service, like `oss-cn-hangzhou`, only used when `credential-provider-type` is `oss-token`. | (none) | No | 0.8.0-incubating | +| `gravitino.iceberg-rest.oss-role-arn` | The ARN of the role to access the OSS data, only used when `credential-provider-type` is `oss-token`. | (none) | Yes, when `credential-provider-type` is `oss-token`. | 0.8.0-incubating | +| `gravitino.iceberg-rest.oss-external-id` | The OSS external id to generate token, only used when `credential-provider-type` is `oss-token`. | (none) | No | 0.8.0-incubating | +| `gravitino.iceberg-rest.oss-token-expire-in-secs` | The OSS security token expire time in secs, only used when `credential-provider-type` is `oss-token`. | 3600 | No | 0.8.0-incubating | For other Iceberg OSS properties not managed by Gravitino like `client.security-token`, you could config it directly by `gravitino.iceberg-rest.client.security-token`. diff --git a/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/ops/IcebergCatalogWrapper.java b/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/ops/IcebergCatalogWrapper.java index a6da28f7a..05c9ee2a1 100644 --- a/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/ops/IcebergCatalogWrapper.java +++ b/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/ops/IcebergCatalogWrapper.java @@ -76,9 +76,7 @@ public class IcebergCatalogWrapper implements AutoCloseable { IcebergConstants.IO_IMPL, IcebergConstants.AWS_S3_REGION, IcebergConstants.ICEBERG_S3_ENDPOINT, - IcebergConstants.ICEBERG_OSS_ENDPOINT, - IcebergConstants.ICEBERG_OSS_ACCESS_KEY_ID, - IcebergConstants.ICEBERG_OSS_ACCESS_KEY_SECRET); + IcebergConstants.ICEBERG_OSS_ENDPOINT); public IcebergCatalogWrapper(IcebergConfig icebergConfig) { this.catalogBackend = diff --git a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTOSSSecretIT.java b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTOSSSecretIT.java new file mode 100644 index 000000000..73f6262d1 --- /dev/null +++ b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTOSSSecretIT.java @@ -0,0 +1,121 @@ +/* + * 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.iceberg.integration.test; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergConstants; +import org.apache.gravitino.credential.CredentialConstants; +import org.apache.gravitino.credential.OSSSecretKeyCredential; +import org.apache.gravitino.iceberg.common.IcebergConfig; +import org.apache.gravitino.integration.test.util.BaseIT; +import org.apache.gravitino.integration.test.util.DownloaderUtils; +import org.apache.gravitino.integration.test.util.ITUtils; +import org.apache.gravitino.storage.OSSProperties; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; + +@EnabledIfEnvironmentVariable(named = "GRAVITINO_TEST_CLOUD_IT", matches = "true") +public class IcebergRESTOSSSecretIT extends IcebergRESTJdbcCatalogIT { + + private String warehouse; + private String accessKey; + private String secretKey; + private String endpoint; + + @Override + void initEnv() { + this.warehouse = + String.format( + "oss://%s/gravitino-test", + getFromEnvOrDefault("GRAVITINO_OSS_BUCKET", "{BUCKET_NAME}")); + this.accessKey = getFromEnvOrDefault("GRAVITINO_OSS_ACCESS_KEY", "{ACCESS_KEY}"); + this.secretKey = getFromEnvOrDefault("GRAVITINO_OSS_SECRET_KEY", "{SECRET_KEY}"); + this.endpoint = getFromEnvOrDefault("GRAVITINO_OSS_ENDPOINT", "{GRAVITINO_OSS_ENDPOINT}"); + + if (ITUtils.isEmbedded()) { + return; + } + try { + downloadIcebergForAliyunJar(); + } catch (IOException e) { + LOG.warn("Download Iceberg Aliyun bundle jar failed,", e); + throw new RuntimeException(e); + } + copyAliyunOSSJar(); + } + + @Override + public Map<String, String> getCatalogConfig() { + HashMap m = new HashMap<String, String>(); + m.putAll(getCatalogJdbcConfig()); + m.putAll(getOSSConfig()); + return m; + } + + public boolean supportsCredentialVending() { + return true; + } + + private Map<String, String> getOSSConfig() { + Map configMap = new HashMap<String, String>(); + + configMap.put( + IcebergConfig.ICEBERG_CONFIG_PREFIX + CredentialConstants.CREDENTIAL_PROVIDER_TYPE, + OSSSecretKeyCredential.OSS_SECRET_KEY_CREDENTIAL_TYPE); + configMap.put( + IcebergConfig.ICEBERG_CONFIG_PREFIX + OSSProperties.GRAVITINO_OSS_ENDPOINT, endpoint); + configMap.put( + IcebergConfig.ICEBERG_CONFIG_PREFIX + OSSProperties.GRAVITINO_OSS_ACCESS_KEY_ID, accessKey); + configMap.put( + IcebergConfig.ICEBERG_CONFIG_PREFIX + OSSProperties.GRAVITINO_OSS_ACCESS_KEY_SECRET, + secretKey); + + configMap.put( + IcebergConfig.ICEBERG_CONFIG_PREFIX + IcebergConstants.IO_IMPL, + "org.apache.iceberg.aliyun.oss.OSSFileIO"); + configMap.put(IcebergConfig.ICEBERG_CONFIG_PREFIX + IcebergConstants.WAREHOUSE, warehouse); + + return configMap; + } + + private void downloadIcebergForAliyunJar() throws IOException { + String icebergBundleJarName = "iceberg-aliyun-1.5.2.jar"; + String icebergBundleJarUri = + "https://repo1.maven.org/maven2/org/apache/iceberg/" + + "iceberg-aliyun/1.5.2/" + + icebergBundleJarName; + String gravitinoHome = System.getenv("GRAVITINO_HOME"); + String targetDir = String.format("%s/iceberg-rest-server/libs/", gravitinoHome); + DownloaderUtils.downloadFile(icebergBundleJarUri, targetDir); + } + + private void copyAliyunOSSJar() { + String gravitinoHome = System.getenv("GRAVITINO_HOME"); + String targetDir = String.format("%s/iceberg-rest-server/libs/", gravitinoHome); + BaseIT.copyBundleJarsToDirectory("aliyun-bundle", targetDir); + } + + private String getFromEnvOrDefault(String envVar, String defaultValue) { + String envValue = System.getenv(envVar); + return Optional.ofNullable(envValue).orElse(defaultValue); + } +}