Skip to content

Commit da23e66

Browse files
authored
Introduce reserved-properties setting; reserve "polaris." by default (#1417)
* initial commit * initial commit * try to test * quarkus fixes * fix a bunch of callsites * Start applying changes * autolint * chase todos * autolint * bugfix * stable * add one test * stable with more tests * autolint * more tests * autolint * stable tests * clean up * oops * stabilize on main * autolint * more changes per review
1 parent 44064cb commit da23e66

File tree

17 files changed

+628
-52
lines changed

17 files changed

+628
-52
lines changed

integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisManagementServiceIntegrationTest.java

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
import org.junit.jupiter.api.BeforeAll;
8585
import org.junit.jupiter.api.Test;
8686
import org.junit.jupiter.api.extension.ExtendWith;
87+
import org.testcontainers.shaded.com.google.common.collect.ImmutableMap;
8788
import org.testcontainers.shaded.org.awaitility.Awaitility;
8889

8990
/**
@@ -2102,6 +2103,129 @@ public void testDropNamespaceStatus() {
21022103
}
21032104
}
21042105

2106+
@Test
2107+
public void testCreateAndUpdateCatalogRoleWithReservedProperties() {
2108+
String catalogName = client.newEntityName("mycatalog1");
2109+
Catalog catalog =
2110+
PolarisCatalog.builder()
2111+
.setType(Catalog.TypeEnum.INTERNAL)
2112+
.setName(catalogName)
2113+
.setProperties(new CatalogProperties("s3://required/base/location"))
2114+
.setStorageConfigInfo(
2115+
new AwsStorageConfigInfo(
2116+
"arn:aws:iam::012345678901:role/jdoe", StorageConfigInfo.StorageTypeEnum.S3))
2117+
.build();
2118+
managementApi.createCatalog(catalog);
2119+
2120+
CatalogRole badCatalogRole =
2121+
new CatalogRole("mycatalogrole", Map.of("polaris.reserved", "foo"), 0L, 0L, 1);
2122+
try (Response response =
2123+
managementApi
2124+
.request("v1/catalogs/{cat}/catalog-roles", Map.of("cat", catalogName))
2125+
.post(Entity.json(new CreateCatalogRoleRequest(badCatalogRole)))) {
2126+
assertThat(response)
2127+
.returns(Response.Status.BAD_REQUEST.getStatusCode(), Response::getStatus);
2128+
}
2129+
2130+
CatalogRole okCatalogRole = new CatalogRole("mycatalogrole", Map.of("foo", "bar"), 0L, 0L, 1);
2131+
try (Response response =
2132+
managementApi
2133+
.request("v1/catalogs/{cat}/catalog-roles", Map.of("cat", catalogName))
2134+
.post(Entity.json(new CreateCatalogRoleRequest(okCatalogRole)))) {
2135+
assertThat(response).returns(Response.Status.CREATED.getStatusCode(), Response::getStatus);
2136+
}
2137+
2138+
UpdateCatalogRoleRequest updateRequest =
2139+
new UpdateCatalogRoleRequest(
2140+
okCatalogRole.getEntityVersion(), Map.of("polaris.reserved", "true"));
2141+
try (Response response =
2142+
managementApi
2143+
.request("v1/catalogs/{cat}/catalog-roles/mycatalogrole", Map.of("cat", catalogName))
2144+
.put(Entity.json(updateRequest))) {
2145+
assertThat(response)
2146+
.returns(Response.Status.BAD_REQUEST.getStatusCode(), Response::getStatus);
2147+
}
2148+
}
2149+
2150+
@Test
2151+
public void testCreateAndUpdatePrincipalRoleWithReservedProperties() {
2152+
String principal = "testCreateAndUpdatePrincipalRoleWithReservedProperties";
2153+
managementApi.createPrincipal(principal);
2154+
2155+
PrincipalRole badPrincipalRole =
2156+
new PrincipalRole(
2157+
client.newEntityName("myprincipalrole"), Map.of("polaris.reserved", "foo"), 0L, 0L, 1);
2158+
try (Response response =
2159+
managementApi
2160+
.request("v1/principal-roles")
2161+
.post(Entity.json(new CreatePrincipalRoleRequest(badPrincipalRole)))) {
2162+
assertThat(response)
2163+
.returns(Response.Status.BAD_REQUEST.getStatusCode(), Response::getStatus);
2164+
}
2165+
2166+
PrincipalRole goodPrincipalRole =
2167+
new PrincipalRole(
2168+
client.newEntityName("myprincipalrole"), Map.of("not.reserved", "foo"), 0L, 0L, 1);
2169+
try (Response response =
2170+
managementApi
2171+
.request("v1/principal-roles")
2172+
.post(Entity.json(new CreatePrincipalRoleRequest(goodPrincipalRole)))) {
2173+
assertThat(response).returns(Response.Status.CREATED.getStatusCode(), Response::getStatus);
2174+
}
2175+
2176+
UpdatePrincipalRoleRequest badUpdate =
2177+
new UpdatePrincipalRoleRequest(
2178+
goodPrincipalRole.getEntityVersion(), ImmutableMap.of("polaris.reserved", "true"));
2179+
try (Response response =
2180+
managementApi
2181+
.request("v1/principal-roles/{pr}", Map.of("pr", goodPrincipalRole.getName()))
2182+
.put(Entity.json(badUpdate))) {
2183+
assertThat(response)
2184+
.returns(Response.Status.BAD_REQUEST.getStatusCode(), Response::getStatus);
2185+
}
2186+
2187+
managementApi.deletePrincipalRole(goodPrincipalRole);
2188+
managementApi.deletePrincipal(principal);
2189+
}
2190+
2191+
@Test
2192+
public void testCreateAndUpdatePrincipalWithReservedProperties() {
2193+
String principal = "testCreateAndUpdatePrincipalWithReservedProperties";
2194+
2195+
Principal badPrincipal =
2196+
new Principal(
2197+
principal, "clientId", ImmutableMap.of("polaris.reserved", "true"), 0L, 0L, 1);
2198+
try (Response response =
2199+
managementApi
2200+
.request("v1/principals")
2201+
.post(Entity.json(new CreatePrincipalRequest(badPrincipal, false)))) {
2202+
assertThat(response)
2203+
.returns(Response.Status.BAD_REQUEST.getStatusCode(), Response::getStatus);
2204+
}
2205+
2206+
Principal goodPrincipal =
2207+
new Principal(principal, "clientId", ImmutableMap.of("not.reserved", "true"), 0L, 0L, 1);
2208+
try (Response response =
2209+
managementApi
2210+
.request("v1/principals")
2211+
.post(Entity.json(new CreatePrincipalRequest(goodPrincipal, false)))) {
2212+
assertThat(response).returns(Response.Status.CREATED.getStatusCode(), Response::getStatus);
2213+
}
2214+
2215+
UpdatePrincipalRequest badUpdate =
2216+
new UpdatePrincipalRequest(
2217+
goodPrincipal.getEntityVersion(), ImmutableMap.of("polaris.reserved", "true"));
2218+
try (Response response =
2219+
managementApi
2220+
.request("v1/principals/{p}", Map.of("p", goodPrincipal.getName()))
2221+
.put(Entity.json(badUpdate))) {
2222+
assertThat(response)
2223+
.returns(Response.Status.BAD_REQUEST.getStatusCode(), Response::getStatus);
2224+
}
2225+
2226+
managementApi.deletePrincipal(principal);
2227+
}
2228+
21052229
public static JWTCreator.Builder defaultJwt() {
21062230
Instant now = Instant.now();
21072231
return JWT.create()

integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogIntegrationTest.java

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@
1919
package org.apache.polaris.service.it.test;
2020

2121
import static jakarta.ws.rs.core.Response.Status.NOT_FOUND;
22-
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
2322
import static org.apache.polaris.service.it.env.PolarisClient.polarisClient;
2423
import static org.assertj.core.api.Assertions.assertThat;
2524
import static org.assertj.core.api.Assertions.assertThatCode;
2625
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2726

2827
import com.google.common.collect.ImmutableMap;
28+
import com.google.common.collect.Sets;
2929
import jakarta.ws.rs.ProcessingException;
3030
import jakarta.ws.rs.client.Entity;
3131
import jakarta.ws.rs.client.Invocation;
@@ -97,6 +97,7 @@
9797
import org.apache.polaris.service.it.env.PolarisApiEndpoints;
9898
import org.apache.polaris.service.it.env.PolarisClient;
9999
import org.apache.polaris.service.it.ext.PolarisIntegrationTestExtension;
100+
import org.apache.polaris.service.types.CreateGenericTableRequest;
100101
import org.apache.polaris.service.types.GenericTable;
101102
import org.assertj.core.api.Assertions;
102103
import org.assertj.core.api.Assumptions;
@@ -1409,7 +1410,7 @@ public void testLoadTableWithSnapshots() {
14091410
assertThatCode(() -> catalogApi.loadTable(currentCatalogName, tableIdentifier, "not-real"))
14101411
.isInstanceOf(RESTException.class)
14111412
.hasMessageContaining("Unrecognized snapshots")
1412-
.hasMessageContaining("code=" + BAD_REQUEST.getStatusCode());
1413+
.hasMessageContaining("code=" + Response.Status.BAD_REQUEST.getStatusCode());
14131414
} finally {
14141415
genericTableApi.purge(currentCatalogName, namespace);
14151416
}
@@ -1450,4 +1451,109 @@ public void testLoadTableWithRefFiltering() {
14501451
genericTableApi.purge(currentCatalogName, namespace);
14511452
}
14521453
}
1454+
1455+
@Test
1456+
public void testCreateGenericTableWithReservedProperty() {
1457+
Namespace namespace = Namespace.of("ns1");
1458+
restCatalog.createNamespace(namespace);
1459+
TableIdentifier tableIdentifier = TableIdentifier.of(namespace, "tbl1");
1460+
1461+
String ns = RESTUtil.encodeNamespace(tableIdentifier.namespace());
1462+
try (Response res =
1463+
genericTableApi
1464+
.request(
1465+
"polaris/v1/{cat}/namespaces/{ns}/generic-tables/",
1466+
Map.of("cat", currentCatalogName, "ns", ns))
1467+
.post(
1468+
Entity.json(
1469+
new CreateGenericTableRequest(
1470+
tableIdentifier.name(),
1471+
"format",
1472+
"doc",
1473+
Map.of("polaris.reserved", "true"))))) {
1474+
Assertions.assertThat(res.getStatus()).isEqualTo(Response.Status.BAD_REQUEST.getStatusCode());
1475+
Assertions.assertThat(res.readEntity(String.class)).contains("reserved prefix");
1476+
}
1477+
1478+
genericTableApi.purge(currentCatalogName, namespace);
1479+
}
1480+
1481+
@Test
1482+
public void testCreateNamespaceWithReservedProperty() {
1483+
Namespace namespace = Namespace.of("ns1");
1484+
assertThatCode(
1485+
() -> {
1486+
restCatalog.createNamespace(namespace, ImmutableMap.of("polaris.reserved", "true"));
1487+
})
1488+
.isInstanceOf(org.apache.iceberg.exceptions.BadRequestException.class)
1489+
.hasMessageContaining("reserved prefix");
1490+
}
1491+
1492+
@Test
1493+
public void testUpdateNamespaceWithReservedProperty() {
1494+
Namespace namespace = Namespace.of("ns1");
1495+
restCatalog.createNamespace(namespace, ImmutableMap.of("a", "b"));
1496+
restCatalog.setProperties(namespace, ImmutableMap.of("c", "d"));
1497+
Assertions.assertThatCode(
1498+
() -> {
1499+
restCatalog.setProperties(namespace, ImmutableMap.of("polaris.reserved", "true"));
1500+
})
1501+
.isInstanceOf(org.apache.iceberg.exceptions.BadRequestException.class)
1502+
.hasMessageContaining("reserved prefix");
1503+
genericTableApi.purge(currentCatalogName, namespace);
1504+
}
1505+
1506+
@Test
1507+
public void testRemoveReservedPropertyFromNamespace() {
1508+
Namespace namespace = Namespace.of("ns1");
1509+
restCatalog.createNamespace(namespace, ImmutableMap.of("a", "b"));
1510+
restCatalog.removeProperties(namespace, Sets.newHashSet("a"));
1511+
Assertions.assertThatCode(
1512+
() -> {
1513+
restCatalog.removeProperties(namespace, Sets.newHashSet("polaris.reserved"));
1514+
})
1515+
.isInstanceOf(org.apache.iceberg.exceptions.BadRequestException.class)
1516+
.hasMessageContaining("reserved prefix");
1517+
genericTableApi.purge(currentCatalogName, namespace);
1518+
}
1519+
1520+
@Test
1521+
public void testCreateTableWithReservedProperty() {
1522+
Namespace namespace = Namespace.of("ns1");
1523+
restCatalog.createNamespace(namespace);
1524+
TableIdentifier identifier = TableIdentifier.of(namespace, "t1");
1525+
Assertions.assertThatCode(
1526+
() -> {
1527+
restCatalog.createTable(
1528+
identifier,
1529+
SCHEMA,
1530+
PartitionSpec.unpartitioned(),
1531+
ImmutableMap.of("polaris.reserved", ""));
1532+
})
1533+
.isInstanceOf(IllegalArgumentException.class)
1534+
.hasMessageContaining("reserved prefix");
1535+
genericTableApi.purge(currentCatalogName, namespace);
1536+
}
1537+
1538+
@Test
1539+
public void testUpdateTableWithReservedProperty() {
1540+
Namespace namespace = Namespace.of("ns1");
1541+
restCatalog.createNamespace(namespace);
1542+
TableIdentifier identifier = TableIdentifier.of(namespace, "t1");
1543+
restCatalog.createTable(identifier, SCHEMA);
1544+
Assertions.assertThatCode(
1545+
() -> {
1546+
var txn =
1547+
restCatalog.newReplaceTableTransaction(
1548+
identifier,
1549+
SCHEMA,
1550+
PartitionSpec.unpartitioned(),
1551+
ImmutableMap.of("polaris.reserved", ""),
1552+
false);
1553+
txn.commitTransaction();
1554+
})
1555+
.isInstanceOf(IllegalArgumentException.class)
1556+
.hasMessageContaining("reserved prefix");
1557+
genericTableApi.purge(currentCatalogName, namespace);
1558+
}
14531559
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.service.quarkus.config;
20+
21+
import io.smallrye.config.ConfigMapping;
22+
import java.util.List;
23+
import org.apache.polaris.service.config.ReservedProperties;
24+
25+
@ConfigMapping(prefix = "polaris.reserved-properties")
26+
public interface QuarkusReservedProperties extends ReservedProperties {
27+
@Override
28+
default List<String> prefixes() {
29+
return List.of("polaris.");
30+
}
31+
}

quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAdminServiceAuthzTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ private PolarisAdminService newTestAdminService(Set<String> activatedPrincipalRo
5555
metaStoreManager,
5656
userSecretsManager,
5757
securityContext(authenticatedPrincipal, activatedPrincipalRoles),
58-
polarisAuthorizer);
58+
polarisAuthorizer,
59+
reservedProperties);
5960
}
6061

6162
private void doTestSufficientPrivileges(

quarkus/service/src/test/java/org/apache/polaris/service/quarkus/admin/PolarisAuthzTestBase.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
import org.apache.polaris.service.catalog.policy.PolicyCatalog;
8787
import org.apache.polaris.service.config.DefaultConfigurationStore;
8888
import org.apache.polaris.service.config.RealmEntityManagerFactory;
89+
import org.apache.polaris.service.config.ReservedProperties;
8990
import org.apache.polaris.service.context.CallContextCatalogFactory;
9091
import org.apache.polaris.service.context.PolarisCallContextCatalogFactory;
9192
import org.apache.polaris.service.events.PolarisEventListener;
@@ -181,6 +182,7 @@ public Map<String, String> getConfigOverrides() {
181182
Map.of(
182183
FeatureConfiguration.ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING.key,
183184
true)));
185+
protected final ReservedProperties reservedProperties = ReservedProperties.NONE;
184186

185187
@Inject protected MetaStoreManagerFactory managerFactory;
186188
@Inject protected RealmEntityManagerFactory realmEntityManagerFactory;
@@ -264,7 +266,8 @@ public void before(TestInfo testInfo) {
264266
metaStoreManager,
265267
userSecretsManager,
266268
securityContext(authenticatedRoot, Set.of()),
267-
polarisAuthorizer);
269+
polarisAuthorizer,
270+
reservedProperties);
268271

269272
String storageLocation = "file:///tmp/authz";
270273
FileStorageConfigInfo storageConfigModel =

quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/GenericTableCatalogTest.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
import org.apache.polaris.service.catalog.io.DefaultFileIOFactory;
8282
import org.apache.polaris.service.catalog.io.FileIOFactory;
8383
import org.apache.polaris.service.config.RealmEntityManagerFactory;
84+
import org.apache.polaris.service.config.ReservedProperties;
8485
import org.apache.polaris.service.events.NoOpPolarisEventListener;
8586
import org.apache.polaris.service.storage.PolarisStorageIntegrationProviderImpl;
8687
import org.apache.polaris.service.task.TaskExecutor;
@@ -141,6 +142,7 @@ public Map<String, String> getConfigOverrides() {
141142
private AuthenticatedPolarisPrincipal authenticatedRoot;
142143
private PolarisEntity catalogEntity;
143144
private SecurityContext securityContext;
145+
private ReservedProperties reservedProperties;
144146

145147
protected static final Schema SCHEMA =
146148
new Schema(
@@ -195,14 +197,18 @@ public void before(TestInfo testInfo) {
195197
securityContext = Mockito.mock(SecurityContext.class);
196198
when(securityContext.getUserPrincipal()).thenReturn(authenticatedRoot);
197199
when(securityContext.isUserInRole(isA(String.class))).thenReturn(true);
200+
201+
reservedProperties = ReservedProperties.NONE;
202+
198203
adminService =
199204
new PolarisAdminService(
200205
callContext,
201206
entityManager,
202207
metaStoreManager,
203208
userSecretsManager,
204209
securityContext,
205-
new PolarisAuthorizerImpl(new PolarisConfigurationStore() {}));
210+
new PolarisAuthorizerImpl(new PolarisConfigurationStore() {}),
211+
reservedProperties);
206212

207213
String storageLocation = "s3://my-bucket/path/to/data";
208214
storageConfigModel =

0 commit comments

Comments
 (0)