Browse Source

Validate `Authorization` header in Azure test fixture (#111242)

Today the Azure test fixture accepts all requests, but we should be
checking that the `Authorization` header is at least present and
approximately correct. This commit adds support for this check.
David Turner 1 year ago
parent
commit
97f6294531

+ 1 - 2
modules/repository-azure/src/internalClusterTest/java/org/elasticsearch/repositories/azure/AzureBlobStoreRepositoryTests.java

@@ -153,9 +153,8 @@ public class AzureBlobStoreRepositoryTests extends ESMockAPIBasedRepositoryInteg
 
     @SuppressForbidden(reason = "this test uses a HttpHandler to emulate an Azure endpoint")
     private static class AzureBlobStoreHttpHandler extends AzureHttpHandler implements BlobStoreHttpHandler {
-
         AzureBlobStoreHttpHandler(final String account, final String container) {
-            super(account, container);
+            super(account, container, null /* no auth header validation */);
         }
     }
 

+ 5 - 2
modules/repository-azure/src/internalClusterTest/java/org/elasticsearch/repositories/azure/AzureStorageCleanupThirdPartyTests.java

@@ -44,11 +44,14 @@ import static org.hamcrest.Matchers.not;
 public class AzureStorageCleanupThirdPartyTests extends AbstractThirdPartyRepositoryTestCase {
     private static final boolean USE_FIXTURE = Booleans.parseBoolean(System.getProperty("test.azure.fixture", "true"));
 
+    private static final String AZURE_ACCOUNT = System.getProperty("test.azure.account");
+
     @ClassRule
     public static AzureHttpFixture fixture = new AzureHttpFixture(
         USE_FIXTURE ? AzureHttpFixture.Protocol.HTTP : AzureHttpFixture.Protocol.NONE,
-        System.getProperty("test.azure.account"),
-        System.getProperty("test.azure.container")
+        AZURE_ACCOUNT,
+        System.getProperty("test.azure.container"),
+        AzureHttpFixture.startsWithPredicate("SharedKey " + AZURE_ACCOUNT + ":")
     );
 
     @Override

+ 2 - 1
modules/repository-azure/src/yamlRestTest/java/org/elasticsearch/repositories/azure/RepositoryAzureClientYamlTestSuiteIT.java

@@ -32,7 +32,8 @@ public class RepositoryAzureClientYamlTestSuiteIT extends ESClientYamlSuiteTestC
     private static AzureHttpFixture fixture = new AzureHttpFixture(
         USE_FIXTURE ? AzureHttpFixture.Protocol.HTTPS : AzureHttpFixture.Protocol.NONE,
         AZURE_TEST_ACCOUNT,
-        AZURE_TEST_CONTAINER
+        AZURE_TEST_CONTAINER,
+        AzureHttpFixture.startsWithPredicate("SharedKey " + AZURE_TEST_ACCOUNT + ":")
     );
 
     private static TestTrustStore trustStore = new TestTrustStore(

+ 21 - 3
test/fixtures/azure-fixture/src/main/java/fixture/azure/AzureHttpFixture.java

@@ -25,6 +25,7 @@ import java.security.SecureRandom;
 import java.security.cert.Certificate;
 import java.util.List;
 import java.util.Objects;
+import java.util.function.Predicate;
 
 import javax.net.ssl.KeyManager;
 import javax.net.ssl.SSLContext;
@@ -38,6 +39,8 @@ public class AzureHttpFixture extends ExternalResource {
     private final Protocol protocol;
     private final String account;
     private final String container;
+    private final Predicate<String> authHeaderPredicate;
+
     private HttpServer server;
 
     public enum Protocol {
@@ -46,10 +49,25 @@ public class AzureHttpFixture extends ExternalResource {
         HTTPS
     }
 
-    public AzureHttpFixture(Protocol protocol, String account, String container) {
+    public static Predicate<String> startsWithPredicate(String expectedPrefix) {
+        return new Predicate<>() {
+            @Override
+            public boolean test(String s) {
+                return s.startsWith(expectedPrefix);
+            }
+
+            @Override
+            public String toString() {
+                return "startsWith[" + expectedPrefix + "]";
+            }
+        };
+    }
+
+    public AzureHttpFixture(Protocol protocol, String account, String container, Predicate<String> authHeaderPredicate) {
         this.protocol = protocol;
         this.account = account;
         this.container = container;
+        this.authHeaderPredicate = authHeaderPredicate;
     }
 
     private String scheme() {
@@ -72,7 +90,7 @@ public class AzureHttpFixture extends ExternalResource {
                 }
                 case HTTP -> {
                     server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
-                    server.createContext("/" + account, new AzureHttpHandler(account, container));
+                    server.createContext("/" + account, new AzureHttpHandler(account, container, authHeaderPredicate));
                     server.start();
                 }
                 case HTTPS -> {
@@ -93,7 +111,7 @@ public class AzureHttpFixture extends ExternalResource {
                         new SecureRandom()
                     );
                     httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
-                    httpsServer.createContext("/" + account, new AzureHttpHandler(account, container));
+                    httpsServer.createContext("/" + account, new AzureHttpHandler(account, container, authHeaderPredicate));
                     httpsServer.start();
                 }
             }

+ 57 - 1
test/fixtures/azure-fixture/src/main/java/fixture/azure/AzureHttpHandler.java

@@ -15,9 +15,12 @@ import org.elasticsearch.common.bytes.BytesArray;
 import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.io.Streams;
 import org.elasticsearch.common.regex.Regex;
+import org.elasticsearch.core.Nullable;
 import org.elasticsearch.core.SuppressForbidden;
 import org.elasticsearch.rest.RestStatus;
 import org.elasticsearch.rest.RestUtils;
+import org.elasticsearch.xcontent.XContentBuilder;
+import org.elasticsearch.xcontent.XContentType;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -32,6 +35,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Predicate;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -45,15 +49,67 @@ public class AzureHttpHandler implements HttpHandler {
     private final Map<String, BytesReference> blobs;
     private final String account;
     private final String container;
+    private final Predicate<String> authHeaderPredicate;
 
-    public AzureHttpHandler(final String account, final String container) {
+    public AzureHttpHandler(final String account, final String container, @Nullable Predicate<String> authHeaderPredicate) {
         this.account = Objects.requireNonNull(account);
         this.container = Objects.requireNonNull(container);
+        this.authHeaderPredicate = authHeaderPredicate;
         this.blobs = new ConcurrentHashMap<>();
     }
 
+    private static List<String> getAuthHeader(HttpExchange exchange) {
+        return exchange.getRequestHeaders().get("Authorization");
+    }
+
+    private boolean isValidAuthHeader(HttpExchange exchange) {
+        if (authHeaderPredicate == null) {
+            return true;
+        }
+
+        final var authHeader = getAuthHeader(exchange);
+        if (authHeader == null) {
+            return false;
+        }
+
+        if (authHeader.size() != 1) {
+            return false;
+        }
+
+        return authHeaderPredicate.test(authHeader.get(0));
+    }
+
     @Override
     public void handle(final HttpExchange exchange) throws IOException {
+        if (isValidAuthHeader(exchange) == false) {
+            try (exchange; var xcb = XContentBuilder.builder(XContentType.JSON.xContent())) {
+                xcb.startObject();
+                xcb.field("method", exchange.getRequestMethod());
+                xcb.field("uri", exchange.getRequestURI().toString());
+                xcb.field("predicate", authHeaderPredicate.toString());
+                xcb.field("authorization", Objects.toString(getAuthHeader(exchange)));
+                xcb.startObject("headers");
+                for (final var header : exchange.getRequestHeaders().entrySet()) {
+                    if (header.getValue() == null) {
+                        xcb.nullField(header.getKey());
+                    } else {
+                        xcb.startArray(header.getKey());
+                        for (final var value : header.getValue()) {
+                            xcb.value(value);
+                        }
+                        xcb.endArray();
+                    }
+                }
+                xcb.endObject();
+                xcb.endObject();
+                final var responseBytes = BytesReference.bytes(xcb);
+                exchange.getResponseHeaders().add("Content-Type", "application/json; charset=utf-8");
+                exchange.sendResponseHeaders(RestStatus.FORBIDDEN.getStatus(), responseBytes.length());
+                responseBytes.writeTo(exchange.getResponseBody());
+                return;
+            }
+        }
+
         final String request = exchange.getRequestMethod() + " " + exchange.getRequestURI().toString();
         if (request.startsWith("GET") || request.startsWith("HEAD") || request.startsWith("DELETE")) {
             int read = exchange.getRequestBody().read();

+ 2 - 1
x-pack/plugin/repositories-metering-api/qa/azure/src/javaRestTest/java/org/elasticsearch/xpack/repositories/metering/azure/AzureRepositoriesMeteringIT.java

@@ -30,7 +30,8 @@ public class AzureRepositoriesMeteringIT extends AbstractRepositoriesMeteringAPI
     private static AzureHttpFixture fixture = new AzureHttpFixture(
         USE_FIXTURE ? AzureHttpFixture.Protocol.HTTPS : AzureHttpFixture.Protocol.NONE,
         AZURE_TEST_ACCOUNT,
-        AZURE_TEST_CONTAINER
+        AZURE_TEST_CONTAINER,
+        AzureHttpFixture.startsWithPredicate("SharedKey " + AZURE_TEST_ACCOUNT + ":")
     );
 
     private static TestTrustStore trustStore = new TestTrustStore(

+ 2 - 1
x-pack/plugin/searchable-snapshots/qa/azure/src/javaRestTest/java/org/elasticsearch/xpack/searchablesnapshots/AzureSearchableSnapshotsIT.java

@@ -31,7 +31,8 @@ public class AzureSearchableSnapshotsIT extends AbstractSearchableSnapshotsRestT
     private static AzureHttpFixture fixture = new AzureHttpFixture(
         USE_FIXTURE ? AzureHttpFixture.Protocol.HTTPS : AzureHttpFixture.Protocol.NONE,
         AZURE_TEST_ACCOUNT,
-        AZURE_TEST_CONTAINER
+        AZURE_TEST_CONTAINER,
+        AzureHttpFixture.startsWithPredicate("SharedKey " + AZURE_TEST_ACCOUNT + ":")
     );
 
     private static TestTrustStore trustStore = new TestTrustStore(

+ 2 - 1
x-pack/plugin/snapshot-based-recoveries/qa/azure/src/javaRestTest/java/org/elasticsearch/xpack/snapshotbasedrecoveries/recovery/AzureSnapshotBasedRecoveryIT.java

@@ -30,7 +30,8 @@ public class AzureSnapshotBasedRecoveryIT extends AbstractSnapshotBasedRecoveryR
     private static AzureHttpFixture fixture = new AzureHttpFixture(
         USE_FIXTURE ? AzureHttpFixture.Protocol.HTTPS : AzureHttpFixture.Protocol.NONE,
         AZURE_TEST_ACCOUNT,
-        AZURE_TEST_CONTAINER
+        AZURE_TEST_CONTAINER,
+        AzureHttpFixture.startsWithPredicate("SharedKey " + AZURE_TEST_ACCOUNT + ":")
     );
 
     private static TestTrustStore trustStore = new TestTrustStore(

+ 2 - 1
x-pack/plugin/snapshot-repo-test-kit/qa/azure/src/javaRestTest/java/org/elasticsearch/repositories/blobstore/testkit/AzureSnapshotRepoTestKitIT.java

@@ -29,7 +29,8 @@ public class AzureSnapshotRepoTestKitIT extends AbstractSnapshotRepoTestKitRestT
     private static AzureHttpFixture fixture = new AzureHttpFixture(
         USE_FIXTURE ? AzureHttpFixture.Protocol.HTTPS : AzureHttpFixture.Protocol.NONE,
         AZURE_TEST_ACCOUNT,
-        AZURE_TEST_CONTAINER
+        AZURE_TEST_CONTAINER,
+        AzureHttpFixture.startsWithPredicate("SharedKey " + AZURE_TEST_ACCOUNT + ":")
     );
 
     private static TestTrustStore trustStore = new TestTrustStore(