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");

Reply via email to