This is an automated email from the ASF dual-hosted git repository.
adutra pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git
The following commit(s) were added to refs/heads/main by this push:
new a75bbdd9 Prevent DoS attacks by rejecting unknown realms (#594)
a75bbdd9 is described below
commit a75bbdd904e655e22dbf1d0834b7a6e470ad1f72
Author: Alexandre Dutra <[email protected]>
AuthorDate: Tue Jan 14 12:29:35 2025 +0100
Prevent DoS attacks by rejecting unknown realms (#594)
---
.../src/main/resources/application.properties | 8 +--
.../quarkus/TimedApplicationEventListenerTest.java | 2 +-
.../polaris/service/quarkus/auth/TokenUtils.java | 2 +-
.../polaris/service/quarkus/catalog/TestUtil.java | 2 +-
.../service/quarkus/ratelimiter/TestUtil.java | 2 +-
.../test/PolarisIntegrationTestFixture.java | 2 +-
.../context/DefaultRealmContextResolver.java | 60 ++++------------------
.../service/context/RealmContextConfiguration.java | 17 +++++-
...Resolver.java => TestRealmContextResolver.java} | 8 +--
9 files changed, 39 insertions(+), 64 deletions(-)
diff --git a/quarkus/service/src/main/resources/application.properties
b/quarkus/service/src/main/resources/application.properties
index ba30712b..0915eb3f 100644
--- a/quarkus/service/src/main/resources/application.properties
+++ b/quarkus/service/src/main/resources/application.properties
@@ -72,8 +72,9 @@ quarkus.otel.sdk.disabled=false
# quarkus.otel.traces.sampler=parentbased_always_on
# quarkus.otel.traces.sampler.arg=1.0d
-polaris.realm-context.default-realm=default-realm
polaris.realm-context.type=default
+polaris.realm-context.realms=realm1,realm2,realm3
+polaris.realm-context.header-name=Polaris-Realm
polaris.features.defaults."ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING"=false
polaris.features.defaults."SUPPORTED_CATALOG_STORAGE_TYPES"=["S3","GCS","AZURE","FILE"]
@@ -85,7 +86,7 @@ polaris.persistence.type=in-memory
polaris.file-io.type=default
-polaris.log.request-id-header-name=request_id
+polaris.log.request-id-header-name=Polaris-Request-Id
# polaris.log.mdc.aid=polaris
# polaris.log.mdc.sid=polaris-service
@@ -138,7 +139,8 @@
polaris.authentication.token-broker.max-token-generation=PT1H
%test.polaris.features.defaults."INITIALIZE_DEFAULT_CATALOG_FILEIO_FOR_TEST"=true
%test.polaris.features.defaults."SKIP_CREDENTIAL_SUBSCOPING_INDIRECTION"=true
%test.polaris.features.defaults."SUPPORTED_CATALOG_STORAGE_TYPES"=["FILE","S3","GCS","AZURE"]
-%test.polaris.realm-context.default-realm=POLARIS
+%test.polaris.realm-context.realms=POLARIS
+%test.polaris.realm-context.type=test
%test.polaris.storage.aws.access-key=accessKey
%test.polaris.storage.aws.secret-key=secretKey
%test.polaris.storage.gcp.token=token
diff --git
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/TimedApplicationEventListenerTest.java
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/TimedApplicationEventListenerTest.java
index fe5e574b..acd60ede 100644
---
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/TimedApplicationEventListenerTest.java
+++
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/TimedApplicationEventListenerTest.java
@@ -18,7 +18,7 @@
*/
package org.apache.polaris.service.quarkus;
-import static
org.apache.polaris.service.context.DefaultRealmContextResolver.REALM_PROPERTY_KEY;
+import static
org.apache.polaris.service.context.TestRealmContextResolver.REALM_PROPERTY_KEY;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.InstanceOfAssertFactories.type;
diff --git
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/auth/TokenUtils.java
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/auth/TokenUtils.java
index 606d87a1..09432308 100644
---
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/auth/TokenUtils.java
+++
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/auth/TokenUtils.java
@@ -19,7 +19,7 @@
package org.apache.polaris.service.quarkus.auth;
import static
org.apache.polaris.service.auth.BasePolarisAuthenticator.PRINCIPAL_ROLE_ALL;
-import static
org.apache.polaris.service.context.DefaultRealmContextResolver.REALM_PROPERTY_KEY;
+import static
org.apache.polaris.service.context.TestRealmContextResolver.REALM_PROPERTY_KEY;
import static org.assertj.core.api.Assertions.assertThat;
import jakarta.ws.rs.client.Client;
diff --git
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/TestUtil.java
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/TestUtil.java
index de9c0aab..5d4ee446 100644
---
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/TestUtil.java
+++
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/TestUtil.java
@@ -18,7 +18,7 @@
*/
package org.apache.polaris.service.quarkus.catalog;
-import static
org.apache.polaris.service.context.DefaultRealmContextResolver.REALM_PROPERTY_KEY;
+import static
org.apache.polaris.service.context.TestRealmContextResolver.REALM_PROPERTY_KEY;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableMap;
diff --git
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/ratelimiter/TestUtil.java
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/ratelimiter/TestUtil.java
index a27f1c31..80c32880 100644
---
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/ratelimiter/TestUtil.java
+++
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/ratelimiter/TestUtil.java
@@ -18,7 +18,7 @@
*/
package org.apache.polaris.service.quarkus.ratelimiter;
-import static
org.apache.polaris.service.context.DefaultRealmContextResolver.REALM_PROPERTY_KEY;
+import static
org.apache.polaris.service.context.TestRealmContextResolver.REALM_PROPERTY_KEY;
import static org.assertj.core.api.Assertions.assertThat;
import jakarta.ws.rs.core.Response;
diff --git
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/test/PolarisIntegrationTestFixture.java
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/test/PolarisIntegrationTestFixture.java
index 5f7e769a..68f32758 100644
---
a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/test/PolarisIntegrationTestFixture.java
+++
b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/test/PolarisIntegrationTestFixture.java
@@ -18,7 +18,7 @@
*/
package org.apache.polaris.service.quarkus.test;
-import static
org.apache.polaris.service.context.DefaultRealmContextResolver.REALM_PROPERTY_KEY;
+import static
org.apache.polaris.service.context.TestRealmContextResolver.REALM_PROPERTY_KEY;
import static org.assertj.core.api.Assertions.assertThat;
import com.fasterxml.jackson.core.JsonProcessingException;
diff --git
a/service/common/src/main/java/org/apache/polaris/service/context/DefaultRealmContextResolver.java
b/service/common/src/main/java/org/apache/polaris/service/context/DefaultRealmContextResolver.java
index c3c7a0bd..c12cdd42 100644
---
a/service/common/src/main/java/org/apache/polaris/service/context/DefaultRealmContextResolver.java
+++
b/service/common/src/main/java/org/apache/polaris/service/context/DefaultRealmContextResolver.java
@@ -18,28 +18,15 @@
*/
package org.apache.polaris.service.context;
-import com.google.common.base.Splitter;
import io.smallrye.common.annotation.Identifier;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
-import java.util.HashMap;
import java.util.Map;
import org.apache.polaris.core.context.RealmContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-/**
- * For local/dev testing, this resolver simply expects a custom bearer-token
format that is a
- * semicolon-separated list of colon-separated key/value pairs that constitute
the realm properties.
- *
- * <p>Example: principal:data-engineer;password:test;realm:acct123
- */
@ApplicationScoped
@Identifier("default")
public class DefaultRealmContextResolver implements RealmContextResolver {
- private static final Logger LOGGER =
LoggerFactory.getLogger(DefaultRealmContextResolver.class);
-
- public static final String REALM_PROPERTY_KEY = "realm";
private final RealmContextConfiguration configuration;
@@ -51,47 +38,18 @@ public class DefaultRealmContextResolver implements
RealmContextResolver {
@Override
public RealmContext resolveRealmContext(
String requestURL, String method, String path, Map<String, String>
headers) {
- // Since this default resolver is strictly for use in test/dev
environments, we'll consider
- // it safe to log all contents. Any "real" resolver used in a prod
environment should make
- // sure to only log non-sensitive contents.
- LOGGER.debug(
- "Resolving RealmContext for method: {}, path: {}, headers: {}",
method, path, headers);
- Map<String, String> parsedProperties = parseBearerTokenAsKvPairs(headers);
-
- if (!parsedProperties.containsKey(REALM_PROPERTY_KEY)
- && headers.containsKey(REALM_PROPERTY_KEY)) {
- parsedProperties.put(REALM_PROPERTY_KEY,
headers.get(REALM_PROPERTY_KEY));
- }
- if (!parsedProperties.containsKey(REALM_PROPERTY_KEY)) {
- LOGGER.warn(
- "Failed to parse {} from headers; using {}",
- REALM_PROPERTY_KEY,
- configuration.defaultRealm());
- parsedProperties.put(REALM_PROPERTY_KEY, configuration.defaultRealm());
- }
- String realmId = parsedProperties.get(REALM_PROPERTY_KEY);
- return () -> realmId;
- }
+ String realm;
- /**
- * Returns kv pairs parsed from the "Authorization: Bearer
k1:v1;k2:v2;k3:v3" header if it exists;
- * if missing, returns empty map.
- */
- static Map<String, String> parseBearerTokenAsKvPairs(Map<String, String>
headers) {
- Map<String, String> parsedProperties = new HashMap<>();
- if (headers != null) {
- String authHeader = headers.get("Authorization");
- if (authHeader != null) {
- String[] parts = authHeader.split(" ");
- if (parts.length == 2 && "Bearer".equalsIgnoreCase(parts[0])) {
- if
(parts[1].matches("[\\w\\d=_+-]+:[\\w\\d=+_-]+(?:;[\\w\\d=+_-]+:[\\w\\d=+_-]+)*"))
{
- parsedProperties.putAll(
-
Splitter.on(';').trimResults().withKeyValueSeparator(':').split(parts[1]));
- }
- }
+ if (headers.containsKey(configuration.headerName())) {
+ realm = headers.get(configuration.headerName());
+ if (!configuration.realms().contains(realm)) {
+ throw new IllegalArgumentException("Unknown realm: " + realm);
}
+ } else {
+ realm = configuration.defaultRealm();
}
- return parsedProperties;
+
+ return () -> realm;
}
}
diff --git
a/service/common/src/main/java/org/apache/polaris/service/context/RealmContextConfiguration.java
b/service/common/src/main/java/org/apache/polaris/service/context/RealmContextConfiguration.java
index 0147d8c2..2e4735e2 100644
---
a/service/common/src/main/java/org/apache/polaris/service/context/RealmContextConfiguration.java
+++
b/service/common/src/main/java/org/apache/polaris/service/context/RealmContextConfiguration.java
@@ -18,8 +18,23 @@
*/
package org.apache.polaris.service.context;
+import jakarta.validation.constraints.Size;
+import java.util.Set;
+
public interface RealmContextConfiguration {
+ /**
+ * The set of realms that are supported by the realm context resolver. The
first realm is
+ * considered the default realm.
+ */
+ @Size(min = 1)
+ Set<String> realms();
+
+ /** The header name that contains the realm identifier. */
+ String headerName();
+
/** The default realm to use when no realm is specified. */
- String defaultRealm();
+ default String defaultRealm() {
+ return realms().iterator().next();
+ }
}
diff --git
a/service/common/src/main/java/org/apache/polaris/service/context/DefaultRealmContextResolver.java
b/service/common/src/main/java/org/apache/polaris/service/context/TestRealmContextResolver.java
similarity index 93%
copy from
service/common/src/main/java/org/apache/polaris/service/context/DefaultRealmContextResolver.java
copy to
service/common/src/main/java/org/apache/polaris/service/context/TestRealmContextResolver.java
index c3c7a0bd..122a5436 100644
---
a/service/common/src/main/java/org/apache/polaris/service/context/DefaultRealmContextResolver.java
+++
b/service/common/src/main/java/org/apache/polaris/service/context/TestRealmContextResolver.java
@@ -35,8 +35,8 @@ import org.slf4j.LoggerFactory;
* <p>Example: principal:data-engineer;password:test;realm:acct123
*/
@ApplicationScoped
-@Identifier("default")
-public class DefaultRealmContextResolver implements RealmContextResolver {
+@Identifier("test")
+public class TestRealmContextResolver implements RealmContextResolver {
private static final Logger LOGGER =
LoggerFactory.getLogger(DefaultRealmContextResolver.class);
public static final String REALM_PROPERTY_KEY = "realm";
@@ -44,7 +44,7 @@ public class DefaultRealmContextResolver implements
RealmContextResolver {
private final RealmContextConfiguration configuration;
@Inject
- public DefaultRealmContextResolver(RealmContextConfiguration configuration) {
+ public TestRealmContextResolver(RealmContextConfiguration configuration) {
this.configuration = configuration;
}
@@ -78,7 +78,7 @@ public class DefaultRealmContextResolver implements
RealmContextResolver {
* Returns kv pairs parsed from the "Authorization: Bearer
k1:v1;k2:v2;k3:v3" header if it exists;
* if missing, returns empty map.
*/
- static Map<String, String> parseBearerTokenAsKvPairs(Map<String, String>
headers) {
+ private static Map<String, String> parseBearerTokenAsKvPairs(Map<String,
String> headers) {
Map<String, String> parsedProperties = new HashMap<>();
if (headers != null) {
String authHeader = headers.get("Authorization");