-
Notifications
You must be signed in to change notification settings - Fork 332
Service: Validate allowed locations from Catalog Storage Config #2473
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,7 +20,6 @@ | |
|
|
||
| import static org.apache.polaris.service.it.env.PolarisClient.polarisClient; | ||
|
|
||
| import com.google.common.collect.ImmutableMap; | ||
| import java.lang.reflect.Method; | ||
| import java.nio.file.Path; | ||
| import java.nio.file.Paths; | ||
|
|
@@ -88,6 +87,7 @@ public abstract class PolarisRestCatalogViewIntegrationBase extends ViewCatalogT | |
| private static ManagementApi managementApi; | ||
|
|
||
| private RESTCatalog restCatalog; | ||
| private StorageConfigInfo storageConfig; | ||
|
|
||
| @BeforeAll | ||
| static void setup(PolarisApiEndpoints apiEndpoints, ClientCredentials credentials) { | ||
|
|
@@ -114,7 +114,7 @@ public void before(TestInfo testInfo) { | |
| Method method = testInfo.getTestMethod().orElseThrow(); | ||
| String catalogName = client.newEntityName(method.getName()); | ||
|
|
||
| StorageConfigInfo storageConfig = getStorageConfigInfo(); | ||
| storageConfig = getStorageConfigInfo(); | ||
| String defaultBaseLocation = | ||
| storageConfig.getAllowedLocations().getFirst() | ||
| + "/" | ||
|
|
@@ -201,16 +201,10 @@ public void createViewWithCustomMetadataLocationUsingPolaris(@TempDir Path tempD | |
| TableIdentifier identifier = TableIdentifier.of("ns", "view"); | ||
|
|
||
| String location = Paths.get(tempDir.toUri().toString()).toString(); | ||
| String customLocation = Paths.get(tempDir.toUri().toString(), "custom-location").toString(); | ||
| String customLocation2 = Paths.get(tempDir.toUri().toString(), "custom-location2").toString(); | ||
| String customLocationChild = | ||
| Paths.get(tempDir.toUri().toString(), "custom-location/child").toString(); | ||
| String customLocation = | ||
| Paths.get(storageConfig.getAllowedLocations().getFirst(), "/custom-location1").toString(); | ||
|
|
||
| catalog() | ||
| .createNamespace( | ||
| identifier.namespace(), | ||
| ImmutableMap.of( | ||
| IcebergTableLikeEntity.USER_SPECIFIED_WRITE_METADATA_LOCATION_KEY, location)); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't make sense to have a |
||
| catalog().createNamespace(identifier.namespace()); | ||
|
|
||
| Assertions.assertThat(catalog().viewExists(identifier)).as("View should not exist").isFalse(); | ||
|
|
||
|
|
@@ -234,35 +228,5 @@ public void createViewWithCustomMetadataLocationUsingPolaris(@TempDir Path tempD | |
| Assertions.assertThat(((BaseView) view).operations().current().metadataFileLocation()) | ||
| .isNotNull() | ||
| .startsWith(customLocation); | ||
|
|
||
| // CANNOT update the view with a new metadata location `baseLocation/customLocation2`, | ||
| // even though the new location is still under the parent namespace's | ||
| // `write.metadata.path=baseLocation`. | ||
| Assertions.assertThatThrownBy( | ||
| () -> | ||
| catalog() | ||
| .loadView(identifier) | ||
| .updateProperties() | ||
| .set( | ||
| IcebergTableLikeEntity.USER_SPECIFIED_WRITE_METADATA_LOCATION_KEY, | ||
| customLocation2) | ||
| .commit()) | ||
| .isInstanceOf(ForbiddenException.class) | ||
| .hasMessageContaining("Forbidden: Invalid locations"); | ||
|
|
||
| // CANNOT update the view with a child metadata location `baseLocation/customLocation/child`, | ||
| // even though it is a subpath of the original view's | ||
| // `write.metadata.path=baseLocation/customLocation`. | ||
| Assertions.assertThatThrownBy( | ||
| () -> | ||
| catalog() | ||
| .loadView(identifier) | ||
| .updateProperties() | ||
| .set( | ||
| IcebergTableLikeEntity.USER_SPECIFIED_WRITE_METADATA_LOCATION_KEY, | ||
| customLocationChild) | ||
| .commit()) | ||
| .isInstanceOf(ForbiddenException.class) | ||
| .hasMessageContaining("Forbidden: Invalid locations"); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
123 changes: 123 additions & 0 deletions
123
polaris-core/src/main/java/org/apache/polaris/core/storage/LocationRestrictions.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| /* | ||
| * 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.polaris.core.storage; | ||
|
|
||
| import jakarta.annotation.Nonnull; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
| import org.apache.iceberg.catalog.TableIdentifier; | ||
| import org.apache.iceberg.exceptions.ForbiddenException; | ||
| import org.apache.polaris.core.config.RealmConfig; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| /** Defines storage location access restrictions for Polaris entities within a specific context. */ | ||
| public class LocationRestrictions { | ||
| private static final Logger LOGGER = LoggerFactory.getLogger(LocationRestrictions.class); | ||
|
|
||
| /** | ||
| * The complete set of storage locations that are permitted for access. | ||
| * | ||
| * <p>This list contains all storage URIs that entities can read from or write to, including both | ||
| * catalog-level allowed locations and any additional user-specified locations when unstructured | ||
| * table access is enabled. | ||
| * | ||
| * <p>All locations in this list have been validated to conform to the storage type's URI scheme | ||
| * requirements during construction. | ||
| */ | ||
| private final List<String> allowedLocations; | ||
|
|
||
| /** | ||
| * The parent location for structured table enforcement. | ||
| * | ||
| * <p>When non-null, this location represents the root under which all new tables must be created, | ||
| * enforcing a structured hierarchy in addition to residing under {@code allowedLocations}. When | ||
| * null, table creation is allowed anywhere within the {@code allowedLocations}. | ||
| */ | ||
| private final String parentLocation; | ||
|
|
||
| public LocationRestrictions( | ||
| @Nonnull PolarisStorageConfigurationInfo storageConfigurationInfo, String parentLocation) { | ||
| this.allowedLocations = storageConfigurationInfo.getAllowedLocations(); | ||
| allowedLocations.forEach(storageConfigurationInfo::validatePrefixForStorageType); | ||
| this.parentLocation = parentLocation; | ||
| } | ||
|
|
||
| public LocationRestrictions(@Nonnull PolarisStorageConfigurationInfo storageConfigurationInfo) { | ||
| this(storageConfigurationInfo, null); | ||
| } | ||
|
|
||
| /** | ||
| * Validates that the requested storage locations are permitted for the given table identifier. | ||
| * | ||
| * <p>This method performs location validation by checking the requested locations against: | ||
| * | ||
| * <ul> | ||
| * <li>The parent location (if configured) for structured table enforcement | ||
| * <li>The allowed locations list for general access permissions | ||
| * </ul> | ||
| * | ||
| * <p>The validation ensures that all requested locations conform to the storage access | ||
| * restrictions defined for this context. If a parent location is configured, all requests must be | ||
| * under that location hierarchy in addition to being within the allowed locations. | ||
| * | ||
| * @param realmConfig the realm configuration containing storage validation rules | ||
| * @param identifier the table identifier for which locations are being validated | ||
| * @param requestLocations the set of storage locations that need validation | ||
| * @throws ForbiddenException if any of the requested locations violate the configured | ||
| * restrictions | ||
| */ | ||
| public void validate( | ||
| RealmConfig realmConfig, TableIdentifier identifier, Set<String> requestLocations) { | ||
| if (parentLocation != null) { | ||
| validateLocations(realmConfig, List.of(parentLocation), requestLocations, identifier); | ||
| } | ||
|
|
||
| validateLocations(realmConfig, allowedLocations, requestLocations, identifier); | ||
| } | ||
|
|
||
| private void validateLocations( | ||
| RealmConfig realmConfig, | ||
| List<String> allowedLocations, | ||
| Set<String> requestLocations, | ||
| TableIdentifier identifier) { | ||
| var validationResults = | ||
| InMemoryStorageIntegration.validateAllowedLocations( | ||
| realmConfig, allowedLocations, Set.of(PolarisStorageActions.ALL), requestLocations); | ||
| validationResults | ||
| .values() | ||
| .forEach( | ||
| actionResult -> | ||
| actionResult | ||
| .values() | ||
| .forEach( | ||
| result -> { | ||
| if (!result.isSuccess()) { | ||
| throw new ForbiddenException( | ||
| "Invalid locations '%s' for identifier '%s': %s", | ||
| requestLocations, identifier, result.getMessage()); | ||
| } else { | ||
| LOGGER.debug( | ||
| "Validated locations '{}' for identifier '{}'", | ||
| requestLocations, | ||
| identifier); | ||
| } | ||
| })); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 0 additions & 68 deletions
68
polaris-core/src/main/java/org/apache/polaris/core/storage/StorageConfigurationOverride.java
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This location still escape from the storage config. We will need more fixes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The base class does add the
@TempDirto allowedLocations inpolaris/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogViewFileIntegrationTestBase.java
Line 43 in f555f30
Is this a different tempdir that escapes? If we want to exercise a failure we probably need to try to set the
locationto something else.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, they are different tmpDir, the paths generated are different. So the namespace location should not be allowed, not sure why it still passed. I will debug it more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Weird. It works as expected when I moved the integration test to unit test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It turns out the Polaris added a structured location to the view in #1966. When a rest client creates view with location A, the Polaris will automatically adds the prefix like /root/namespace/A, related code
polaris/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalog.java
Line 1244 in 17a359b
The unit tests I added don't have the issue as it doesn't go through that route.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In short, the location checking is fine. I will merge this PR, and file another to fix the view issue.