Browse Source

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

Lorenzo Dematté 7 months ago
parent
commit
67d0dd4df2

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

@@ -1214,6 +1214,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)
     )
         .flatMap(Function.identity())

+ 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

@@ -49,6 +49,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;
@@ -74,6 +75,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;
@@ -644,6 +646,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);
         }
     }
 
@@ -651,6 +655,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);
         }
     }
 
@@ -658,6 +664,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);
         }
     }
 
@@ -665,6 +673,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);
         }
     }
 
@@ -672,6 +682,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);
         }
     }
 
@@ -681,22 +693,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());
         }
     }
 
@@ -704,6 +731,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());
         }
     }
 
@@ -711,6 +740,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());
         }
     }
 
@@ -718,6 +749,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());
         }
     }
 
@@ -725,6 +758,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());
         }
     }
 
@@ -732,6 +767,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());
         }
     }
 
@@ -739,6 +776,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());
         }
     }
 
@@ -751,6 +790,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     ) {
         if (isNetworkUrlConnection(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(that)) {
+            checkURLFileRead(callerClass, that.getURL());
         }
     }
 
@@ -763,6 +804,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     ) {
         if (isNetworkUrlConnection(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(that)) {
+            checkURLFileRead(callerClass, that.getURL());
         }
     }
 
@@ -775,6 +818,8 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     ) {
         if (isNetworkUrlConnection(that)) {
             policyManager.checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(that)) {
+            checkURLFileRead(callerClass, that.getURL());
         }
     }
 
@@ -782,6 +827,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());
         }
     }
 
@@ -789,6 +836,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());
         }
     }
 
@@ -817,6 +866,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());
         }
     }
 
@@ -824,6 +875,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());
         }
     }
 
@@ -831,6 +884,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());
         }
     }
 
@@ -838,6 +893,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());
         }
     }
 
@@ -845,6 +902,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());
         }
     }
 
@@ -852,6 +911,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());
         }
     }
 
@@ -2724,4 +2785,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());
+    }
 }