Browse Source

[Entitlements] Add URLConnection instrumentation for file protocol (#123824) (#124064)

Lorenzo Dematté 7 months ago
parent
commit
27d80f2d9c

+ 20 - 0
libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/EntitlementChecker.java

@@ -1148,6 +1148,26 @@ public interface EntitlementChecker {
         WatchEvent.Modifier... modifiers
     );
 
+    // URLConnection
+
+    void check$sun_net_www_protocol_file_FileURLConnection$connect(Class<?> callerClass, java.net.URLConnection that);
+
+    void check$sun_net_www_protocol_file_FileURLConnection$getHeaderFields(Class<?> callerClass, java.net.URLConnection that);
+
+    void check$sun_net_www_protocol_file_FileURLConnection$getHeaderField(Class<?> callerClass, java.net.URLConnection that, String name);
+
+    void check$sun_net_www_protocol_file_FileURLConnection$getHeaderField(Class<?> callerClass, java.net.URLConnection that, int n);
+
+    void check$sun_net_www_protocol_file_FileURLConnection$getContentLength(Class<?> callerClass, java.net.URLConnection that);
+
+    void check$sun_net_www_protocol_file_FileURLConnection$getContentLengthLong(Class<?> callerClass, java.net.URLConnection that);
+
+    void check$sun_net_www_protocol_file_FileURLConnection$getHeaderFieldKey(Class<?> callerClass, java.net.URLConnection that, int n);
+
+    void check$sun_net_www_protocol_file_FileURLConnection$getLastModified(Class<?> callerClass, java.net.URLConnection that);
+
+    void check$sun_net_www_protocol_file_FileURLConnection$getInputStream(Class<?> callerClass, java.net.URLConnection that);
+
     ////////////////////
     //
     // Thread management

+ 5 - 0
libs/entitlement/qa/entitled-plugin/src/main/java/org/elasticsearch/entitlement/qa/entitled/EntitledActions.java

@@ -71,4 +71,9 @@ public final class EntitledActions {
     public static URLConnection createFtpURLConnection() throws IOException {
         return URI.create("ftp://127.0.0.1:12345/").toURL().openConnection();
     }
+
+    public static URLConnection createFileURLConnection() throws IOException {
+        var fileUrl = createTempFileForWrite().toUri().toURL();
+        return fileUrl.openConnection();
+    }
 }

+ 1 - 0
libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/RestEntitlementsCheckAction.java

@@ -194,6 +194,7 @@ public class RestEntitlementsCheckAction extends BaseRestHandler {
         getTestEntries(PathActions.class),
         getTestEntries(SpiActions.class),
         getTestEntries(SystemActions.class),
+        getTestEntries(URLConnectionFileActions.class),
         getTestEntries(URLConnectionNetworkActions.class),
         getTestEntries(VersionSpecificManageThreadsActions.class),
         getTestEntries(VersionSpecificNioFileSystemActions.class)

+ 117 - 0
libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/URLConnectionFileActions.java

@@ -0,0 +1,117 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the "Elastic License
+ * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
+ * Public License v 1"; you may not use this file except in compliance with, at
+ * your election, the "Elastic License 2.0", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
+ */
+
+package org.elasticsearch.entitlement.qa.test;
+
+import org.elasticsearch.core.CheckedConsumer;
+import org.elasticsearch.entitlement.qa.entitled.EntitledActions;
+
+import java.io.IOException;
+import java.net.URLConnection;
+
+import static org.elasticsearch.entitlement.qa.test.EntitlementTest.ExpectedAccess.PLUGINS;
+
+class URLConnectionFileActions {
+
+    private static void withJdkFileConnection(CheckedConsumer<URLConnection, Exception> connectionConsumer) throws Exception {
+        var conn = EntitledActions.createFileURLConnection();
+        // Be sure we got the connection implementation we want
+        assert conn.getClass().getSimpleName().equals("FileURLConnection");
+        try {
+            connectionConsumer.accept(conn);
+        } catch (IOException e) {
+            // It's OK, it means we passed entitlement checks, and we tried to perform some operation
+        }
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void sunFileURLConnectionConnect() throws Exception {
+        withJdkFileConnection(URLConnection::connect);
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void sunFileURLConnectionGetHeaderFields() throws Exception {
+        withJdkFileConnection(URLConnection::getHeaderFields);
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void sunFileURLConnectionGetHeaderFieldWithName() throws Exception {
+        withJdkFileConnection(urlConnection -> urlConnection.getHeaderField("date"));
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void sunFileURLConnectionGetHeaderFieldWithIndex() throws Exception {
+        withJdkFileConnection(urlConnection -> urlConnection.getHeaderField(0));
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void sunFileURLConnectionGetContentLength() throws Exception {
+        withJdkFileConnection(URLConnection::getContentLength);
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void sunFileURLConnectionGetContentLengthLong() throws Exception {
+        withJdkFileConnection(URLConnection::getContentLengthLong);
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void sunFileURLConnectionGetHeaderFieldKey() throws Exception {
+        withJdkFileConnection(urlConnection -> urlConnection.getHeaderFieldKey(0));
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void sunFileURLConnectionGetLastModified() throws Exception {
+        withJdkFileConnection(URLConnection::getLastModified);
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void sunFileURLConnectionGetInputStream() throws Exception {
+        withJdkFileConnection(URLConnection::getInputStream);
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void sunFileURLConnectionGetContentType() throws Exception {
+        withJdkFileConnection(URLConnection::getContentType);
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void sunFileURLConnectionGetContentEncoding() throws Exception {
+        withJdkFileConnection(URLConnection::getContentEncoding);
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void sunFileURLConnectionGetExpiration() throws Exception {
+        withJdkFileConnection(URLConnection::getExpiration);
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void sunFileURLConnectionGetDate() throws Exception {
+        withJdkFileConnection(URLConnection::getDate);
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void sunFileURLConnectionGetHeaderFieldInt() throws Exception {
+        withJdkFileConnection(conn -> conn.getHeaderFieldInt("field", 0));
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void sunFileURLConnectionGetHeaderFieldLong() throws Exception {
+        withJdkFileConnection(conn -> conn.getHeaderFieldLong("field", 0));
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void sunFileURLConnectionGetContent() throws Exception {
+        withJdkFileConnection(URLConnection::getContent);
+    }
+
+    @EntitlementTest(expectedAccess = PLUGINS)
+    static void sunFileURLConnectionGetContentWithClasses() throws Exception {
+        withJdkFileConnection(conn -> conn.getContent(new Class<?>[] { String.class }));
+    }
+}

+ 123 - 0
libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java

@@ -42,6 +42,7 @@ import java.net.Socket;
 import java.net.SocketAddress;
 import java.net.SocketImplFactory;
 import java.net.URI;
+import java.net.URISyntaxException;
 import java.net.URL;
 import java.net.URLStreamHandler;
 import java.net.URLStreamHandlerFactory;
@@ -67,6 +68,7 @@ import java.nio.file.Files;
 import java.nio.file.LinkOption;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.nio.file.StandardOpenOption;
 import java.nio.file.WatchEvent;
 import java.nio.file.WatchService;
@@ -636,6 +638,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     public void check$java_net_URL$openConnection(Class<?> callerClass, java.net.URL that) {
         if (isNetworkUrl(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrl(that)) {
+            checkURLFileRead(callerClass, that);
         }
     }
 
@@ -643,6 +647,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     public void check$java_net_URL$openConnection(Class<?> callerClass, URL that, Proxy proxy) {
         if (proxy.type() != Proxy.Type.DIRECT || isNetworkUrl(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrl(that)) {
+            checkURLFileRead(callerClass, that);
         }
     }
 
@@ -650,6 +656,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     public void check$java_net_URL$openStream(Class<?> callerClass, java.net.URL that) {
         if (isNetworkUrl(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrl(that)) {
+            checkURLFileRead(callerClass, that);
         }
     }
 
@@ -657,6 +665,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     public void check$java_net_URL$getContent(Class<?> callerClass, java.net.URL that) {
         if (isNetworkUrl(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrl(that)) {
+            checkURLFileRead(callerClass, that);
         }
     }
 
@@ -664,6 +674,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     public void check$java_net_URL$getContent(Class<?> callerClass, java.net.URL that, Class<?>[] classes) {
         if (isNetworkUrl(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrl(that)) {
+            checkURLFileRead(callerClass, that);
         }
     }
 
@@ -673,22 +685,37 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
         "sun.net.www.protocol.mailto.MailToURLConnection"
     );
 
+    private static final List<String> FILE_URL_CONNECT_CLASS_NAMES = List.of("sun.net.www.protocol.file.FileURLConnection");
+
     private static final Set<String> NETWORK_PROTOCOLS = Set.of("http", "https", "ftp", "mailto");
 
+    private static final Set<String> FILE_PROTOCOLS = Set.of("file");
+
     private static boolean isNetworkUrl(java.net.URL url) {
         return NETWORK_PROTOCOLS.contains(url.getProtocol());
     }
 
+    private static boolean isFileUrl(java.net.URL url) {
+        return FILE_PROTOCOLS.contains(url.getProtocol());
+    }
+
     private static boolean isNetworkUrlConnection(java.net.URLConnection urlConnection) {
         var connectionClass = urlConnection.getClass();
         return HttpURLConnection.class.isAssignableFrom(connectionClass)
             || ADDITIONAL_NETWORK_URL_CONNECT_CLASS_NAMES.contains(connectionClass.getName());
     }
 
+    private static boolean isFileUrlConnection(java.net.URLConnection urlConnection) {
+        var connectionClass = urlConnection.getClass();
+        return FILE_URL_CONNECT_CLASS_NAMES.contains(connectionClass.getName());
+    }
+
     @Override
     public void check$java_net_URLConnection$getContentLength(Class<?> callerClass, java.net.URLConnection that) {
         if (isNetworkUrlConnection(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(that)) {
+            checkURLFileRead(callerClass, that.getURL());
         }
     }
 
@@ -696,6 +723,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     public void check$java_net_URLConnection$getContentLengthLong(Class<?> callerClass, java.net.URLConnection that) {
         if (isNetworkUrlConnection(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(that)) {
+            checkURLFileRead(callerClass, that.getURL());
         }
     }
 
@@ -703,6 +732,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     public void check$java_net_URLConnection$getContentType(Class<?> callerClass, java.net.URLConnection that) {
         if (isNetworkUrlConnection(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(that)) {
+            checkURLFileRead(callerClass, that.getURL());
         }
     }
 
@@ -710,6 +741,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     public void check$java_net_URLConnection$getContentEncoding(Class<?> callerClass, java.net.URLConnection that) {
         if (isNetworkUrlConnection(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(that)) {
+            checkURLFileRead(callerClass, that.getURL());
         }
     }
 
@@ -717,6 +750,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     public void check$java_net_URLConnection$getExpiration(Class<?> callerClass, java.net.URLConnection that) {
         if (isNetworkUrlConnection(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(that)) {
+            checkURLFileRead(callerClass, that.getURL());
         }
     }
 
@@ -724,6 +759,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     public void check$java_net_URLConnection$getDate(Class<?> callerClass, java.net.URLConnection that) {
         if (isNetworkUrlConnection(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(that)) {
+            checkURLFileRead(callerClass, that.getURL());
         }
     }
 
@@ -731,6 +768,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     public void check$java_net_URLConnection$getLastModified(Class<?> callerClass, java.net.URLConnection that) {
         if (isNetworkUrlConnection(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(that)) {
+            checkURLFileRead(callerClass, that.getURL());
         }
     }
 
@@ -743,6 +782,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     ) {
         if (isNetworkUrlConnection(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(that)) {
+            checkURLFileRead(callerClass, that.getURL());
         }
     }
 
@@ -755,6 +796,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     ) {
         if (isNetworkUrlConnection(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(that)) {
+            checkURLFileRead(callerClass, that.getURL());
         }
     }
 
@@ -767,6 +810,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     ) {
         if (isNetworkUrlConnection(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(that)) {
+            checkURLFileRead(callerClass, that.getURL());
         }
     }
 
@@ -774,6 +819,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     public void check$java_net_URLConnection$getContent(Class<?> callerClass, java.net.URLConnection that) {
         if (isNetworkUrlConnection(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(that)) {
+            checkURLFileRead(callerClass, that.getURL());
         }
     }
 
@@ -781,6 +828,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     public void check$java_net_URLConnection$getContent(Class<?> callerClass, java.net.URLConnection that, Class<?>[] classes) {
         if (isNetworkUrlConnection(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(that)) {
+            checkURLFileRead(callerClass, that.getURL());
         }
     }
 
@@ -809,6 +858,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     public void check$sun_net_www_URLConnection$getHeaderField(Class<?> callerClass, java.net.URLConnection that, String name) {
         if (isNetworkUrlConnection(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(that)) {
+            checkURLFileRead(callerClass, that.getURL());
         }
     }
 
@@ -816,6 +867,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     public void check$sun_net_www_URLConnection$getHeaderFields(Class<?> callerClass, java.net.URLConnection that) {
         if (isNetworkUrlConnection(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(that)) {
+            checkURLFileRead(callerClass, that.getURL());
         }
     }
 
@@ -823,6 +876,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     public void check$sun_net_www_URLConnection$getHeaderFieldKey(Class<?> callerClass, java.net.URLConnection that, int n) {
         if (isNetworkUrlConnection(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(that)) {
+            checkURLFileRead(callerClass, that.getURL());
         }
     }
 
@@ -830,6 +885,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     public void check$sun_net_www_URLConnection$getHeaderField(Class<?> callerClass, java.net.URLConnection that, int n) {
         if (isNetworkUrlConnection(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(that)) {
+            checkURLFileRead(callerClass, that.getURL());
         }
     }
 
@@ -837,6 +894,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     public void check$sun_net_www_URLConnection$getContentType(Class<?> callerClass, java.net.URLConnection that) {
         if (isNetworkUrlConnection(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(that)) {
+            checkURLFileRead(callerClass, that.getURL());
         }
     }
 
@@ -844,6 +903,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     public void check$sun_net_www_URLConnection$getContentLength(Class<?> callerClass, java.net.URLConnection that) {
         if (isNetworkUrlConnection(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(that)) {
+            checkURLFileRead(callerClass, that.getURL());
         }
     }
 
@@ -2621,4 +2682,66 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     ) {
         policyManager.checkFileRead(callerClass, that);
     }
+
+    private void checkURLFileRead(Class<?> callerClass, URL url) {
+        try {
+            policyManager.checkFileRead(callerClass, Paths.get(url.toURI()));
+        } catch (URISyntaxException e) {
+            // We expect this method to be called only on File URLs; otherwise the underlying method would fail anyway
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void check$sun_net_www_protocol_file_FileURLConnection$connect(Class<?> callerClass, java.net.URLConnection that) {
+        checkURLFileRead(callerClass, that.getURL());
+    }
+
+    @Override
+    public void check$sun_net_www_protocol_file_FileURLConnection$getHeaderFields(Class<?> callerClass, java.net.URLConnection that) {
+        checkURLFileRead(callerClass, that.getURL());
+    }
+
+    @Override
+    public void check$sun_net_www_protocol_file_FileURLConnection$getHeaderField(
+        Class<?> callerClass,
+        java.net.URLConnection that,
+        String name
+    ) {
+        checkURLFileRead(callerClass, that.getURL());
+    }
+
+    @Override
+    public void check$sun_net_www_protocol_file_FileURLConnection$getHeaderField(Class<?> callerClass, java.net.URLConnection that, int n) {
+        checkURLFileRead(callerClass, that.getURL());
+    }
+
+    @Override
+    public void check$sun_net_www_protocol_file_FileURLConnection$getContentLength(Class<?> callerClass, java.net.URLConnection that) {
+        checkURLFileRead(callerClass, that.getURL());
+    }
+
+    @Override
+    public void check$sun_net_www_protocol_file_FileURLConnection$getContentLengthLong(Class<?> callerClass, java.net.URLConnection that) {
+        checkURLFileRead(callerClass, that.getURL());
+    }
+
+    @Override
+    public void check$sun_net_www_protocol_file_FileURLConnection$getHeaderFieldKey(
+        Class<?> callerClass,
+        java.net.URLConnection that,
+        int n
+    ) {
+        checkURLFileRead(callerClass, that.getURL());
+    }
+
+    @Override
+    public void check$sun_net_www_protocol_file_FileURLConnection$getLastModified(Class<?> callerClass, java.net.URLConnection that) {
+        checkURLFileRead(callerClass, that.getURL());
+    }
+
+    @Override
+    public void check$sun_net_www_protocol_file_FileURLConnection$getInputStream(Class<?> callerClass, java.net.URLConnection that) {
+        checkURLFileRead(callerClass, that.getURL());
+    }
 }