diff --git a/build.gradle b/build.gradle index 187574da9e62a..652248240b4bf 100644 --- a/build.gradle +++ b/build.gradle @@ -433,12 +433,18 @@ gradle.projectsEvaluated { project.tasks.withType(Test) { task -> if (task != null) { - if (BuildParams.runtimeJavaVersion > JavaVersion.VERSION_17) { + if (BuildParams.runtimeJavaVersion > JavaVersion.VERSION_17 && BuildParams.runtimeJavaVersion <= JavaVersion.VERSION_23) { task.jvmArgs += ["-Djava.security.manager=allow"] } if (BuildParams.runtimeJavaVersion >= JavaVersion.VERSION_20) { task.jvmArgs += ["--add-modules=jdk.incubator.vector"] } + + // Add Java Agent for security sandboxing + if (!(project.path in [':build-tools', ":libs:agent-sm:bootstrap", ":libs:agent-sm:agent"])) { + dependsOn(project(':libs:agent-sm:agent').copyJars) + jvmArgs += ["-javaagent:" + project(':libs:agent-sm:agent').jar.archiveFile.get()] + } } } diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 65986f2361c9d..e8459443e8a04 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -110,12 +110,12 @@ dependencies { api 'com.netflix.nebula:gradle-info-plugin:12.1.6' api 'org.apache.rat:apache-rat:0.15' api "commons-io:commons-io:${props.getProperty('commonsio')}" - api "net.java.dev.jna:jna:5.14.0" + api "net.java.dev.jna:jna:5.16.0" api 'com.gradleup.shadow:shadow-gradle-plugin:8.3.5' api 'org.jdom:jdom2:2.0.6.1' api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${props.getProperty('kotlin')}" api 'de.thetaphi:forbiddenapis:3.8' - api 'com.avast.gradle:gradle-docker-compose-plugin:0.17.6' + api 'com.avast.gradle:gradle-docker-compose-plugin:0.17.12' api "org.yaml:snakeyaml:${props.getProperty('snakeyaml')}" api 'org.apache.maven:maven-model:3.9.6' api 'com.networknt:json-schema-validator:1.2.0' diff --git a/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java b/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java index d79dfb1124757..4932009132457 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/OpenSearchTestBasePlugin.java @@ -115,7 +115,8 @@ public void execute(Task t) { test.jvmArgs("--illegal-access=warn"); } } - if (test.getJavaVersion().compareTo(JavaVersion.VERSION_17) > 0) { + if (test.getJavaVersion().compareTo(JavaVersion.VERSION_17) > 0 + && test.getJavaVersion().compareTo(JavaVersion.VERSION_24) < 0) { test.jvmArgs("-Djava.security.manager=allow"); } } diff --git a/buildSrc/src/main/java/org/opensearch/gradle/test/DistroTestPlugin.java b/buildSrc/src/main/java/org/opensearch/gradle/test/DistroTestPlugin.java index 888cd8d4bf5b5..654af7da65662 100644 --- a/buildSrc/src/main/java/org/opensearch/gradle/test/DistroTestPlugin.java +++ b/buildSrc/src/main/java/org/opensearch/gradle/test/DistroTestPlugin.java @@ -77,9 +77,9 @@ import java.util.stream.Stream; public class DistroTestPlugin implements Plugin { - private static final String SYSTEM_JDK_VERSION = "21.0.6+7"; + private static final String SYSTEM_JDK_VERSION = "23.0.2+7"; private static final String SYSTEM_JDK_VENDOR = "adoptium"; - private static final String GRADLE_JDK_VERSION = "21.0.6+7"; + private static final String GRADLE_JDK_VERSION = "23.0.2+7"; private static final String GRADLE_JDK_VENDOR = "adoptium"; // all distributions used by distro tests. this is temporary until tests are per distribution diff --git a/client/rest-high-level/src/test/resources/org/opensearch/bootstrap/test.policy b/client/rest-high-level/src/test/resources/org/opensearch/bootstrap/test.policy index 2604c2492d8ab..96cd3e9f148cf 100644 --- a/client/rest-high-level/src/test/resources/org/opensearch/bootstrap/test.policy +++ b/client/rest-high-level/src/test/resources/org/opensearch/bootstrap/test.policy @@ -8,4 +8,5 @@ grant { permission java.net.SocketPermission "*", "connect,resolve"; + permission java.net.NetPermission "accessUnixDomainSocket"; }; diff --git a/distribution/archives/build.gradle b/distribution/archives/build.gradle index 792b1ab57ddbc..65faad621aa73 100644 --- a/distribution/archives/build.gradle +++ b/distribution/archives/build.gradle @@ -38,6 +38,9 @@ CopySpec archiveFiles(CopySpec modulesFiles, String distributionType, String pla into('lib') { with libFiles() } + into('agent') { + with agentFiles() + } into('config') { dirPermissions { unix 0750 @@ -226,3 +229,9 @@ subprojects { group = "org.opensearch.distribution" } + +tasks.each { + if (it.name.startsWith("build")) { + it.dependsOn project(':libs:agent-sm:agent').copyJars, project(':libs:agent-sm:agent').assemble + } +} diff --git a/distribution/build.gradle b/distribution/build.gradle index 8fe9a89059a50..72477fabd636b 100644 --- a/distribution/build.gradle +++ b/distribution/build.gradle @@ -357,6 +357,18 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) { } } + agentFiles = { + copySpec { + from(project(':libs:agent-sm:agent').copyJars) { + include '**/*.jar' + exclude '**/*-javadoc.jar' + exclude '**/*-sources.jar' + // strip the version since jvm.options is using agent without version + rename("opensearch-agent-${project.version}.jar", "opensearch-agent.jar") + } + } + } + modulesFiles = { platform -> copySpec { eachFile { diff --git a/distribution/src/config/jvm.options b/distribution/src/config/jvm.options index a8c96f33ce51d..2e3d8474b9a3b 100644 --- a/distribution/src/config/jvm.options +++ b/distribution/src/config/jvm.options @@ -77,7 +77,7 @@ ${error.file} 9-:-Xlog:gc*,gc+age=trace,safepoint:file=${loggc}:utctime,pid,tags:filecount=32,filesize=64m # Explicitly allow security manager (https://bugs.openjdk.java.net/browse/JDK-8270380) -18-:-Djava.security.manager=allow +18-23:-Djava.security.manager=allow # JDK 20+ Incubating Vector Module for SIMD optimizations; # disabling may reduce performance on vector optimized lucene @@ -89,3 +89,6 @@ ${error.file} # See please https://bugs.openjdk.org/browse/JDK-8341127 (openjdk/jdk#21283) 23:-XX:CompileCommand=dontinline,java/lang/invoke/MethodHandle.setAsTypeCache 23:-XX:CompileCommand=dontinline,java/lang/invoke/MethodHandle.asTypeUncached + +# It should be JDK-24 (but we cannot bring JDK-24 since Gradle does not support it yet) +21-:-javaagent:agent/opensearch-agent.jar diff --git a/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/SystemJvmOptions.java b/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/SystemJvmOptions.java index af7138569972a..5bedb3ac5ca3e 100644 --- a/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/SystemJvmOptions.java +++ b/distribution/tools/launchers/src/main/java/org/opensearch/tools/launchers/SystemJvmOptions.java @@ -85,7 +85,7 @@ static List systemJvmOptions() { } private static String allowSecurityManagerOption() { - if (Runtime.version().feature() > 17) { + if (Runtime.version().feature() > 17 && Runtime.version().feature() < 24) { return "-Djava.security.manager=allow"; } else { return ""; diff --git a/gradle/ide.gradle b/gradle/ide.gradle index c16205468d63d..79df92abec2e5 100644 --- a/gradle/ide.gradle +++ b/gradle/ide.gradle @@ -82,7 +82,7 @@ if (System.getProperty('idea.active') == 'true') { runConfigurations { defaults(JUnit) { vmParameters = '-ea -Djava.locale.providers=SPI,CLDR' - if (BuildParams.runtimeJavaVersion > JavaVersion.VERSION_17) { + if (BuildParams.runtimeJavaVersion > JavaVersion.VERSION_17 && BuildParams.runtimeJavaVersion < JavaVersion.VERSION_24) { vmParameters += ' -Djava.security.manager=allow' } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4ccb794137c14..105fc2e6ed720 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ opensearch = "3.0.0" lucene = "10.1.0" bundled_jdk_vendor = "adoptium" -bundled_jdk = "21.0.6+7" +bundled_jdk = "23.0.2+7" # optional dependencies spatial4j = "0.7" @@ -31,7 +31,7 @@ grpc = "1.68.2" json_smart = "2.5.2" # when updating the JNA version, also update the version in buildSrc/build.gradle -jna = "5.13.0" +jna = "5.16.0" netty = "4.1.118.Final" joda = "2.12.7" @@ -70,9 +70,9 @@ password4j = "1.8.2" randomizedrunner = "2.7.1" junit = "4.13.2" hamcrest = "2.1" -mockito = "5.14.2" +mockito = "5.16.0" objenesis = "3.3" -bytebuddy = "1.15.10" +bytebuddy = "1.17.3" # benchmark dependencies jmh = "1.35" diff --git a/libs/agent-sm/agent-policy/build.gradle b/libs/agent-sm/agent-policy/build.gradle index a44c2c1349909..cf24b0524b1d7 100644 --- a/libs/agent-sm/agent-policy/build.gradle +++ b/libs/agent-sm/agent-policy/build.gradle @@ -22,6 +22,13 @@ base { disableTasks('forbiddenApisMain') +test.enabled = false +testingConventions.enabled = false + +tasks.named('jarHell').configure { + enabled = false +} + dependencies { testImplementation(project(":test:framework")) } diff --git a/libs/agent-sm/agent-policy/src/main/java/org/opensearch/secure_sm/policy/Password.java b/libs/agent-sm/agent-policy/src/main/java/org/opensearch/secure_sm/policy/Password.java deleted file mode 100644 index ffe5f734fa0ea..0000000000000 --- a/libs/agent-sm/agent-policy/src/main/java/org/opensearch/secure_sm/policy/Password.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.secure_sm.policy; - -import java.io.ByteArrayInputStream; -import java.io.Console; -import java.io.IOException; -import java.io.InputStream; -import java.io.PushbackInputStream; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.CharsetEncoder; -import java.nio.charset.CodingErrorAction; -import java.util.Arrays; - -/** - * Adapted from: https://github.com/openjdk/jdk23u/blob/master/src/java.base/share/classes/sun/security/util/Password.java - */ -public class Password { - /** Reads user password from given input stream. */ - public static char[] readPassword(InputStream in) throws IOException { - return readPassword(in, false); - } - - /** Reads user password from given input stream. - * @param isEchoOn true if the password should be echoed on the screen - */ - @SuppressWarnings("fallthrough") - public static char[] readPassword(InputStream in, boolean isEchoOn) throws IOException { - - char[] consoleEntered = null; - byte[] consoleBytes = null; - - try { - // Use the new java.io.Console class - Console con = null; - if (!isEchoOn && in == System.in && ((con = System.console()) != null)) { - consoleEntered = con.readPassword(); - // readPassword returns "" if you just print ENTER, - // to be compatible with old Password class, change to null - if (consoleEntered != null && consoleEntered.length == 0) { - return null; - } - consoleBytes = convertToBytes(consoleEntered); - in = new ByteArrayInputStream(consoleBytes); - } - - // Rest of the lines still necessary for KeyStoreLoginModule - // and when there is no console. - - char[] lineBuffer; - char[] buf; - int i; - - buf = lineBuffer = new char[128]; - - int room = buf.length; - int offset = 0; - int c; - - boolean done = false; - while (!done) { - switch (c = in.read()) { - case -1: - case '\n': - done = true; - break; - - case '\r': - int c2 = in.read(); - if ((c2 != '\n') && (c2 != -1)) { - if (!(in instanceof PushbackInputStream)) { - in = new PushbackInputStream(in); - } - ((PushbackInputStream) in).unread(c2); - } else { - done = true; - break; - } - /* fall through */ - default: - if (--room < 0) { - buf = new char[offset + 128]; - room = buf.length - offset - 1; - System.arraycopy(lineBuffer, 0, buf, 0, offset); - Arrays.fill(lineBuffer, ' '); - lineBuffer = buf; - } - buf[offset++] = (char) c; - break; - } - } - - if (offset == 0) { - return null; - } - - char[] ret = new char[offset]; - System.arraycopy(buf, 0, ret, 0, offset); - Arrays.fill(buf, ' '); - - return ret; - } finally { - if (consoleEntered != null) { - Arrays.fill(consoleEntered, ' '); - } - if (consoleBytes != null) { - Arrays.fill(consoleBytes, (byte) 0); - } - } - } - - /** - * Change a password read from Console.readPassword() into - * its original bytes. - * - * @param pass a char[] - * @return its byte[] format, similar to new String(pass).getBytes() - */ - private static byte[] convertToBytes(char[] pass) { - if (enc == null) { - synchronized (Password.class) { - enc = System.console() - .charset() - .newEncoder() - .onMalformedInput(CodingErrorAction.REPLACE) - .onUnmappableCharacter(CodingErrorAction.REPLACE); - } - } - byte[] ba = new byte[(int) (enc.maxBytesPerChar() * pass.length)]; - ByteBuffer bb = ByteBuffer.wrap(ba); - synchronized (enc) { - enc.reset().encode(CharBuffer.wrap(pass), bb, true); - } - if (bb.position() < ba.length) { - ba[bb.position()] = '\n'; - } - return ba; - } - - private static volatile CharsetEncoder enc; -} diff --git a/libs/agent-sm/agent-policy/src/main/java/org/opensearch/secure_sm/policy/PolicyFile.java b/libs/agent-sm/agent-policy/src/main/java/org/opensearch/secure_sm/policy/PolicyFile.java index 14b1a8f56375c..2ac7aa8eac727 100644 --- a/libs/agent-sm/agent-policy/src/main/java/org/opensearch/secure_sm/policy/PolicyFile.java +++ b/libs/agent-sm/agent-policy/src/main/java/org/opensearch/secure_sm/policy/PolicyFile.java @@ -1,47 +1,11 @@ -/* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - package org.opensearch.secure_sm.policy; -import javax.security.auth.Subject; -import javax.security.auth.x500.X500Principal; - import java.io.File; +import java.io.FileInputStream; import java.io.FilePermission; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.ObjectInputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; @@ -49,43 +13,29 @@ import java.net.SocketPermission; import java.net.URI; import java.net.URL; -import java.security.AllPermission; import java.security.CodeSource; -import java.security.KeyStore; -import java.security.KeyStoreException; import java.security.Permission; import java.security.PermissionCollection; import java.security.Permissions; -import java.security.Principal; import java.security.ProtectionDomain; import java.security.Security; -import java.security.SecurityPermission; import java.security.UnresolvedPermission; import java.security.cert.Certificate; -import java.security.cert.X509Certificate; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; import java.util.List; -import java.util.Map; -import java.util.PropertyPermission; import java.util.Set; -import java.util.StringTokenizer; import java.util.concurrent.ConcurrentHashMap; import static java.nio.charset.StandardCharsets.UTF_8; /** - * Adapted from: https://github.com/openjdk/jdk23u/blob/master/src/java.base/share/classes/sun/security/provider/PolicyFile.java + * Adapted from: + * https://github.com/openjdk/jdk23u/blob/master/src/java.base/share/classes/sun/security/provider/PolicyFile.java */ @SuppressWarnings("removal") public class PolicyFile extends java.security.Policy { - private static final String SELF = "${{self}}"; - private static final String X500PRINCIPAL = "javax.security.auth.x500.X500Principal"; private static final String POLICY = "java.security.policy"; private static final String POLICY_URL = "policy.url."; @@ -95,8 +45,6 @@ public class PolicyFile extends java.security.Policy { // can be updated if refresh() is called private volatile PolicyInfo policyInfo; - private boolean expandProperties = true; - private boolean allowSystemProperties = true; private boolean notUtf8 = false; private URL url; @@ -115,14 +63,6 @@ public class PolicyFile extends java.security.Policy { */ private static Set badPolicyURLs = Collections.newSetFromMap(new ConcurrentHashMap()); - /** - * Initializes the Policy object and reads the default policy - * configuration file(s) into the Policy object. - */ - public PolicyFile() { - init((URL) null); - } - /** * Initializes the Policy object and reads the default policy * from the specified URL only. @@ -179,31 +119,29 @@ private void initPolicyFile(final PolicyInfo newInfo, final URL url) { private boolean initPolicyFile(final String propname, final String urlname, final PolicyInfo newInfo) { boolean loaded_policy = false; - if (allowSystemProperties) { - String extra_policy = System.getProperty(propname); - if (extra_policy != null) { - boolean overrideAll = false; - if (extra_policy.startsWith("=")) { - overrideAll = true; - extra_policy = extra_policy.substring(1); + String extra_policy = System.getProperty(propname); + if (extra_policy != null) { + boolean overrideAll = false; + if (extra_policy.startsWith("=")) { + overrideAll = true; + extra_policy = extra_policy.substring(1); + } + try { + extra_policy = PropertyExpander.expand(extra_policy); + URL policyURL; + + File policyFile = new File(extra_policy); + if (policyFile.exists()) { + policyURL = ParseUtil.fileToEncodedURL(new File(policyFile.getCanonicalPath())); + } else { + policyURL = newURL(extra_policy); } - try { - extra_policy = PropertyExpander.expand(extra_policy); - URL policyURL; - - File policyFile = new File(extra_policy); - if (policyFile.exists()) { - policyURL = ParseUtil.fileToEncodedURL(new File(policyFile.getCanonicalPath())); - } else { - policyURL = newURL(extra_policy); - } - if (init(policyURL, newInfo)) { - loaded_policy = true; - } - } catch (Exception e) {} - if (overrideAll) { - return Boolean.valueOf(loaded_policy); + if (init(policyURL, newInfo)) { + loaded_policy = true; } + } catch (Exception e) {} + if (overrideAll) { + return Boolean.valueOf(loaded_policy); } } @@ -249,28 +187,15 @@ private boolean init(URL policy, PolicyInfo newInfo) { return false; } - try (InputStreamReader isr = getInputStreamReader(PolicyUtil.getInputStream(policy))) { + try (InputStreamReader isr = getInputStreamReader(getInputStream(policy))) { - PolicyParser pp = new PolicyParser(expandProperties); + PolicyParser pp = new PolicyParser(); pp.read(isr); - KeyStore keyStore = null; - try { - keyStore = PolicyUtil.getKeyStore( - policy, - pp.getKeyStoreUrl(), - pp.getKeyStoreType(), - pp.getKeyStoreProvider(), - pp.getStorePassURL() - ); - } catch (Exception e) { - // ignore, treat it like we have no keystore - } - Enumeration enum_ = pp.grantElements(); while (enum_.hasMoreElements()) { PolicyParser.GrantEntry ge = enum_.nextElement(); - addGrantEntry(ge, keyStore, newInfo); + addGrantEntry(ge, newInfo); } return true; } catch (PolicyParser.ParsingException pe) { @@ -282,6 +207,16 @@ private boolean init(URL policy, PolicyInfo newInfo) { return false; } + public static InputStream getInputStream(URL url) throws IOException { + if ("file".equals(url.getProtocol())) { + String path = url.getFile().replace('/', File.separatorChar); + path = ParseUtil.decode(path); + return new FileInputStream(path); + } else { + return url.openStream(); + } + } + private InputStreamReader getInputStreamReader(InputStream is) { /* * Read in policy using UTF-8 by default. @@ -295,26 +230,6 @@ private InputStreamReader getInputStreamReader(InputStream is) { private void initStaticPolicy(final PolicyInfo newInfo) { PolicyEntry pe = new PolicyEntry(new CodeSource(null, (Certificate[]) null)); pe.add(SecurityConstants.LOCAL_LISTEN_PERMISSION); - pe.add(new PropertyPermission("java.version", SecurityConstants.PROPERTY_READ_ACTION)); - pe.add(new PropertyPermission("java.vendor", SecurityConstants.PROPERTY_READ_ACTION)); - pe.add(new PropertyPermission("java.vendor.url", SecurityConstants.PROPERTY_READ_ACTION)); - pe.add(new PropertyPermission("java.class.version", SecurityConstants.PROPERTY_READ_ACTION)); - pe.add(new PropertyPermission("os.name", SecurityConstants.PROPERTY_READ_ACTION)); - pe.add(new PropertyPermission("os.version", SecurityConstants.PROPERTY_READ_ACTION)); - pe.add(new PropertyPermission("os.arch", SecurityConstants.PROPERTY_READ_ACTION)); - pe.add(new PropertyPermission("file.separator", SecurityConstants.PROPERTY_READ_ACTION)); - pe.add(new PropertyPermission("path.separator", SecurityConstants.PROPERTY_READ_ACTION)); - pe.add(new PropertyPermission("line.separator", SecurityConstants.PROPERTY_READ_ACTION)); - pe.add(new PropertyPermission("java.specification.version", SecurityConstants.PROPERTY_READ_ACTION)); - pe.add(new PropertyPermission("java.specification.maintenance.version", SecurityConstants.PROPERTY_READ_ACTION)); - pe.add(new PropertyPermission("java.specification.vendor", SecurityConstants.PROPERTY_READ_ACTION)); - pe.add(new PropertyPermission("java.specification.name", SecurityConstants.PROPERTY_READ_ACTION)); - pe.add(new PropertyPermission("java.vm.specification.version", SecurityConstants.PROPERTY_READ_ACTION)); - pe.add(new PropertyPermission("java.vm.specification.vendor", SecurityConstants.PROPERTY_READ_ACTION)); - pe.add(new PropertyPermission("java.vm.specification.name", SecurityConstants.PROPERTY_READ_ACTION)); - pe.add(new PropertyPermission("java.vm.version", SecurityConstants.PROPERTY_READ_ACTION)); - pe.add(new PropertyPermission("java.vm.vendor", SecurityConstants.PROPERTY_READ_ACTION)); - pe.add(new PropertyPermission("java.vm.name", SecurityConstants.PROPERTY_READ_ACTION)); // No need to sync because no one has access to newInfo yet newInfo.policyEntries.add(pe); @@ -325,84 +240,41 @@ private void initStaticPolicy(final PolicyInfo newInfo) { * * @return null if signedBy alias is not recognized */ - private CodeSource getCodeSource(PolicyParser.GrantEntry ge, KeyStore keyStore, PolicyInfo newInfo) - throws java.net.MalformedURLException { + private CodeSource getCodeSource(PolicyParser.GrantEntry ge) throws java.net.MalformedURLException { Certificate[] certs = null; - if (ge.signedBy != null) { - certs = getCertificates(keyStore, ge.signedBy, newInfo); - if (certs == null) { - return null; - } - } - URL location; if (ge.codeBase != null) location = newURL(ge.codeBase); else location = null; - return (canonicalizeCodebase(new CodeSource(location, certs), false)); + return (canonicalizeCodebase(new CodeSource(location, certs))); } /** * Add one policy entry to the list. */ - private void addGrantEntry(PolicyParser.GrantEntry ge, KeyStore keyStore, PolicyInfo newInfo) { + private void addGrantEntry(PolicyParser.GrantEntry ge, PolicyInfo newInfo) { try { - CodeSource codesource = getCodeSource(ge, keyStore, newInfo); + CodeSource codesource = getCodeSource(ge); // skip if signedBy alias was unknown... if (codesource == null) return; - // perform keystore alias principal replacement. - // for example, if alias resolves to X509 certificate, - // replace principal with: - // -- skip if alias is unknown - if (replacePrincipals(ge.principals, keyStore) == false) return; - PolicyEntry entry = new PolicyEntry(codesource, ge.principals); + PolicyEntry entry = new PolicyEntry(codesource); Enumeration enum_ = ge.permissionElements(); while (enum_.hasMoreElements()) { PolicyParser.PermissionEntry pe = enum_.nextElement(); try { - // perform ${{ ... }} expansions within permission name - expandPermissionName(pe, keyStore); - - // XXX special case PrivateCredentialPermission-SELF - Permission perm; - if (pe.permission.equals("javax.security.auth.PrivateCredentialPermission") && pe.name.endsWith(" self")) { - pe.name = pe.name.substring(0, pe.name.indexOf("self")) + SELF; - } - // check for self - if (pe.name != null && pe.name.contains(SELF)) { - // Create a "SelfPermission" , it could be an - // an unresolved permission which will be resolved - // when implies is called - // Add it to entry - Certificate[] certs; - if (pe.signedBy != null) { - certs = getCertificates(keyStore, pe.signedBy, newInfo); - } else { - certs = null; - } - perm = new SelfPermission(pe.permission, pe.name, pe.action, certs); - } else { - perm = getInstance(pe.permission, pe.name, pe.action); - } + Permission perm = getInstance(pe.permission, pe.name, pe.action); entry.add(perm); } catch (ClassNotFoundException cnfe) { - Certificate[] certs; - if (pe.signedBy != null) { - certs = getCertificates(keyStore, pe.signedBy, newInfo); - } else { - certs = null; - } + // maybe FIX ME. + Certificate[] certs = null; + + Permission perm = new UnresolvedPermission(pe.permission, pe.name, pe.action, certs); + entry.add(perm); - // only add if we had no signer or we had - // a signer and found the keys for it. - if (certs != null || pe.signedBy == null) { - Permission perm = new UnresolvedPermission(pe.permission, pe.name, pe.action, certs); - entry.add(perm); - } } catch (java.lang.reflect.InvocationTargetException ite) { ite.printStackTrace(System.err); } catch (Exception e) { @@ -425,25 +297,27 @@ private void addGrantEntry(PolicyParser.GrantEntry ge, KeyStore keyStore, Policy * constructor on the * object. * - * @param type the type of Permission being created. - * @param name the name of the Permission being created. + * @param type the type of Permission being created. + * @param name the name of the Permission being created. * @param actions the actions of the Permission being created. * - * @exception ClassNotFoundException if the particular Permission - * class could not be found. + * @exception ClassNotFoundException if the particular Permission + * class could not be found. * - * @exception IllegalAccessException if the class or initializer is - * not accessible. + * @exception IllegalAccessException if the class or initializer is + * not accessible. * - * @exception InstantiationException if getInstance tries to - * instantiate an abstract class or an interface, or if the - * instantiation fails for some other reason. + * @exception InstantiationException if getInstance tries to + * instantiate an abstract class or an + * interface, or if the + * instantiation fails for some other + * reason. * - * @exception NoSuchMethodException if the (String, String) constructor - * is not found. + * @exception NoSuchMethodException if the (String, String) constructor + * is not found. * - * @exception InvocationTargetException if the underlying Permission - * constructor throws an exception. + * @exception InvocationTargetException if the underlying Permission + * constructor throws an exception. * */ @@ -498,78 +372,8 @@ private static Permission getKnownPermission(Class claz, String name, String return new FilePermission(name, actions); } else if (claz.equals(SocketPermission.class)) { return new SocketPermission(name, actions); - } else if (claz.equals(RuntimePermission.class)) { - return new RuntimePermission(name, actions); - } else if (claz.equals(PropertyPermission.class)) { - return new PropertyPermission(name, actions); } else if (claz.equals(NetPermission.class)) { return new NetPermission(name, actions); - } else if (claz.equals(AllPermission.class)) { - return SecurityConstants.ALL_PERMISSION; - } else if (claz.equals(SecurityPermission.class)) { - return new SecurityPermission(name, actions); - } else { - return null; - } - } - - /** - * Creates one of the well-known principals in the java.base module - * directly instead of via reflection. Keep list short to not penalize - * principals from other modules. - */ - private static Principal getKnownPrincipal(Class claz, String name) { - if (claz.equals(X500Principal.class)) { - return new X500Principal(name); - } else { - return null; - } - } - - /** - * Fetch all certs associated with this alias. - */ - private Certificate[] getCertificates(KeyStore keyStore, String aliases, PolicyInfo newInfo) { - - List vcerts = null; - - StringTokenizer st = new StringTokenizer(aliases, ","); - int n = 0; - - while (st.hasMoreTokens()) { - String alias = st.nextToken().trim(); - n++; - Certificate cert = null; - // See if this alias's cert has already been cached - synchronized (newInfo.aliasMapping) { - cert = (Certificate) newInfo.aliasMapping.get(alias); - - if (cert == null && keyStore != null) { - - try { - cert = keyStore.getCertificate(alias); - } catch (KeyStoreException kse) { - // never happens, because keystore has already been loaded - // when we call this - } - if (cert != null) { - newInfo.aliasMapping.put(alias, cert); - newInfo.aliasMapping.put(cert, alias); - } - } - } - - if (cert != null) { - if (vcerts == null) vcerts = new ArrayList<>(); - vcerts.add(cert); - } - } - - // make sure n == vcerts.size, since we are doing a logical *and* - if (vcerts != null && n == vcerts.size()) { - Certificate[] certs = new Certificate[vcerts.size()]; - vcerts.toArray(certs); - return certs; } else { return null; } @@ -589,10 +393,10 @@ public void refresh() { * granted. * * @param pd the ProtectionDomain to test - * @param p the Permission object to be tested for implication. + * @param p the Permission object to be tested for implication. * * @return true if "permission" is a proper subset of a permission - * granted to this ProtectionDomain. + * granted to this ProtectionDomain. * * @see java.security.ProtectionDomain */ @@ -609,29 +413,16 @@ public boolean implies(ProtectionDomain pd, Permission p) { /** * Examines this Policy and returns the permissions granted - * to the specified ProtectionDomain. This includes + * to the specified ProtectionDomain. This includes * the permissions currently associated with the domain as well * as the policy permissions granted to the domain's - * CodeSource, ClassLoader, and Principals. - * - *

Note that this Policy implementation has - * special handling for PrivateCredentialPermissions. - * When this method encounters a PrivateCredentialPermission - * which specifies "self" as the Principal class and name, - * it does not add that Permission to the returned - * PermissionCollection. Instead, it builds - * a new PrivateCredentialPermission - * for each Principal associated with the provided - * Subject. Each new PrivateCredentialPermission - * contains the same Credential class as specified in the - * originally granted permission, as well as the Class and name - * for the respective Principal. + * CodeSource and ClassLoader. * * @param domain the Permissions granted to this - * ProtectionDomain are returned. + * ProtectionDomain are returned. * * @return the Permissions granted to the provided - * ProtectionDomain. + * ProtectionDomain. */ @Override public PermissionCollection getPermissions(ProtectionDomain domain) { @@ -663,8 +454,9 @@ public PermissionCollection getPermissions(ProtectionDomain domain) { * the set of permissions for the specified CodeSource. * * @param codesource the CodeSource associated with the caller. - * This encapsulates the original location of the code (where the code - * came from) and the public key(s) of its signer. + * This encapsulates the original location of the code (where + * the code + * came from) and the public key(s) of its signer. * * @return the set of permissions according to the policy. */ @@ -679,7 +471,7 @@ public PermissionCollection getPermissions(CodeSource codesource) { * ProtectionDomain. * * @param perms the Permissions to populate - * @param pd the ProtectionDomain associated with the caller. + * @param pd the ProtectionDomain associated with the caller. * * @return the set of Permissions according to the policy. */ @@ -687,8 +479,8 @@ private PermissionCollection getPermissions(Permissions perms, ProtectionDomain final CodeSource cs = pd.getCodeSource(); if (cs == null) return perms; - CodeSource canonCodeSource = canonicalizeCodebase(cs, true); - return getPermissions(perms, canonCodeSource, pd.getPrincipals()); + CodeSource canonCodeSource = canonicalizeCodebase(cs); + return getPermissions(perms, canonCodeSource); } /** @@ -697,9 +489,10 @@ private PermissionCollection getPermissions(Permissions perms, ProtectionDomain * CodeSource. * * @param perms the permissions to populate - * @param cs the codesource associated with the caller. - * This encapsulates the original location of the code (where the code - * came from) and the public key(s) of its signer. + * @param cs the codesource associated with the caller. + * This encapsulates the original location of the code (where the + * code + * came from) and the public key(s) of its signer. * * @return the set of permissions according to the policy. */ @@ -707,19 +500,16 @@ private PermissionCollection getPermissions(Permissions perms, final CodeSource if (cs == null) return perms; - CodeSource canonCodeSource = canonicalizeCodebase(cs, true); - return getPermissions(perms, canonCodeSource, null); - } + CodeSource canonCodeSource = canonicalizeCodebase(cs); - private Permissions getPermissions(Permissions perms, final CodeSource cs, Principal[] principals) { for (PolicyEntry entry : policyInfo.policyEntries) { - addPermissions(perms, cs, principals, entry); + addPermissions(perms, cs, entry); } return perms; } - private void addPermissions(Permissions perms, final CodeSource cs, Principal[] principals, final PolicyEntry entry) { + private void addPermissions(Permissions perms, final CodeSource cs, final PolicyEntry entry) { // check to see if the CodeSource implies Boolean imp = entry.getCodeSource().implies(cs); @@ -728,320 +518,17 @@ private void addPermissions(Permissions perms, final CodeSource cs, Principal[] return; } - // check to see if the Principals imply - - List entryPs = entry.getPrincipals(); - - if (entryPs == null || entryPs.isEmpty()) { - - // policy entry has no principals - - // add perms regardless of principals in current ACC - - addPerms(perms, principals, entry); - return; - - } else if (principals == null || principals.length == 0) { - - // current thread has no principals but this policy entry - // has principals - perms are not added - - return; - } - - // current thread has principals and this policy entry - // has principals. see if policy entry principals match - // principals in current ACC - - for (PolicyParser.PrincipalEntry pppe : entryPs) { - - // Check for wildcards - if (pppe.isWildcardClass()) { - // a wildcard class matches all principals in current ACC - continue; - } - - if (pppe.isWildcardName()) { - // a wildcard name matches any principal with the same class - if (wildcardPrincipalNameImplies(pppe.principalClass, principals)) { - continue; - } - // policy entry principal not in current ACC - - // immediately return and go to next policy entry - return; - } - - Set pSet = new HashSet<>(Arrays.asList(principals)); - Subject subject = new Subject(true, pSet, Collections.EMPTY_SET, Collections.EMPTY_SET); - try { - ClassLoader cl = Thread.currentThread().getContextClassLoader(); - Class pClass = Class.forName(pppe.principalClass, false, cl); - Principal p = getKnownPrincipal(pClass, pppe.principalName); - if (p == null) { - if (!Principal.class.isAssignableFrom(pClass)) { - // not the right subtype - throw new ClassCastException(pppe.principalClass + " is not a Principal"); - } - - Constructor c = pClass.getConstructor(PARAMS1); - p = (Principal) c.newInstance(new Object[] { pppe.principalName }); - - } - - // check if the Principal implies the current - // thread's principals - if (!p.implies(subject)) { - // policy principal does not imply the current Subject - - // immediately return and go to next policy entry - return; - } - } catch (Exception e) { - // fall back to default principal comparison. - // see if policy entry principal is in current ACC - - if (!pppe.implies(subject)) { - // policy entry principal not in current ACC - - // immediately return and go to next policy entry - return; - } - } - - // either the principal information matched, - // or the Principal.implies succeeded. - // continue loop and test the next policy principal - } - - // all policy entry principals were found in the current ACC - - // grant the policy permissions - - addPerms(perms, principals, entry); + addPerms(perms, entry); } - /** - * Returns true if the array of principals contains at least one - * principal of the specified class. - */ - private static boolean wildcardPrincipalNameImplies(String principalClass, Principal[] principals) { - for (Principal p : principals) { - if (principalClass.equals(p.getClass().getName())) { - return true; - } - } - return false; - } - - private void addPerms(Permissions perms, Principal[] accPs, PolicyEntry entry) { + private void addPerms(Permissions perms, PolicyEntry entry) { for (int i = 0; i < entry.permissions.size(); i++) { Permission p = entry.permissions.get(i); - - if (p instanceof SelfPermission) { - // handle "SELF" permissions - expandSelf((SelfPermission) p, entry.getPrincipals(), accPs, perms); - } else { - perms.add(p); - } - } - } - - /** - * @param sp the SelfPermission that needs to be expanded. - * - * @param entryPs list of principals for the Policy entry. - * - * @param pdp Principal array from the current ProtectionDomain. - * - * @param perms the PermissionCollection where the individual - * Permissions will be added after expansion. - */ - - private void expandSelf(SelfPermission sp, List entryPs, Principal[] pdp, Permissions perms) { - - if (entryPs == null || entryPs.isEmpty()) { - return; - } - int startIndex = 0; - int v; - StringBuilder sb = new StringBuilder(); - while ((v = sp.getSelfName().indexOf(SELF, startIndex)) != -1) { - - // add non-SELF string - sb.append(sp.getSelfName().substring(startIndex, v)); - - // expand SELF - Iterator pli = entryPs.iterator(); - while (pli.hasNext()) { - PolicyParser.PrincipalEntry pppe = pli.next(); - String[][] principalInfo = getPrincipalInfo(pppe, pdp); - for (int i = 0; i < principalInfo.length; i++) { - if (i != 0) { - sb.append(", "); - } - sb.append(principalInfo[i][0] + " " + "\"" + principalInfo[i][1] + "\""); - } - if (pli.hasNext()) { - sb.append(", "); - } - } - startIndex = v + SELF.length(); - } - // add remaining string (might be the entire string) - sb.append(sp.getSelfName().substring(startIndex)); - - try { - // first try to instantiate the permission - perms.add(getInstance(sp.getSelfType(), sb.toString(), sp.getSelfActions())); - } catch (ClassNotFoundException cnfe) { - // ok, the permission is not in the bootclasspath. - // before we add an UnresolvedPermission, check to see - // whether this perm already belongs to the collection. - // if so, use that perm's ClassLoader to create a new - // one. - Class pc = null; - synchronized (perms) { - Enumeration e = perms.elements(); - while (e.hasMoreElements()) { - Permission pElement = e.nextElement(); - if (pElement.getClass().getName().equals(sp.getSelfType())) { - pc = pElement.getClass(); - break; - } - } - } - if (pc == null) { - // create an UnresolvedPermission - perms.add(new UnresolvedPermission(sp.getSelfType(), sb.toString(), sp.getSelfActions(), sp.getCerts())); - } else { - try { - // we found an instantiated permission. - // use its class loader to instantiate a new permission. - Constructor c; - // name parameter can not be null - if (sp.getSelfActions() == null) { - try { - c = pc.getConstructor(PARAMS1); - perms.add((Permission) c.newInstance(new Object[] { sb.toString() })); - } catch (NoSuchMethodException ne) { - c = pc.getConstructor(PARAMS2); - perms.add((Permission) c.newInstance(new Object[] { sb.toString(), sp.getSelfActions() })); - } - } else { - c = pc.getConstructor(PARAMS2); - perms.add((Permission) c.newInstance(new Object[] { sb.toString(), sp.getSelfActions() })); - } - } catch (Exception nme) {} - } - } catch (Exception e) {} - } - - /** - * return the principal class/name pair in the 2D array. - * array[x][y]: x corresponds to the array length. - * if (y == 0), it's the principal class. - * if (y == 1), it's the principal name. - */ - private String[][] getPrincipalInfo(PolicyParser.PrincipalEntry pe, Principal[] pdp) { - - // there are 3 possibilities: - // 1) the entry's Principal class and name are not wildcarded - // 2) the entry's Principal name is wildcarded only - // 3) the entry's Principal class and name are wildcarded - - if (!pe.isWildcardClass() && !pe.isWildcardName()) { - - // build an info array for the principal - // from the Policy entry - String[][] info = new String[1][2]; - info[0][0] = pe.principalClass; - info[0][1] = pe.principalName; - return info; - - } else if (!pe.isWildcardClass() && pe.isWildcardName()) { - - // build an info array for every principal - // in the current domain which has a principal class - // that is equal to policy entry principal class name - List plist = new ArrayList<>(); - for (int i = 0; i < pdp.length; i++) { - if (pe.principalClass.equals(pdp[i].getClass().getName())) plist.add(pdp[i]); - } - String[][] info = new String[plist.size()][2]; - int i = 0; - for (Principal p : plist) { - info[i][0] = p.getClass().getName(); - info[i][1] = p.getName(); - i++; - } - return info; - - } else { - - // build an info array for every - // one of the current Domain's principals - - String[][] info = new String[pdp.length][2]; - - for (int i = 0; i < pdp.length; i++) { - info[i][0] = pdp[i].getClass().getName(); - info[i][1] = pdp[i].getName(); - } - return info; - } - } - - /* - * Returns the signer certificates from the list of certificates - * associated with the given code source. - * - * The signer certificates are those certificates that were used - * to verify signed code originating from the codesource location. - * - * This method assumes that in the given code source, each signer - * certificate is followed by its supporting certificate chain - * (which may be empty), and that the signer certificate and its - * supporting certificate chain are ordered bottom-to-top - * (i.e., with the signer certificate first and the (root) certificate - * authority last). - */ - protected Certificate[] getSignerCertificates(CodeSource cs) { - Certificate[] certs = null; - if ((certs = cs.getCertificates()) == null) return null; - for (int i = 0; i < certs.length; i++) { - if (!(certs[i] instanceof X509Certificate)) return cs.getCertificates(); - } - - // Do we have to do anything? - int i = 0; - int count = 0; - while (i < certs.length) { - count++; - while (((i + 1) < certs.length) - && ((X509Certificate) certs[i]).getIssuerX500Principal() - .equals(((X509Certificate) certs[i + 1]).getSubjectX500Principal())) { - i++; - } - i++; - } - if (count == certs.length) - // Done - return certs; - - List userCertList = new ArrayList<>(); - i = 0; - while (i < certs.length) { - userCertList.add(certs[i]); - while (((i + 1) < certs.length) - && ((X509Certificate) certs[i]).getIssuerX500Principal() - .equals(((X509Certificate) certs[i + 1]).getSubjectX500Principal())) { - i++; - } - i++; + perms.add(p); } - Certificate[] userCerts = new Certificate[userCertList.size()]; - userCertList.toArray(userCerts); - return userCerts; } - private CodeSource canonicalizeCodebase(CodeSource cs, boolean extractSignerCerts) { - + private CodeSource canonicalizeCodebase(CodeSource cs) { String path = null; CodeSource canonCs = cs; @@ -1077,24 +564,13 @@ private CodeSource canonicalizeCodebase(CodeSource cs, boolean extractSignerCert URL csUrl = null; path = canonPath(path); csUrl = ParseUtil.fileToEncodedURL(new File(path)); - - if (extractSignerCerts) { - canonCs = new CodeSource(csUrl, getSignerCertificates(cs)); - } else { - canonCs = new CodeSource(csUrl, cs.getCertificates()); - } + canonCs = new CodeSource(csUrl, cs.getCertificates()); } catch (IOException ioe) { - // leave codesource as it is, unless we have to extract its - // signer certificates - if (extractSignerCerts) { - canonCs = new CodeSource(cs.getLocation(), getSignerCertificates(cs)); - } - } - } else { - if (extractSignerCerts) { - canonCs = new CodeSource(cs.getLocation(), getSignerCertificates(cs)); + // leave codesource as it is... + // FIX ME: log an exception? } } + return canonCs; } @@ -1110,143 +586,37 @@ private static String canonPath(String path) throws IOException { } } - /** - * return true if no replacement was performed, - * or if replacement succeeded. - */ - private boolean replacePrincipals(List principals, KeyStore keystore) { - - if (principals == null || principals.isEmpty() || keystore == null) return true; - - for (PolicyParser.PrincipalEntry pppe : principals) { - if (pppe.isReplaceName()) { - - // perform replacement - // (only X509 replacement is possible now) - String name; - if ((name = getDN(pppe.principalName, keystore)) == null) { - return false; - } - - pppe.principalClass = X500PRINCIPAL; - pppe.principalName = name; - } - } - // return true if no replacement was performed, - // or if replacement succeeded - return true; - } - - private void expandPermissionName(PolicyParser.PermissionEntry pe, KeyStore keystore) throws Exception { - // short cut the common case - if (pe.name == null || pe.name.indexOf("${{", 0) == -1) { - return; - } - - int startIndex = 0; - int b, e; - StringBuilder sb = new StringBuilder(); - while ((b = pe.name.indexOf("${{", startIndex)) != -1) { - e = pe.name.indexOf("}}", b); - if (e < 1) { - break; - } - sb.append(pe.name.substring(startIndex, b)); - - // get the value in ${{...}} - String value = pe.name.substring(b + 3, e); - - // parse up to the first ':' - int colonIndex; - String prefix = value; - String suffix; - if ((colonIndex = value.indexOf(':')) != -1) { - prefix = value.substring(0, colonIndex); - } - - // handle different prefix possibilities - if (prefix.equalsIgnoreCase("self")) { - // do nothing - handled later - sb.append(pe.name.substring(b, e + 2)); - startIndex = e + 2; - continue; - } else if (prefix.equalsIgnoreCase("alias")) { - // get the suffix and perform keystore alias replacement - if (colonIndex == -1) { - throw new Exception("Alias name not provided pe.name: " + pe.name); - } - suffix = value.substring(colonIndex + 1); - if ((suffix = getDN(suffix, keystore)) == null) { - throw new Exception("Unable to perform substitution on alias suffix: " + value.substring(colonIndex + 1)); - } - - sb.append(X500PRINCIPAL + " \"" + suffix + "\""); - startIndex = e + 2; - } else { - throw new Exception("Substitution value prefix unsupported: " + prefix); - } - } - - // copy the rest of the value - sb.append(pe.name.substring(startIndex)); - - pe.name = sb.toString(); - } - - private String getDN(String alias, KeyStore keystore) { - Certificate cert = null; - try { - cert = keystore.getCertificate(alias); - } catch (Exception e) { - return null; - } - - if (!(cert instanceof X509Certificate x509Cert)) { - return null; - } else { - // 4702543: X500 names with an EmailAddress - // were encoded incorrectly. create new - // X500Principal name with correct encoding - - X500Principal p = new X500Principal(x509Cert.getSubjectX500Principal().toString()); - return p.getName(); - } - } - /** * Each entry in the policy configuration file is represented by a - * PolicyEntry object.

+ * PolicyEntry object. + *

* - * A PolicyEntry is a (CodeSource,Permission) pair. The + * A PolicyEntry is a (CodeSource,Permission) pair. The * CodeSource contains the (URL, PublicKey) that together identify * where the Java bytecodes come from and who (if anyone) signed - * them. The URL could refer to localhost. The URL could also be + * them. The URL could refer to localhost. The URL could also be * null, meaning that this policy entry is given to all comers, as - * long as they match the signer field. The signer could be null, - * meaning the code is not signed.

- * - * The Permission contains the (Type, Name, Action) triplet.

+ * long as they match the signer field. The signer could be null, + * meaning the code is not signed. + *

* - * For now, the Policy object retrieves the public key from the - * X.509 certificate on disk that corresponds to the signedBy - * alias specified in the Policy config file. For reasons of - * efficiency, the Policy object keeps a hashtable of certs already - * read in. This could be replaced by a secure internal key - * store. + * The Permission contains the (Type, Name, Action) triplet. + *

* *

* For example, the entry + * *

      *          permission java.io.File "/tmp", "read,write",
-     *          signedBy "Duke";
      * 
+ * * is represented internally + * *
      *
      * FilePermission f = new FilePermission("/tmp", "read,write");
-     * PublicKey p = publickeys.get("Duke");
      * URL u = InetAddress.getLocalHost();
-     * CodeBase c = new CodeBase( p, u );
+     * CodeBase c = new CodeBase(u);
      * pe = new PolicyEntry(f, c);
      * 
* @@ -1261,7 +631,6 @@ private static class PolicyEntry { private final CodeSource codesource; final List permissions; - private final List principals; /** * Given a Permission and a CodeSource, create a policy entry. @@ -1270,24 +639,15 @@ private static class PolicyEntry { * XXX policy entries * * @param cs the CodeSource, which encapsulates the URL and the - * public key - * attributes from the policy config file. Validity checks - * are performed on the public key before PolicyEntry is - * called. + * public key + * attributes from the policy config file. Validity checks + * are performed on the public key before PolicyEntry is + * called. * */ - PolicyEntry(CodeSource cs, List principals) { - this.codesource = cs; - this.permissions = new ArrayList(); - this.principals = principals; // can be null - } - PolicyEntry(CodeSource cs) { - this(cs, null); - } - - List getPrincipals() { - return principals; // can be null + this.codesource = cs; + this.permissions = new ArrayList();// can be null } /** @@ -1325,259 +685,6 @@ public String toString() { } } - private static class SelfPermission extends Permission { - - @java.io.Serial - private static final long serialVersionUID = -8315562579967246806L; - - /** - * The class name of the Permission class that will be - * created when this self permission is expanded . - * - * @serial - */ - private String type; - - /** - * The permission name. - * - * @serial - */ - private String name; - - /** - * The actions of the permission. - * - * @serial - */ - private String actions; - - /** - * The certs of the permission. - * - * @serial - */ - private Certificate[] certs; - - /** - * Creates a new SelfPermission containing the permission - * information needed later to expand the self - * @param type the class name of the Permission class that will be - * created when this permission is expanded and if necessary resolved. - * @param name the name of the permission. - * @param actions the actions of the permission. - * @param certs the certificates the permission's class was signed with. - * This is a list of certificate chains, where each chain is composed of - * a signer certificate and optionally its supporting certificate chain. - * Each chain is ordered bottom-to-top (i.e., with the signer - * certificate first and the (root) certificate authority last). - */ - public SelfPermission(String type, String name, String actions, Certificate[] certs) { - super(type); - if (type == null) { - throw new NullPointerException("Ttype cannot be null"); - } - this.type = type; - this.name = name; - this.actions = actions; - if (certs != null) { - // Extract the signer certs from the list of certificates. - for (int i = 0; i < certs.length; i++) { - if (!(certs[i] instanceof X509Certificate)) { - // there is no concept of signer certs, so we store the - // entire cert array - this.certs = certs.clone(); - break; - } - } - - if (this.certs == null) { - // Go through the list of certs and see if all the certs are - // signer certs. - int i = 0; - int count = 0; - while (i < certs.length) { - count++; - while (((i + 1) < certs.length) - && ((X509Certificate) certs[i]).getIssuerX500Principal() - .equals(((X509Certificate) certs[i + 1]).getSubjectX500Principal())) { - i++; - } - i++; - } - if (count == certs.length) { - // All the certs are signer certs, so we store the - // entire array - this.certs = certs.clone(); - } - - if (this.certs == null) { - // extract the signer certs - List signerCerts = new ArrayList<>(); - i = 0; - while (i < certs.length) { - signerCerts.add(certs[i]); - while (((i + 1) < certs.length) - && ((X509Certificate) certs[i]).getIssuerX500Principal() - .equals(((X509Certificate) certs[i + 1]).getSubjectX500Principal())) { - i++; - } - i++; - } - this.certs = new Certificate[signerCerts.size()]; - signerCerts.toArray(this.certs); - } - } - } - } - - /** - * This method always returns false for SelfPermission permissions. - * That is, an SelfPermission never considered to - * imply another permission. - * - * @param p the permission to check against. - * - * @return false. - */ - @Override - public boolean implies(Permission p) { - return false; - } - - /** - * Checks two SelfPermission objects for equality. - * - * Checks that obj is an SelfPermission, and has - * the same type (class) name, permission name, actions, and - * certificates as this object. - * - * @param obj the object we are testing for equality with this object. - * - * @return true if obj is an SelfPermission, and has the same - * type (class) name, permission name, actions, and - * certificates as this object. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - - if (!(obj instanceof SelfPermission)) return false; - SelfPermission that = (SelfPermission) obj; - - if (!(this.type.equals(that.type) && this.name.equals(that.name) && this.actions.equals(that.actions))) return false; - - if ((this.certs == null) && (that.certs == null)) { - return true; - } - - if ((this.certs == null) || (that.certs == null)) { - return false; - } - - if (this.certs.length != that.certs.length) { - return false; - } - - int i, j; - boolean match; - - for (i = 0; i < this.certs.length; i++) { - match = false; - for (j = 0; j < that.certs.length; j++) { - if (this.certs[i].equals(that.certs[j])) { - match = true; - break; - } - } - if (!match) return false; - } - - for (i = 0; i < that.certs.length; i++) { - match = false; - for (j = 0; j < this.certs.length; j++) { - if (that.certs[i].equals(this.certs[j])) { - match = true; - break; - } - } - if (!match) return false; - } - return true; - } - - /** - * Returns the hash code value for this object. - * - * @return a hash code value for this object. - */ - @Override - public int hashCode() { - int hash = type.hashCode(); - if (name != null) hash ^= name.hashCode(); - if (actions != null) hash ^= actions.hashCode(); - return hash; - } - - /** - * Returns the canonical string representation of the actions, - * which currently is the empty string "", since there are no actions - * for an SelfPermission. That is, the actions for the - * permission that will be created when this SelfPermission - * is resolved may be non-null, but an SelfPermission - * itself is never considered to have any actions. - * - * @return the empty string "". - */ - @Override - public String getActions() { - return ""; - } - - public String getSelfType() { - return type; - } - - public String getSelfName() { - return name; - } - - public String getSelfActions() { - return actions; - } - - public Certificate[] getCerts() { - return (certs == null ? null : certs.clone()); - } - - /** - * Returns a string describing this SelfPermission. The convention - * is to specify the class name, the permission name, and the actions, - * in the following format: '(unresolved "ClassName" "name" "actions")'. - * - * @return information about this SelfPermission. - */ - @Override - public String toString() { - return "(SelfPermission " + type + " " + name + " " + actions + ")"; - } - - /** - * Restores the state of this object from the stream. - * - * @param stream the {@code ObjectInputStream} from which data is read - * @throws IOException if an I/O error occurs - * @throws ClassNotFoundException if a serialized class cannot be loaded - */ - @java.io.Serial - private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { - stream.defaultReadObject(); - if (certs != null) { - this.certs = certs.clone(); - } - } - } - /** * holds policy information that we need to synch on */ @@ -1585,12 +692,8 @@ private static class PolicyInfo { // Stores grant entries in the policy final List policyEntries; - // Maps aliases to certs - final Map aliasMapping; - PolicyInfo(int numCaches) { policyEntries = new ArrayList<>(); - aliasMapping = Collections.synchronizedMap(new HashMap<>(11)); } } diff --git a/libs/agent-sm/agent-policy/src/main/java/org/opensearch/secure_sm/policy/PolicyParser.java b/libs/agent-sm/agent-policy/src/main/java/org/opensearch/secure_sm/policy/PolicyParser.java index 9d5b0d5a13722..47d0c1c9c4df3 100644 --- a/libs/agent-sm/agent-policy/src/main/java/org/opensearch/secure_sm/policy/PolicyParser.java +++ b/libs/agent-sm/agent-policy/src/main/java/org/opensearch/secure_sm/policy/PolicyParser.java @@ -33,8 +33,6 @@ package org.opensearch.secure_sm.policy; -import javax.security.auth.x500.X500Principal; - import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; @@ -44,16 +42,7 @@ import java.io.StreamTokenizer; import java.io.Writer; import java.security.GeneralSecurityException; -import java.security.Principal; -import java.util.Collection; import java.util.Enumeration; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.Locale; -import java.util.Map; -import java.util.StringTokenizer; -import java.util.TreeMap; import java.util.Vector; /** @@ -62,26 +51,16 @@ public class PolicyParser { private final Vector grantEntries; - private Map domainEntries; private StreamTokenizer st; private int lookahead; - private boolean expandProp = false; - private String keyStoreUrlString = null; // unexpanded - private String keyStoreType = null; - private String keyStoreProvider = null; - private String storePassURL = null; private String expand(String value) throws PropertyExpander.ExpandException { return expand(value, false); } private String expand(String value, boolean encodeURL) throws PropertyExpander.ExpandException { - if (!expandProp) { - return value; - } else { - return PropertyExpander.expand(value, encodeURL); - } + return PropertyExpander.expand(value, encodeURL); } /** @@ -92,11 +71,6 @@ public PolicyParser() { grantEntries = new Vector<>(); } - public PolicyParser(boolean expandProp) { - this(); - this.expandProp = expandProp; - } - /** * Reads a policy configuration into the Policy object using a * Reader object. @@ -157,34 +131,11 @@ public void read(Reader policy) throws ParsingException, IOException { ge = parseGrantEntry(); // could be null if we couldn't expand a property if (ge != null) add(ge); - } else if (peek("keystore") && keyStoreUrlString == null) { - // only one keystore entry per policy file, others will be - // ignored - parseKeyStoreEntry(); - } else if (peek("keystorePasswordURL") && storePassURL == null) { - // only one keystore passwordURL per policy file, others will be - // ignored - parseStorePassURL(); - } else if (ge == null && keyStoreUrlString == null && storePassURL == null && peek("domain")) { - if (domainEntries == null) { - domainEntries = new TreeMap<>(); - } - DomainEntry de = parseDomainEntry(); - String domainName = de.getName(); - if (domainEntries.putIfAbsent(domainName, de) != null) { - Object[] source = { domainName }; - String msg = "duplicate keystore domain name: " + domainName; - throw new ParsingException(msg, source); - } } else { // error? } match(";"); } - - if (keyStoreUrlString == null && storePassURL != null) { - throw new ParsingException("Keystore Password URL cannot be specified without also specifying keystore"); - } } public void add(GrantEntry ge) { @@ -199,56 +150,6 @@ public boolean remove(GrantEntry ge) { return grantEntries.removeElement(ge); } - /** - * Returns the (possibly expanded) keystore location, or null if the - * expansion fails. - */ - public String getKeyStoreUrl() { - try { - if (keyStoreUrlString != null && keyStoreUrlString.length() != 0) { - return expand(keyStoreUrlString, true).replace(File.separatorChar, '/'); - } - } catch (PropertyExpander.ExpandException peee) { - return null; - } - return null; - } - - public void setKeyStoreUrl(String url) { - keyStoreUrlString = url; - } - - public String getKeyStoreType() { - return keyStoreType; - } - - public void setKeyStoreType(String type) { - keyStoreType = type; - } - - public String getKeyStoreProvider() { - return keyStoreProvider; - } - - public void setKeyStoreProvider(String provider) { - keyStoreProvider = provider; - } - - public String getStorePassURL() { - try { - if (storePassURL != null && storePassURL.length() != 0) { - return expand(storePassURL, true).replace(File.separatorChar, '/'); - } - } catch (PropertyExpander.ExpandException peee) { - return null; - } - return null; - } - - public void setStorePassURL(String storePassURL) { - this.storePassURL = storePassURL; - } - /** * Enumerate all the entries in the global policy object. * This method is used by policy admin tools. The tools @@ -259,10 +160,6 @@ public Enumeration grantElements() { return grantEntries.elements(); } - public Collection getDomainEntries() { - return domainEntries.values(); - } - /** * write out the policy */ @@ -274,15 +171,6 @@ public void write(Writer policy) { out.println("/* DO NOT EDIT */"); out.println(); - // write the (unexpanded) keystore entry as the first entry of the - // policy file - if (keyStoreUrlString != null) { - writeKeyStoreEntry(out); - } - if (storePassURL != null) { - writeStorePassURL(out); - } - // write "grant" entries for (GrantEntry ge : grantEntries) { ge.write(out); @@ -291,70 +179,11 @@ public void write(Writer policy) { out.flush(); } - /** - * parses a keystore entry - */ - private void parseKeyStoreEntry() throws ParsingException, IOException { - match("keystore"); - keyStoreUrlString = match("quoted string"); - - // parse keystore type - if (!peek(",")) { - return; // default type - } - match(","); - - if (peek("\"")) { - keyStoreType = match("quoted string"); - } else { - throw new ParsingException(st.lineno(), "Expected keystore type"); - } - - // parse keystore provider - if (!peek(",")) { - return; // provider optional - } - match(","); - - if (peek("\"")) { - keyStoreProvider = match("quoted string"); - } else { - throw new ParsingException(st.lineno(), "Keystore provider expected"); - } - } - - private void parseStorePassURL() throws ParsingException, IOException { - match("keyStorePasswordURL"); - storePassURL = match("quoted string"); - } - - /** - * writes the (unexpanded) keystore entry - */ - private void writeKeyStoreEntry(PrintWriter out) { - out.print("keystore \""); - out.print(keyStoreUrlString); - out.print('"'); - if (keyStoreType != null && !keyStoreType.isEmpty()) out.print(", \"" + keyStoreType + "\""); - if (keyStoreProvider != null && !keyStoreProvider.isEmpty()) out.print(", \"" + keyStoreProvider + "\""); - out.println(";"); - out.println(); - } - - private void writeStorePassURL(PrintWriter out) { - out.print("keystorePasswordURL \""); - out.print(storePassURL); - out.print('"'); - out.println(";"); - out.println(); - } - /** * parse a Grant entry */ private GrantEntry parseGrantEntry() throws ParsingException, IOException { GrantEntry e = new GrantEntry(); - LinkedList principals = null; boolean ignoreEntry = false; match("grant"); @@ -365,84 +194,11 @@ private GrantEntry parseGrantEntry() throws ParsingException, IOException { if (e.codeBase != null) throw new ParsingException(st.lineno(), "Multiple Codebase expressions"); e.codeBase = match("quoted string"); peekAndMatch(","); - } else if (peekAndMatch("SignedBy")) { - if (e.signedBy != null) throw new ParsingException(st.lineno(), "Multiple SignedBy expressions"); - e.signedBy = match("quoted string"); - - // verify syntax of the aliases - StringTokenizer aliases = new StringTokenizer(e.signedBy, ",", true); - int actr = 0; - int cctr = 0; - while (aliases.hasMoreTokens()) { - String alias = aliases.nextToken().trim(); - if (alias.equals(",")) cctr++; - else if (!alias.isEmpty()) actr++; - } - if (actr <= cctr) throw new ParsingException(st.lineno(), "SignedBy has an empty alias"); - - peekAndMatch(","); - } else if (peekAndMatch("Principal")) { - if (principals == null) { - principals = new LinkedList<>(); - } - - String principalClass; - String principalName; - - if (peek("\"")) { - // both the principalClass and principalName - // will be replaced later - principalClass = PrincipalEntry.REPLACE_NAME; - principalName = match("principal type"); - } else { - // check for principalClass wildcard - if (peek("*")) { - match("*"); - principalClass = PrincipalEntry.WILDCARD_CLASS; - } else { - principalClass = match("principal type"); - } - - // check for principalName wildcard - if (peek("*")) { - match("*"); - principalName = PrincipalEntry.WILDCARD_NAME; - } else { - principalName = match("quoted string"); - } - - // disallow WILDCARD_CLASS && actual name - if (principalClass.equals(PrincipalEntry.WILDCARD_CLASS) && !principalName.equals(PrincipalEntry.WILDCARD_NAME)) { - throw new ParsingException(st.lineno(), "Cannot specify Principal with a wildcard class without a wildcard name"); - } - } - - try { - principalName = expand(principalName); - - if (principalClass.equals("javax.security.auth.x500.X500Principal") - && !principalName.equals(PrincipalEntry.WILDCARD_NAME)) { - - // 4702543: X500 names with an EmailAddress - // were encoded incorrectly. construct a new - // X500Principal with correct encoding. - - X500Principal p = new X500Principal((new X500Principal(principalName)).toString()); - principalName = p.getName(); - } - - principals.add(new PrincipalEntry(principalClass, principalName)); - } catch (PropertyExpander.ExpandException peee) { - ignoreEntry = true; - } - peekAndMatch(","); - } else { - throw new ParsingException(st.lineno(), "Expected codeBase or SignedBy or Principal"); + throw new ParsingException(st.lineno(), "Expected codeBase"); } } - if (principals != null) e.principals = principals; match("{"); while (!peek("}")) { @@ -461,7 +217,6 @@ private GrantEntry parseGrantEntry() throws ParsingException, IOException { match("}"); try { - if (e.signedBy != null) e.signedBy = expand(e.signedBy); if (e.codeBase != null) { e.codeBase = expand(e.codeBase, true).replace(File.separatorChar, '/'); } @@ -499,70 +254,9 @@ private PermissionEntry parsePermissionEntry() throws ParsingException, IOExcept } match(","); } - - if (peekAndMatch("SignedBy")) { - e.signedBy = expand(match("quoted string")); - } return e; } - /** - * parse a domain entry - */ - private DomainEntry parseDomainEntry() throws ParsingException, IOException { - DomainEntry domainEntry; - String name; - Map properties = new HashMap<>(); - - match("domain"); - name = match("domain name"); - - while (!peek("{")) { - // get the domain properties - properties = parseProperties("{"); - } - match("{"); - domainEntry = new DomainEntry(name, properties); - - while (!peek("}")) { - - match("keystore"); - name = match("keystore name"); - // get the keystore properties - if (!peek("}")) { - properties = parseProperties(";"); - } - match(";"); - domainEntry.add(new KeyStoreEntry(name, properties)); - } - match("}"); - - return domainEntry; - } - - /* - * Return a collection of domain properties or keystore properties. - */ - private Map parseProperties(String terminator) throws ParsingException, IOException { - - Map properties = new HashMap<>(); - String key; - String value; - while (!peek(terminator)) { - key = match("property name"); - match("="); - - try { - value = expand(match("quoted string")); - } catch (PropertyExpander.ExpandException peee) { - throw new IOException(peee.getLocalizedMessage()); - } - properties.put(key.toLowerCase(Locale.ENGLISH), value); - } - - return properties; - } - private boolean peekAndMatch(String expect) throws ParsingException, IOException { if (peek(expect)) { match(expect); @@ -620,17 +314,9 @@ private String match(String expect) throws ParsingException, IOException { } else if (expect.equalsIgnoreCase("permission type")) { value = st.sval; lookahead = st.nextToken(); - } else if (expect.equalsIgnoreCase("principal type")) { - value = st.sval; - lookahead = st.nextToken(); - } else if (expect.equalsIgnoreCase("domain name") - || expect.equalsIgnoreCase("keystore name") - || expect.equalsIgnoreCase("property name")) { - value = st.sval; - lookahead = st.nextToken(); - } else { - throw new ParsingException(st.lineno(), expect, st.sval); - } + } else { + throw new ParsingException(st.lineno(), expect, st.sval); + } break; case '"': if (expect.equalsIgnoreCase("quoted string")) { @@ -639,9 +325,6 @@ private String match(String expect) throws ParsingException, IOException { } else if (expect.equalsIgnoreCase("permission type")) { value = st.sval; lookahead = st.nextToken(); - } else if (expect.equalsIgnoreCase("principal type")) { - value = st.sval; - lookahead = st.nextToken(); } else { throw new ParsingException(st.lineno(), expect, st.sval); } @@ -700,7 +383,7 @@ private void skipEntry() throws ParsingException, IOException { *

* For example, the entry *

-     *      grant signedBy "Duke" {
+     *      grant {
      *          permission java.io.FilePermission "/tmp", "read,write";
      *      };
      *
@@ -711,33 +394,28 @@ private void skipEntry() throws ParsingException, IOException {
      * pe = new PermissionEntry("java.io.FilePermission",
      *                           "/tmp", "read,write");
      *
-     * ge = new GrantEntry("Duke", null);
+     * ge = new GrantEntry(null);
      *
      * ge.add(pe);
      *
      * 
* - * @author Roland Schemers + * @author Roland Schemers, edited by Craig Perkins * * version 1.19, 05/21/98 */ public static class GrantEntry { - public String signedBy; public String codeBase; - public LinkedList principals; public Vector permissionEntries; public GrantEntry() { - principals = new LinkedList<>(); permissionEntries = new Vector<>(); } - public GrantEntry(String signedBy, String codeBase) { + public GrantEntry(String codeBase) { this.codeBase = codeBase; - this.signedBy = signedBy; - principals = new LinkedList<>(); permissionEntries = new Vector<>(); } @@ -745,18 +423,10 @@ public void add(PermissionEntry pe) { permissionEntries.addElement(pe); } - public boolean remove(PrincipalEntry pe) { - return principals.remove(pe); - } - public boolean remove(PermissionEntry pe) { return permissionEntries.removeElement(pe); } - public boolean contains(PrincipalEntry pe) { - return principals.contains(pe); - } - public boolean contains(PermissionEntry pe) { return permissionEntries.contains(pe); } @@ -770,26 +440,10 @@ public Enumeration permissionElements() { public void write(PrintWriter out) { out.print("grant"); - if (signedBy != null) { - out.print(" signedBy \""); - out.print(signedBy); - out.print('"'); - if (codeBase != null) out.print(", "); - } if (codeBase != null) { out.print(" codeBase \""); out.print(codeBase); out.print('"'); - if (principals != null && principals.size() > 0) out.print(",\n"); - } - if (principals != null && principals.size() > 0) { - Iterator pli = principals.iterator(); - while (pli.hasNext()) { - out.print(" "); - PrincipalEntry pe = pli.next(); - pe.write(out); - if (pli.hasNext()) out.print(",\n"); - } } out.println(" {"); for (PermissionEntry pe : permissionEntries) { @@ -802,126 +456,11 @@ public void write(PrintWriter out) { public Object clone() { GrantEntry ge = new GrantEntry(); ge.codeBase = this.codeBase; - ge.signedBy = this.signedBy; - ge.principals = new LinkedList<>(this.principals); ge.permissionEntries = new Vector<>(this.permissionEntries); return ge; } } - /** - * Principal info (class and name) in a grant entry - */ - public static class PrincipalEntry implements Principal { - - public static final String WILDCARD_CLASS = "WILDCARD_PRINCIPAL_CLASS"; - public static final String WILDCARD_NAME = "WILDCARD_PRINCIPAL_NAME"; - public static final String REPLACE_NAME = "PolicyParser.REPLACE_NAME"; - - String principalClass; - String principalName; - - /** - * A PrincipalEntry consists of the Principal class and Principal name. - * - * @param principalClass the Principal class - * @param principalName the Principal name - * @throws NullPointerException if principalClass or principalName - * are null - */ - public PrincipalEntry(String principalClass, String principalName) { - if (principalClass == null || principalName == null) throw new NullPointerException("principalClass or principalName is null"); - this.principalClass = principalClass; - this.principalName = principalName; - } - - boolean isWildcardName() { - return principalName.equals(WILDCARD_NAME); - } - - boolean isWildcardClass() { - return principalClass.equals(WILDCARD_CLASS); - } - - boolean isReplaceName() { - return principalClass.equals(REPLACE_NAME); - } - - public String getPrincipalClass() { - return principalClass; - } - - public String getPrincipalName() { - return principalName; - } - - public String getDisplayClass() { - if (isWildcardClass()) { - return "*"; - } else if (isReplaceName()) { - return ""; - } else return principalClass; - } - - public String getDisplayName() { - return getDisplayName(false); - } - - public String getDisplayName(boolean addQuote) { - if (isWildcardName()) { - return "*"; - } else { - if (addQuote) return "\"" + principalName + "\""; - else return principalName; - } - } - - @Override - public String getName() { - return principalName; - } - - @Override - public String toString() { - if (!isReplaceName()) { - return getDisplayClass() + "/" + getDisplayName(); - } else { - return getDisplayName(); - } - } - - /** - * Test for equality between the specified object and this object. - * Two PrincipalEntries are equal if their class and name values - * are equal. - * - * @param obj the object to test for equality with this object - * @return true if the objects are equal, false otherwise - */ - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - - if (!(obj instanceof PrincipalEntry that)) return false; - - return (principalClass.equals(that.principalClass) && principalName.equals(that.principalName)); - } - - /** - * Return a hashcode for this PrincipalEntry. - * - * @return a hashcode for this PrincipalEntry - */ - @Override - public int hashCode() { - return principalClass.hashCode(); - } - - public void write(PrintWriter out) { - out.print("principal " + getDisplayClass() + " " + getDisplayName(true)); - } - } - /** * Each permission entry in the policy configuration file is * represented by a @@ -949,7 +488,6 @@ public static class PermissionEntry { public String permission; public String name; public String action; - public String signedBy; public PermissionEntry() {} @@ -990,15 +528,9 @@ public boolean equals(Object obj) { } if (this.action == null) { - if (that.action != null) return false; - } else { - if (!this.action.equals(that.action)) return false; - } - - if (this.signedBy == null) { - return that.signedBy == null; + return that.action == null; } else { - return this.signedBy.equals(that.signedBy); + return this.action.equals(that.action); } } @@ -1022,109 +554,10 @@ public void write(PrintWriter out) { out.print(action); out.print('"'); } - if (signedBy != null) { - out.print(", signedBy \""); - out.print(signedBy); - out.print('"'); - } out.println(";"); } } - /** - * Each domain entry in the keystore domain configuration file is - * represented by a DomainEntry object. - */ - static class DomainEntry { - private final String name; - private final Map properties; - private final Map entries; - - DomainEntry(String name, Map properties) { - this.name = name; - this.properties = properties; - entries = new HashMap<>(); - } - - String getName() { - return name; - } - - Map getProperties() { - return properties; - } - - Collection getEntries() { - return entries.values(); - } - - void add(KeyStoreEntry entry) throws ParsingException { - String keystoreName = entry.getName(); - if (!entries.containsKey(keystoreName)) { - entries.put(keystoreName, entry); - } else { - Object[] source = { keystoreName }; - String msg = "duplicate keystore name: " + keystoreName; - throw new ParsingException(msg, source); - } - } - - @Override - public String toString() { - StringBuilder s = new StringBuilder("\ndomain ").append(name); - - if (properties != null) { - for (Map.Entry property : properties.entrySet()) { - s.append("\n ").append(property.getKey()).append('=').append(property.getValue()); - } - } - s.append(" {\n"); - - for (KeyStoreEntry entry : entries.values()) { - s.append(entry).append("\n"); - } - s.append("}"); - - return s.toString(); - } - } - - /** - * Each keystore entry in the keystore domain configuration file is - * represented by a KeyStoreEntry object. - */ - - static class KeyStoreEntry { - private final String name; - private final Map properties; - - KeyStoreEntry(String name, Map properties) { - this.name = name; - this.properties = properties; - } - - String getName() { - return name; - } - - Map getProperties() { - return properties; - } - - @Override - public String toString() { - StringBuilder s = new StringBuilder("\n keystore ").append(name); - if (properties != null) { - for (Map.Entry property : properties.entrySet()) { - s.append("\n ").append(property.getKey()).append('=').append(property.getValue()); - } - } - s.append(";"); - - return s.toString(); - } - } - public static class ParsingException extends GeneralSecurityException { @java.io.Serial diff --git a/libs/agent-sm/agent-policy/src/main/java/org/opensearch/secure_sm/policy/PolicyUtil.java b/libs/agent-sm/agent-policy/src/main/java/org/opensearch/secure_sm/policy/PolicyUtil.java deleted file mode 100644 index ed19379b697c0..0000000000000 --- a/libs/agent-sm/agent-policy/src/main/java/org/opensearch/secure_sm/policy/PolicyUtil.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.secure_sm.policy; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.util.Arrays; - -/** - * Adapted from: https://github.com/openjdk/jdk23u/blob/master/src/java.base/share/classes/sun/security/util/PolicyUtil.java - */ -public class PolicyUtil { - - // standard PKCS11 KeyStore type - private static final String P11KEYSTORE = "PKCS11"; - - // reserved word - private static final String NONE = "NONE"; - - /* - * Fast path reading from file urls in order to avoid calling - * FileURLConnection.connect() which can be quite slow the first time - * it is called. We really should clean up FileURLConnection so that - * this is not a problem but in the meantime this fix helps reduce - * start up time noticeably for the new launcher. -- DAC - */ - public static InputStream getInputStream(URL url) throws IOException { - if ("file".equals(url.getProtocol())) { - String path = url.getFile().replace('/', File.separatorChar); - path = ParseUtil.decode(path); - return new FileInputStream(path); - } else { - return url.openStream(); - } - } - - /** - * this is intended for use by the policy parser to - * instantiate a KeyStore from the information in the GUI/policy file - */ - public static KeyStore getKeyStore( - URL policyUrl, // URL of policy file - String keyStoreName, // input: keyStore URL - String keyStoreType, // input: keyStore type - String keyStoreProvider, // input: keyStore provider - String storePassURL // input: keyStore password - ) throws KeyStoreException, IOException, NoSuchProviderException, NoSuchAlgorithmException, java.security.cert.CertificateException { - - if (keyStoreName == null) { - throw new IllegalArgumentException("null KeyStore name"); - } - - char[] keyStorePassword = null; - try { - KeyStore ks; - if (keyStoreType == null) { - keyStoreType = KeyStore.getDefaultType(); - } - - if (P11KEYSTORE.equalsIgnoreCase(keyStoreType) && !NONE.equals(keyStoreName)) { - throw new IllegalArgumentException( - "Invalid value (" - + keyStoreName - + ") for keystore URL. If the keystore type is \"" - + P11KEYSTORE - + "\", the keystore url must be \"" - + NONE - + "\"" - ); - } - - if (keyStoreProvider != null) { - ks = KeyStore.getInstance(keyStoreType, keyStoreProvider); - } else { - ks = KeyStore.getInstance(keyStoreType); - } - - if (storePassURL != null) { - URL passURL; - try { - @SuppressWarnings("deprecation") - var _unused = passURL = new URL(storePassURL); - // absolute URL - } catch (MalformedURLException e) { - // relative URL - if (policyUrl == null) { - throw e; - } - @SuppressWarnings("deprecation") - var _unused = passURL = new URL(policyUrl, storePassURL); - } - - try (InputStream in = passURL.openStream()) { - keyStorePassword = Password.readPassword(in); - } - } - - if (NONE.equals(keyStoreName)) { - ks.load(null, keyStorePassword); - } else { - /* - * location of keystore is specified as absolute URL in policy - * file, or is relative to URL of policy file - */ - URL keyStoreUrl; - try { - @SuppressWarnings("deprecation") - var _unused = keyStoreUrl = new URL(keyStoreName); - // absolute URL - } catch (MalformedURLException e) { - // relative URL - if (policyUrl == null) { - throw e; - } - @SuppressWarnings("deprecation") - var _unused = keyStoreUrl = new URL(policyUrl, keyStoreName); - } - - try (InputStream inStream = new BufferedInputStream(getInputStream(keyStoreUrl))) { - ks.load(inStream, keyStorePassword); - } - } - return ks; - } finally { - if (keyStorePassword != null) { - Arrays.fill(keyStorePassword, ' '); - } - } - } -} diff --git a/libs/agent-sm/agent/build.gradle b/libs/agent-sm/agent/build.gradle new file mode 100644 index 0000000000000..c79b51b8a6736 --- /dev/null +++ b/libs/agent-sm/agent/build.gradle @@ -0,0 +1,67 @@ +apply plugin: 'opensearch.build' +apply plugin: 'opensearch.publish' + +base { + archivesName = 'opensearch-agent' +} + +configurations { + bootstrap.extendsFrom(implementation) +} + +dependencies { + implementation project(":libs:agent-sm:bootstrap") + implementation "net.bytebuddy:byte-buddy:${versions.bytebuddy}" + compileOnly "com.google.code.findbugs:jsr305:3.0.2" + + testImplementation project(":test:framework") + testImplementation "junit:junit:${versions.junit}" +} + +var bootClasspath = configurations.bootstrap.incoming.artifactView { }.files + .getFiles() + .collect { it.name } + +jar { + manifest { + attributes( + "Can-Redefine-Classes": "true", + "Can-Retransform-Classes": "true", + "Agent-Class": "org.opensearch.javaagent.Agent", + "Premain-Class": "org.opensearch.javaagent.Agent", + "Boot-Class-Path": bootClasspath.join(' ') + ) + } +} + +compileJava { + options.compilerArgs -= '-Werror' +} + +test.enabled = true +testingConventions.enabled = false + +tasks.named('forbiddenApisMain').configure { + replaceSignatureFiles 'jdk-signatures' +} + +task copyJars(type: Copy) { + from(configurations.runtimeClasspath) + into "$buildDir/distributions" + dependsOn jar +} + +thirdPartyAudit { + ignoreMissingClasses( + 'com.sun.jna.FunctionMapper', + 'com.sun.jna.JNIEnv', + 'com.sun.jna.Library', + 'com.sun.jna.Native', + 'com.sun.jna.NativeLibrary', + 'com.sun.jna.Platform' + ) +} + +tasks.named('validateNebulaPom') { + dependsOn copyJars +} diff --git a/libs/agent-sm/agent/licenses/byte-buddy-1.17.3.jar.sha1 b/libs/agent-sm/agent/licenses/byte-buddy-1.17.3.jar.sha1 new file mode 100644 index 0000000000000..083ebb4fb91fa --- /dev/null +++ b/libs/agent-sm/agent/licenses/byte-buddy-1.17.3.jar.sha1 @@ -0,0 +1 @@ +dff77e21ebdac42bb4ebf5f3311fc7bfbac19cc3 \ No newline at end of file diff --git a/libs/agent-sm/agent/licenses/byte-buddy-LICENSE.txt b/libs/agent-sm/agent/licenses/byte-buddy-LICENSE.txt new file mode 100644 index 0000000000000..719c6605bb9b4 --- /dev/null +++ b/libs/agent-sm/agent/licenses/byte-buddy-LICENSE.txt @@ -0,0 +1,180 @@ +This product bundles ASM 9.7.1, which is available under a "3-clause BSD" +license. For details, see licenses/ASM. For more information visit ${asm.url}. + +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/libs/agent-sm/agent/licenses/byte-buddy-NOTICE.txt b/libs/agent-sm/agent/licenses/byte-buddy-NOTICE.txt new file mode 100644 index 0000000000000..929cbc6b18bba --- /dev/null +++ b/libs/agent-sm/agent/licenses/byte-buddy-NOTICE.txt @@ -0,0 +1,13 @@ +Copyright 2014 - Present Rafael Winterhalter + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/libs/agent-sm/agent/src/main/java/org/opensearch/javaagent/Agent.java b/libs/agent-sm/agent/src/main/java/org/opensearch/javaagent/Agent.java new file mode 100644 index 0000000000000..85705a02cb307 --- /dev/null +++ b/libs/agent-sm/agent/src/main/java/org/opensearch/javaagent/Agent.java @@ -0,0 +1,90 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.javaagent; + +import org.opensearch.javaagent.bootstrap.AgentPolicy; + +import java.lang.instrument.Instrumentation; +import java.nio.channels.SocketChannel; +import java.util.Map; + +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.ClassFileLocator; +import net.bytebuddy.dynamic.loading.ClassInjector; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.matcher.ElementMatcher.Junction; +import net.bytebuddy.matcher.ElementMatchers; + +/** + * Java Agent + */ +public class Agent { + /** + * Constructor + */ + private Agent() {} + + /** + * Premain + * @param agentArguments agent arguments + * @param instrumentation instrumentation + * @throws Exception Exception + */ + public static void premain(String agentArguments, Instrumentation instrumentation) throws Exception { + initAgent(instrumentation); + } + + /** + * Agent Main + * @param agentArguments agent arguments + * @param instrumentation instrumentation + * @throws Exception Exception + */ + public static void agentmain(String agentArguments, Instrumentation instrumentation) throws Exception { + initAgent(instrumentation); + } + + private static AgentBuilder createAgentBuilder(Instrumentation inst) throws Exception { + final Junction systemType = ElementMatchers.isSubTypeOf(SocketChannel.class); + + final AgentBuilder.Transformer transformer = (b, typeDescription, classLoader, module, pd) -> b.visit( + Advice.to(SocketChannelInterceptor.class) + .on(ElementMatchers.named("connect").and(ElementMatchers.not(ElementMatchers.isAbstract()))) + ); + + ClassInjector.UsingUnsafe.ofBootLoader() + .inject( + Map.of( + new TypeDescription.ForLoadedType(StackCallerChainExtractor.class), + ClassFileLocator.ForClassLoader.read(StackCallerChainExtractor.class), + new TypeDescription.ForLoadedType(AgentPolicy.class), + ClassFileLocator.ForClassLoader.read(AgentPolicy.class) + ) + ); + + final ByteBuddy byteBuddy = new ByteBuddy().with(Implementation.Context.Disabled.Factory.INSTANCE); + final AgentBuilder agentBuilder = new AgentBuilder.Default(byteBuddy).with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE) + .with(AgentBuilder.RedefinitionStrategy.REDEFINITION) + .with(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemError()) + .with(AgentBuilder.TypeStrategy.Default.REDEFINE) + .ignore(ElementMatchers.none()) + .type(systemType) + .transform(transformer); + + return agentBuilder; + } + + private static void initAgent(Instrumentation instrumentation) throws Exception { + AgentBuilder agentBuilder = createAgentBuilder(instrumentation); + agentBuilder.installOn(instrumentation); + } +} diff --git a/libs/agent-sm/agent/src/main/java/org/opensearch/javaagent/SocketChannelInterceptor.java b/libs/agent-sm/agent/src/main/java/org/opensearch/javaagent/SocketChannelInterceptor.java new file mode 100644 index 0000000000000..b3e0251c6f1b1 --- /dev/null +++ b/libs/agent-sm/agent/src/main/java/org/opensearch/javaagent/SocketChannelInterceptor.java @@ -0,0 +1,72 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.javaagent; + +import org.opensearch.javaagent.bootstrap.AgentPolicy; + +import java.lang.StackWalker.Option; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.NetPermission; +import java.net.SocketPermission; +import java.net.UnixDomainSocketAddress; +import java.security.Policy; +import java.security.ProtectionDomain; +import java.util.List; + +import net.bytebuddy.asm.Advice; +import net.bytebuddy.asm.Advice.Origin; + +/** + * SocketChannelInterceptor + */ +public class SocketChannelInterceptor { + /** + * SocketChannelInterceptor + */ + public SocketChannelInterceptor() {} + + /** + * Interceptors + * @param args arguments + * @param method method + * @throws Exception exceptions + */ + @Advice.OnMethodEnter + @SuppressWarnings("removal") + public static void intercept(@Advice.AllArguments Object[] args, @Origin Method method) throws Exception { + final Policy policy = AgentPolicy.getPolicy(); + if (policy == null) { + return; /* noop */ + } + + final StackWalker walker = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE); + final List callers = walker.walk(new StackCallerChainExtractor()); + + if (args[0] instanceof InetSocketAddress address) { + if (!AgentPolicy.isTrustedHost(address.getHostString())) { + final String host = address.getHostString() + ":" + address.getPort(); + + final SocketPermission permission = new SocketPermission(host, "connect,resolve"); + for (final ProtectionDomain domain : callers) { + if (!policy.implies(domain, permission)) { + throw new SecurityException("Denied access to: " + host + ", domain " + domain); + } + } + } + } else if (args[0] instanceof UnixDomainSocketAddress address) { + final NetPermission permission = new NetPermission("accessUnixDomainSocket"); + for (final ProtectionDomain domain : callers) { + if (!policy.implies(domain, permission)) { + throw new SecurityException("Denied access to: " + address + ", domain " + domain); + } + } + } + } +} diff --git a/libs/agent-sm/agent/src/main/java/org/opensearch/javaagent/StackCallerChainExtractor.java b/libs/agent-sm/agent/src/main/java/org/opensearch/javaagent/StackCallerChainExtractor.java new file mode 100644 index 0000000000000..6c33ca73e107d --- /dev/null +++ b/libs/agent-sm/agent/src/main/java/org/opensearch/javaagent/StackCallerChainExtractor.java @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.javaagent; + +import java.lang.StackWalker.StackFrame; +import java.security.ProtectionDomain; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Stack Caller Chain Extractor + */ +public final class StackCallerChainExtractor implements Function, List> { + /** + * Constructor + */ + public StackCallerChainExtractor() {} + + /** + * Folds the stack + * @param frames stack frames + */ + @Override + public List apply(Stream frames) { + return frames.map(StackFrame::getDeclaringClass) + .map(Class::getProtectionDomain) + .filter(pd -> pd.getCodeSource() != null) /* JDK */ + .distinct() + .collect(Collectors.toList()); + } +} diff --git a/libs/agent-sm/agent/src/main/java/org/opensearch/javaagent/SuppressForbidden.java b/libs/agent-sm/agent/src/main/java/org/opensearch/javaagent/SuppressForbidden.java new file mode 100644 index 0000000000000..e66e5a4d3f0e6 --- /dev/null +++ b/libs/agent-sm/agent/src/main/java/org/opensearch/javaagent/SuppressForbidden.java @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.javaagent; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to suppress forbidden-apis errors inside a whole class, a method, or a field. + */ +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) +@interface SuppressForbidden { + String reason(); +} diff --git a/libs/agent-sm/agent/src/main/java/org/opensearch/javaagent/package-info.java b/libs/agent-sm/agent/src/main/java/org/opensearch/javaagent/package-info.java new file mode 100644 index 0000000000000..447a0d8828875 --- /dev/null +++ b/libs/agent-sm/agent/src/main/java/org/opensearch/javaagent/package-info.java @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/** + * Java Agent + */ +package org.opensearch.javaagent; diff --git a/libs/agent-sm/agent/src/main/java/org/opensearch/package-info.java b/libs/agent-sm/agent/src/main/java/org/opensearch/package-info.java new file mode 100644 index 0000000000000..a15b5b51ae3de --- /dev/null +++ b/libs/agent-sm/agent/src/main/java/org/opensearch/package-info.java @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/** + * Java Agent + */ +package org.opensearch; diff --git a/libs/agent-sm/agent/src/test/java/org/opensearch/javaagent/SocketChannelInterceptorAllowTests.java b/libs/agent-sm/agent/src/test/java/org/opensearch/javaagent/SocketChannelInterceptorAllowTests.java new file mode 100644 index 0000000000000..88f0e5c1f5af2 --- /dev/null +++ b/libs/agent-sm/agent/src/test/java/org/opensearch/javaagent/SocketChannelInterceptorAllowTests.java @@ -0,0 +1,160 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.javaagent; + +import org.opensearch.test.OpenSearchTestCase; + +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnixDomainSocketAddress; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.nio.file.Path; + +import static org.junit.Assert.fail; + +@SuppressForbidden(reason = "Test class needs to use socket connections and DNS resolution for testing network permissions") +public class SocketChannelInterceptorAllowTests extends OpenSearchTestCase { + + private SocketChannelInterceptor interceptor; + private Method testMethod; + + @Override + public void setUp() throws Exception { + super.setUp(); + + interceptor = new SocketChannelInterceptor(); + testMethod = SocketChannelInterceptor.class.getDeclaredMethod("intercept", Object[].class, Method.class); + } + + public void testAllowedLocalhost() throws Exception { + InetSocketAddress address = new InetSocketAddress("localhost", 9200); + Object[] args = new Object[] { address }; + + try { + SocketChannelInterceptor.intercept(args, testMethod); + } catch (SecurityException e) { + fail("Should allow connection to localhost: " + e.getMessage()); + } + } + + public void testAllowedLocalhostIPv6() throws Exception { + InetSocketAddress address = new InetSocketAddress("::1", 9200); + Object[] args = new Object[] { address }; + + try { + SocketChannelInterceptor.intercept(args, testMethod); + } catch (SecurityException e) { + fail("Should allow connection to localhost IPv6 (::1): " + e.getMessage()); + } + } + + public void testAllowedExternalHost() throws Exception { + InetSocketAddress address = new InetSocketAddress("google.com", 443); + Object[] args = new Object[] { address }; + + try { + SocketChannelInterceptor.intercept(args, testMethod); + } catch (SecurityException e) { + fail("Should allow connection to external host: " + e.getMessage()); + } + } + + public void testAllowedUnixDomainSocket() throws Exception { + Path socketPath = Path.of("/tmp/test.sock"); + UnixDomainSocketAddress address = UnixDomainSocketAddress.of(socketPath); + Object[] args = new Object[] { address }; + + try { + SocketChannelInterceptor.intercept(args, testMethod); + } catch (SecurityException e) { + fail("Should allow Unix domain socket connection: " + e.getMessage()); + } + } + + public void testAllowedResolveLocalHost() throws Exception { + InetSocketAddress address = new InetSocketAddress("localhost", 9200); + Object[] args = new Object[] { address }; + + try { + InetAddress inetAddress = address.getAddress(); + SocketChannelInterceptor.intercept(args, testMethod); + assertNotNull("InetAddress should not be null", inetAddress); + assertFalse("Address should not be unresolved", address.isUnresolved()); + } catch (SecurityException e) { + fail("Should allow DNS resolution for localhost: " + e.getMessage()); + } + } + + public void testAllowedResolveExternalHost() throws Exception { + String host = "opensearch.org"; + int port = 443; + + try { + InetSocketAddress address = new InetSocketAddress(host, port); + Object[] args = new Object[] { address }; + + SocketChannelInterceptor.intercept(args, testMethod); + + assertFalse("Address should be resolved", address.isUnresolved()); + assertNotNull("Should have IP address", address.getAddress()); + + } catch (SecurityException e) { + fail("Should allow DNS resolution for external host: " + e.getMessage()); + } + } + + public void testAllowedListenPermission() throws Exception { + InetSocketAddress address = new InetSocketAddress("localhost", 0); + Object[] args = new Object[] { address }; + + try { + try (ServerSocketChannel serverChannel = ServerSocketChannel.open()) { + serverChannel.bind(address); + int boundPort = ((InetSocketAddress) serverChannel.getLocalAddress()).getPort(); + assertTrue("Should bind to an ephemeral port", boundPort > 0); + } + } catch (SecurityException e) { + fail("Should allow binding to localhost: " + e.getMessage()); + } + } + + public void testCombinedPermissions() throws Exception { + // 1. Create a server socket + try (ServerSocketChannel serverChannel = ServerSocketChannel.open()) { + InetSocketAddress bindAddress = new InetSocketAddress("localhost", 0); + serverChannel.bind(bindAddress); + + int port = ((InetSocketAddress) serverChannel.getLocalAddress()).getPort(); + + // 2. Resolve hostname + InetSocketAddress clientAddress = new InetSocketAddress("localhost", port); + Object[] args = new Object[] { clientAddress }; + + // Test the interceptor + SocketChannelInterceptor.intercept(args, testMethod); + + // Verify resolution + assertFalse("Address should be resolved", clientAddress.isUnresolved()); + + // 3. Connect to server + try (SocketChannel clientChannel = SocketChannel.open()) { + clientChannel.connect(clientAddress); + assertTrue("Should connect successfully", clientChannel.isConnected()); + } + } + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + } + +} diff --git a/libs/agent-sm/agent/src/test/resources/org/opensearch/bootstrap/test.policy b/libs/agent-sm/agent/src/test/resources/org/opensearch/bootstrap/test.policy new file mode 100644 index 0000000000000..eb0b2f7078883 --- /dev/null +++ b/libs/agent-sm/agent/src/test/resources/org/opensearch/bootstrap/test.policy @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +grant { + permission java.net.NetPermission "accessUnixDomainSocket"; + permission java.net.SocketPermission "*", "connect,resolve"; +}; \ No newline at end of file diff --git a/libs/build.gradle b/libs/build.gradle index c0fcc1ff2b977..2ac4e03819f3a 100644 --- a/libs/build.gradle +++ b/libs/build.gradle @@ -40,19 +40,22 @@ subprojects { * other libs. This keeps our dependencies simpler. */ project.afterEvaluate { - configurations.all { Configuration conf -> - dependencies.matching { it instanceof ProjectDependency }.all { ProjectDependency dep -> - Project depProject = project.project(dep.path) - if (depProject != null - && (false == depProject.path.equals(':libs:opensearch-core') && - false == depProject.path.equals(':libs:opensearch-common')) - && depProject.path.startsWith(':libs')) { - throw new InvalidUserDataException("projects in :libs " - + "may not depend on other projects libs except " - + ":libs:opensearch-core or :libs:opensearch-common but " - + "${project.path} depends on ${depProject.path}") + if (!project.path.equals(':libs:agent-sm:agent')) { + configurations.all { Configuration conf -> + dependencies.matching { it instanceof ProjectDependency }.all { ProjectDependency dep -> + Project depProject = project.project(dep.path) + if (depProject != null + && (false == depProject.path.equals(':libs:opensearch-core') && + false == depProject.path.equals(':libs:opensearch-common')&& + false == depProject.path.equals(':libs:agent-sm:agent-policy')) + && depProject.path.startsWith(':libs')) { + throw new InvalidUserDataException("projects in :libs " + + "may not depend on other projects libs except " + + ":libs:opensearch-core or :libs:opensearch-common but " + + "${project.path} depends on ${depProject.path}") + } + } } - } } } } diff --git a/libs/nio/src/test/java/org/opensearch/nio/SocketChannelContextTests.java b/libs/nio/src/test/java/org/opensearch/nio/SocketChannelContextTests.java index 1e559b597ca89..c20f7bd906af0 100644 --- a/libs/nio/src/test/java/org/opensearch/nio/SocketChannelContextTests.java +++ b/libs/nio/src/test/java/org/opensearch/nio/SocketChannelContextTests.java @@ -125,6 +125,7 @@ public void testSignalWhenPeerClosed() throws IOException { assertTrue(context.closeNow()); } + @AwaitsFix(bugUrl = "https://github.com/opensearch-project/OpenSearch/pull/16731") public void testRegisterInitiatesConnect() throws IOException { InetSocketAddress address = mock(InetSocketAddress.class); boolean isAccepted = randomBoolean(); @@ -205,6 +206,7 @@ public void testConnectFails() throws IOException { assertSame(ioException, exception.get()); } + @AwaitsFix(bugUrl = "https://github.com/opensearch-project/OpenSearch/pull/16731") public void testConnectCanSetSocketOptions() throws IOException { InetSocketAddress address = mock(InetSocketAddress.class); Config.Socket config; diff --git a/libs/secure-sm/build.gradle b/libs/secure-sm/build.gradle index 7a0b06699bf35..9febde423f796 100644 --- a/libs/secure-sm/build.gradle +++ b/libs/secure-sm/build.gradle @@ -31,6 +31,7 @@ apply plugin: 'opensearch.publish' dependencies { // do not add non-test compile dependencies to secure-sm without a good reason to do so + api project(":libs:agent-sm:agent-policy") testImplementation "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}" testImplementation "junit:junit:${versions.junit}" diff --git a/plugins/repository-azure/licenses/jna-platform-5.13.0.jar.sha1 b/plugins/repository-azure/licenses/jna-platform-5.13.0.jar.sha1 deleted file mode 100644 index e2a8ba1c1bbd3..0000000000000 --- a/plugins/repository-azure/licenses/jna-platform-5.13.0.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -88e9a306715e9379f3122415ef4ae759a352640d \ No newline at end of file diff --git a/plugins/repository-azure/licenses/jna-platform-5.16.0.jar.sha1 b/plugins/repository-azure/licenses/jna-platform-5.16.0.jar.sha1 new file mode 100644 index 0000000000000..84b5ec150d7a5 --- /dev/null +++ b/plugins/repository-azure/licenses/jna-platform-5.16.0.jar.sha1 @@ -0,0 +1 @@ +b2a9065f97c166893d504b164706512338e3bbc2 \ No newline at end of file diff --git a/plugins/repository-hdfs/build.gradle b/plugins/repository-hdfs/build.gradle index d3c92ac39f5b4..466abb0e60e02 100644 --- a/plugins/repository-hdfs/build.gradle +++ b/plugins/repository-hdfs/build.gradle @@ -146,7 +146,7 @@ for (String fixtureName : ['hdfsFixture', 'haHdfsFixture', 'secureHdfsFixture', } final List miniHDFSArgs = [] - if (BuildParams.runtimeJavaVersion >= JavaVersion.VERSION_23) { + if (BuildParams.runtimeJavaVersion >= JavaVersion.VERSION_23 && BuildParams.runtimeJavaVersion < JavaVersion.VERSION_24) { miniHDFSArgs.add('-Djava.security.manager=allow') } diff --git a/plugins/repository-hdfs/src/test/resources/org/opensearch/bootstrap/test.policy b/plugins/repository-hdfs/src/test/resources/org/opensearch/bootstrap/test.policy new file mode 100644 index 0000000000000..7899f339e5732 --- /dev/null +++ b/plugins/repository-hdfs/src/test/resources/org/opensearch/bootstrap/test.policy @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +grant { + permission java.net.NetPermission "accessUnixDomainSocket"; + permission java.net.SocketPermission "*", "connect,resolve"; +}; diff --git a/plugins/repository-s3/src/internalClusterTest/resources/org/opensearch/bootstrap/test.policy b/plugins/repository-s3/src/internalClusterTest/resources/org/opensearch/bootstrap/test.policy new file mode 100644 index 0000000000000..7899f339e5732 --- /dev/null +++ b/plugins/repository-s3/src/internalClusterTest/resources/org/opensearch/bootstrap/test.policy @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +grant { + permission java.net.NetPermission "accessUnixDomainSocket"; + permission java.net.SocketPermission "*", "connect,resolve"; +}; diff --git a/server/build.gradle b/server/build.gradle index fd2cac4c7506f..dfcfc5c6df99e 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -71,6 +71,7 @@ dependencies { api project(":libs:opensearch-task-commons") implementation project(':libs:opensearch-arrow-spi') + compileOnly project(":libs:agent-sm:bootstrap") compileOnly project(':libs:opensearch-plugin-classloader') testRuntimeOnly project(':libs:opensearch-plugin-classloader') @@ -378,7 +379,7 @@ tasks.named("licenseHeaders").configure { tasks.test { environment "node.roles.test", "[]" if (BuildParams.runtimeJavaVersion > JavaVersion.VERSION_1_8) { - jvmArgs += ["--add-opens", "java.base/java.nio.file=ALL-UNNAMED"] + jvmArgs += ["--add-opens", "java.base/java.nio.file=ALL-UNNAMED", "-Djdk.attach.allowAttachSelf=true", "-XX:+EnableDynamicAgentLoading" ] } } diff --git a/server/licenses/jna-5.13.0.jar.sha1 b/server/licenses/jna-5.13.0.jar.sha1 deleted file mode 100644 index faf2012f0b5c0..0000000000000 --- a/server/licenses/jna-5.13.0.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -1200e7ebeedbe0d10062093f32925a912020e747 \ No newline at end of file diff --git a/server/licenses/jna-5.16.0.jar.sha1 b/server/licenses/jna-5.16.0.jar.sha1 new file mode 100644 index 0000000000000..4b45642a01952 --- /dev/null +++ b/server/licenses/jna-5.16.0.jar.sha1 @@ -0,0 +1 @@ +ebea09f91dc9f7048099f963fb8d6f919f0a4d9c \ No newline at end of file diff --git a/server/src/main/java/org/opensearch/bootstrap/BootstrapChecks.java b/server/src/main/java/org/opensearch/bootstrap/BootstrapChecks.java index 8285f361ee220..b484c33fda5c9 100644 --- a/server/src/main/java/org/opensearch/bootstrap/BootstrapChecks.java +++ b/server/src/main/java/org/opensearch/bootstrap/BootstrapChecks.java @@ -47,6 +47,7 @@ import org.opensearch.discovery.DiscoveryModule; import org.opensearch.env.Environment; import org.opensearch.index.IndexModule; +import org.opensearch.javaagent.bootstrap.AgentPolicy; import org.opensearch.monitor.jvm.JvmInfo; import org.opensearch.monitor.process.ProcessProbe; import org.opensearch.node.NodeRoleSettings; @@ -57,6 +58,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.security.AllPermission; +import java.security.Policy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -720,10 +722,10 @@ public final BootstrapCheckResult check(BootstrapContext context) { @SuppressWarnings("removal") boolean isAllPermissionGranted() { - final SecurityManager sm = System.getSecurityManager(); - assert sm != null; + final Policy policy = AgentPolicy.getPolicy(); + assert policy != null; try { - sm.checkPermission(new AllPermission()); + AgentPolicy.checkPermission(new AllPermission()); } catch (final SecurityException e) { return false; } diff --git a/server/src/main/java/org/opensearch/bootstrap/OpenSearch.java b/server/src/main/java/org/opensearch/bootstrap/OpenSearch.java index 162b9be318cd5..7b011b5828428 100644 --- a/server/src/main/java/org/opensearch/bootstrap/OpenSearch.java +++ b/server/src/main/java/org/opensearch/bootstrap/OpenSearch.java @@ -48,7 +48,6 @@ import java.io.IOException; import java.nio.file.Path; -import java.security.Permission; import java.security.Security; import java.util.Arrays; import java.util.Locale; @@ -86,19 +85,7 @@ class OpenSearch extends EnvironmentAwareCommand { @SuppressWarnings("removal") public static void main(final String[] args) throws Exception { overrideDnsCachePolicyProperties(); - /* - * We want the JVM to think there is a security manager installed so that if internal policy decisions that would be based on the - * presence of a security manager or lack thereof act as if there is a security manager present (e.g., DNS cache policy). This - * forces such policies to take effect immediately. - */ - System.setSecurityManager(new SecurityManager() { - - @Override - public void checkPermission(Permission perm) { - // grant all permissions so that we can later set the security manager to the one that we want - } - }); LogConfigurator.registerErrorListener(); final OpenSearch opensearch = new OpenSearch(); int status = main(args, opensearch, Terminal.DEFAULT); diff --git a/server/src/main/java/org/opensearch/bootstrap/Security.java b/server/src/main/java/org/opensearch/bootstrap/Security.java index acf2d7ec6a5ac..5f65fcd139be9 100644 --- a/server/src/main/java/org/opensearch/bootstrap/Security.java +++ b/server/src/main/java/org/opensearch/bootstrap/Security.java @@ -41,9 +41,10 @@ import org.opensearch.common.transport.PortsRange; import org.opensearch.env.Environment; import org.opensearch.http.HttpTransportSettings; +import org.opensearch.javaagent.bootstrap.AgentPolicy; import org.opensearch.plugins.PluginInfo; import org.opensearch.plugins.PluginsService; -import org.opensearch.secure_sm.SecureSM; +import org.opensearch.secure_sm.policy.PolicyFile; import org.opensearch.transport.TcpTransport; import java.io.IOException; @@ -59,7 +60,6 @@ import java.security.NoSuchAlgorithmException; import java.security.Permissions; import java.security.Policy; -import java.security.URIParameter; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -144,7 +144,7 @@ static void configure(Environment environment, boolean filterBadDefaults) throws // enable security policy: union of template and environment-based paths, and possibly plugin permissions Map codebases = getCodebaseJarMap(JarHell.parseClassPath()); - Policy.setPolicy( + AgentPolicy.setPolicy( new OpenSearchPolicy( codebases, createPermissions(environment), @@ -159,7 +159,7 @@ static void configure(Environment environment, boolean filterBadDefaults) throws // SecureSM matches class names as regular expressions so we escape the $ that arises from the nested class name OpenSearchUncaughtExceptionHandler.PrivilegedHaltAction.class.getName().replace("$", "\\$"), Command.class.getName() }; - System.setSecurityManager(new SecureSM(classesThatCanExit)); + // System.setSecurityManager(new SecureSM(classesThatCanExit)); // do some basic tests selfTest(); @@ -280,14 +280,14 @@ static Policy readPolicy(URL policyFile, Map codebases) { addCodebaseToSystemProperties(propertiesSet, url, property, aliasProperty); } - return Policy.getInstance("JavaPolicy", new URIParameter(policyFile.toURI())); + return new PolicyFile(policyFile); } finally { // clear codebase properties for (String property : propertiesSet) { System.clearProperty(property); } } - } catch (NoSuchAlgorithmException | URISyntaxException e) { + } catch (final RuntimeException e) { throw new IllegalArgumentException("unable to parse policy file `" + policyFile + "`", e); } } diff --git a/server/src/main/java/org/opensearch/common/util/concurrent/ThreadContext.java b/server/src/main/java/org/opensearch/common/util/concurrent/ThreadContext.java index 8c15706adceeb..d680fc04789f8 100644 --- a/server/src/main/java/org/opensearch/common/util/concurrent/ThreadContext.java +++ b/server/src/main/java/org/opensearch/common/util/concurrent/ThreadContext.java @@ -44,14 +44,12 @@ import org.opensearch.core.common.io.stream.StreamOutput; import org.opensearch.core.common.io.stream.Writeable; import org.opensearch.http.HttpTransportSettings; -import org.opensearch.secure_sm.ThreadContextPermission; import org.opensearch.tasks.Task; import org.opensearch.tasks.TaskThreadContextStatePropagator; import org.opensearch.transport.client.OriginSettingClient; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.security.Permission; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -114,11 +112,6 @@ public final class ThreadContext implements Writeable { public static final String ACTION_ORIGIN_TRANSIENT_NAME = "action.origin"; // thread context permissions - - private static final Permission ACCESS_SYSTEM_THREAD_CONTEXT_PERMISSION = new ThreadContextPermission("markAsSystemContext"); - private static final Permission STASH_AND_MERGE_THREAD_CONTEXT_PERMISSION = new ThreadContextPermission("stashAndMergeHeaders"); - private static final Permission STASH_WITH_ORIGIN_THREAD_CONTEXT_PERMISSION = new ThreadContextPermission("stashWithOrigin"); - private static final Logger logger = LogManager.getLogger(ThreadContext.class); private static final ThreadContextStruct DEFAULT_CONTEXT = new ThreadContextStruct(); private final Map defaultHeader; @@ -223,10 +216,6 @@ public Writeable captureAsWriteable() { */ @SuppressWarnings("removal") public StoredContext stashWithOrigin(String origin) { - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - sm.checkPermission(STASH_WITH_ORIGIN_THREAD_CONTEXT_PERMISSION); - } final ThreadContext.StoredContext storedContext = stashContext(); putTransient(ACTION_ORIGIN_TRANSIENT_NAME, origin); return storedContext; @@ -246,10 +235,6 @@ public StoredContext stashWithOrigin(String origin) { */ @SuppressWarnings("removal") public StoredContext stashAndMergeHeaders(Map headers) { - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - sm.checkPermission(STASH_AND_MERGE_THREAD_CONTEXT_PERMISSION); - } final ThreadContextStruct context = threadLocal.get(); Map newHeader = new HashMap<>(headers); newHeader.putAll(context.requestHeaders); @@ -605,10 +590,6 @@ boolean isDefaultContext() { */ @SuppressWarnings("removal") public void markAsSystemContext() { - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - sm.checkPermission(ACCESS_SYSTEM_THREAD_CONTEXT_PERMISSION); - } threadLocal.set(threadLocal.get().setSystemContext(propagators)); } diff --git a/server/src/main/resources/org/opensearch/bootstrap/security.policy b/server/src/main/resources/org/opensearch/bootstrap/security.policy index f521ce0011540..fbe0afb3c2a95 100644 --- a/server/src/main/resources/org/opensearch/bootstrap/security.policy +++ b/server/src/main/resources/org/opensearch/bootstrap/security.policy @@ -93,6 +93,30 @@ grant codeBase "${codebase.reactor-core}" { permission java.net.SocketPermission "*", "connect,resolve"; }; +grant codeBase "${codebase.opensearch-cli}" { + permission java.net.SocketPermission "*", "connect,resolve"; +}; + +grant codeBase "${codebase.opensearch-core}" { + permission java.net.SocketPermission "*", "connect,resolve"; +}; + +grant codeBase "${codebase.jackson-core}" { + permission java.net.SocketPermission "*", "connect,resolve"; +}; + +grant codeBase "${codebase.opensearch-common}" { + permission java.net.SocketPermission "*", "connect,resolve"; +}; + +grant codeBase "${codebase.opensearch-x-content}" { + permission java.net.SocketPermission "*", "connect,resolve"; +}; + +grant codeBase "${codebase.opensearch}" { + permission java.net.SocketPermission "*", "connect,resolve"; +}; + //// Everything else: grant { diff --git a/server/src/main/resources/org/opensearch/bootstrap/test-framework.policy b/server/src/main/resources/org/opensearch/bootstrap/test-framework.policy index 78f302e9b23db..5fe1a5b64e6c7 100644 --- a/server/src/main/resources/org/opensearch/bootstrap/test-framework.policy +++ b/server/src/main/resources/org/opensearch/bootstrap/test-framework.policy @@ -101,9 +101,14 @@ grant codeBase "${codebase.junit}" { permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; }; +grant codeBase "${codebase.opensearch-core}" { + // opensearch-nio makes and accepts socket connections + permission java.net.SocketPermission "*", "accept,resolve,connect"; +}; + grant codeBase "${codebase.opensearch-nio}" { // opensearch-nio makes and accepts socket connections - permission java.net.SocketPermission "*", "accept,connect"; + permission java.net.SocketPermission "*", "accept,resolve,connect"; }; grant codeBase "${codebase.opensearch-rest-client}" { @@ -111,16 +116,19 @@ grant codeBase "${codebase.opensearch-rest-client}" { permission java.net.SocketPermission "*", "connect"; // rest client uses system properties which gets the default proxy permission java.net.NetPermission "getProxySelector"; + permission java.net.NetPermission "accessUnixDomainSocket"; }; grant codeBase "${codebase.httpcore5}" { // httpcore makes socket connections for rest tests permission java.net.SocketPermission "*", "connect"; + permission java.net.NetPermission "accessUnixDomainSocket"; }; grant codeBase "${codebase.httpclient5}" { // httpclient5 makes socket connections for rest tests permission java.net.SocketPermission "*", "connect,resolve"; + permission java.net.NetPermission "accessUnixDomainSocket"; }; grant codeBase "${codebase.httpcore-nio}" { diff --git a/server/src/test/java/org/opensearch/ExceptionSerializationTests.java b/server/src/test/java/org/opensearch/ExceptionSerializationTests.java index dd55abb65d19f..f4cc39684c86b 100644 --- a/server/src/test/java/org/opensearch/ExceptionSerializationTests.java +++ b/server/src/test/java/org/opensearch/ExceptionSerializationTests.java @@ -131,6 +131,7 @@ import java.io.EOFException; import java.io.FileNotFoundException; import java.io.IOException; +import java.net.URI; import java.net.URISyntaxException; import java.nio.file.AccessDeniedException; import java.nio.file.AtomicMoveNotSupportedException; @@ -166,7 +167,7 @@ public void testExceptionRegistration() throws ClassNotFoundException, IOExcepti final Set> notRegistered = new HashSet<>(); final Set> hasDedicatedWrite = new HashSet<>(); final Set> registered = new HashSet<>(); - final String path = "/org/opensearch"; + final String path = "org/opensearch"; final Path coreLibStartPath = PathUtils.get(OpenSearchException.class.getProtectionDomain().getCodeSource().getLocation().toURI()); final Path startPath = PathUtils.get(OpenSearchServerException.class.getProtectionDomain().getCodeSource().getLocation().toURI()) .resolve("org") @@ -254,7 +255,8 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx Files.walkFileTree(coreLibStartPath, visitor); // walk the server module start path Files.walkFileTree(startPath, visitor); - final Path testStartPath = PathUtils.get(ExceptionSerializationTests.class.getResource(path).toURI()); + final URI location = ExceptionSerializationTests.class.getProtectionDomain().getCodeSource().getLocation().toURI(); + final Path testStartPath = PathUtils.get(location).resolve(path); Files.walkFileTree(testStartPath, visitor); assertTrue(notRegistered.remove(TestException.class)); assertTrue(notRegistered.remove(UnknownHeaderException.class)); diff --git a/server/src/test/resources/org/opensearch/bootstrap/test.policy b/server/src/test/resources/org/opensearch/bootstrap/test.policy index c2b5a8e9c0a4e..30396afaf2ca4 100644 --- a/server/src/test/resources/org/opensearch/bootstrap/test.policy +++ b/server/src/test/resources/org/opensearch/bootstrap/test.policy @@ -10,4 +10,10 @@ grant { // allow to test Security policy and codebases permission java.util.PropertyPermission "*", "read,write"; permission java.security.SecurityPermission "createPolicy.JavaPolicy"; + permission java.net.NetPermission "accessUnixDomainSocket"; +}; + +grant codeBase "${codebase.framework}" { + permission java.net.NetPermission "accessUnixDomainSocket"; + permission java.net.SocketPermission "*", "accept,connect"; }; diff --git a/test/framework/build.gradle b/test/framework/build.gradle index e5297ca0807a4..41ed1ff13ef72 100644 --- a/test/framework/build.gradle +++ b/test/framework/build.gradle @@ -53,6 +53,9 @@ dependencies { api "org.bouncycastle:bcpkix-fips:${versions.bouncycastle_pkix}" api "org.bouncycastle:bcutil-fips:${versions.bouncycastle_util}" + compileOnly project(":libs:agent-sm:bootstrap") + compileOnly "com.github.spotbugs:spotbugs-annotations:4.9.0" + annotationProcessor "org.apache.logging.log4j:log4j-core:${versions.log4j}" } @@ -97,9 +100,12 @@ test { systemProperty 'tests.gradle_wire_compat_versions', BuildParams.bwcVersions.wireCompatible.join(',') systemProperty 'tests.gradle_unreleased_versions', BuildParams.bwcVersions.unreleased.join(',') - if (BuildParams.runtimeJavaVersion >= JavaVersion.VERSION_18) { + if (BuildParams.runtimeJavaVersion >= JavaVersion.VERSION_18 && BuildParams.runtimeJavaVersion <= JavaVersion.VERSION_23) { jvmArgs += ["-Djava.security.manager=allow"] } + + dependsOn(project(':libs:agent-sm:agent').copyJars) + jvmArgs += ["-javaagent:" + project(':libs:agent-sm:agent').jar.archiveFile.get()] } tasks.register("integTest", Test) { diff --git a/test/framework/licenses/byte-buddy-1.15.10.jar.sha1 b/test/framework/licenses/byte-buddy-1.15.10.jar.sha1 deleted file mode 100644 index b89163a2aa842..0000000000000 --- a/test/framework/licenses/byte-buddy-1.15.10.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -635c873fadd853c084f84fdc3cbd58c5dd8537f9 \ No newline at end of file diff --git a/test/framework/licenses/byte-buddy-1.17.3.jar.sha1 b/test/framework/licenses/byte-buddy-1.17.3.jar.sha1 new file mode 100644 index 0000000000000..083ebb4fb91fa --- /dev/null +++ b/test/framework/licenses/byte-buddy-1.17.3.jar.sha1 @@ -0,0 +1 @@ +dff77e21ebdac42bb4ebf5f3311fc7bfbac19cc3 \ No newline at end of file diff --git a/test/framework/licenses/mockito-core-5.14.2.jar.sha1 b/test/framework/licenses/mockito-core-5.14.2.jar.sha1 deleted file mode 100644 index a9fe959400ceb..0000000000000 --- a/test/framework/licenses/mockito-core-5.14.2.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -f7bf936008d7664e2002c3faf0c02071c8d10e7c \ No newline at end of file diff --git a/test/framework/licenses/mockito-core-5.16.0.jar.sha1 b/test/framework/licenses/mockito-core-5.16.0.jar.sha1 new file mode 100644 index 0000000000000..42b8beb215c2b --- /dev/null +++ b/test/framework/licenses/mockito-core-5.16.0.jar.sha1 @@ -0,0 +1 @@ +bd466e0926d691891f8f38823e0cbdcec6754358 \ No newline at end of file diff --git a/test/framework/src/main/java/org/opensearch/bootstrap/AgentAttach.java b/test/framework/src/main/java/org/opensearch/bootstrap/AgentAttach.java new file mode 100644 index 0000000000000..0a0df6756f21f --- /dev/null +++ b/test/framework/src/main/java/org/opensearch/bootstrap/AgentAttach.java @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.bootstrap; + +public final class AgentAttach { + public static boolean agentIsAttached() { + try { + Class.forName("org.opensearch.javaagent.Agent", false, ClassLoader.getSystemClassLoader()); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } +} diff --git a/test/framework/src/main/java/org/opensearch/bootstrap/BootstrapForTesting.java b/test/framework/src/main/java/org/opensearch/bootstrap/BootstrapForTesting.java index 76c7ce0628aac..70129daa2994d 100644 --- a/test/framework/src/main/java/org/opensearch/bootstrap/BootstrapForTesting.java +++ b/test/framework/src/main/java/org/opensearch/bootstrap/BootstrapForTesting.java @@ -46,9 +46,9 @@ import org.opensearch.common.settings.Settings; import org.opensearch.core.common.Strings; import org.opensearch.core.util.FileSystemUtils; +import org.opensearch.javaagent.bootstrap.AgentPolicy; import org.opensearch.mockito.plugin.PriviledgedMockMaker; import org.opensearch.plugins.PluginInfo; -import org.opensearch.secure_sm.SecureSM; import org.junit.Assert; import java.io.InputStream; @@ -168,7 +168,7 @@ public class BootstrapForTesting { final Optional testPolicy = Optional.ofNullable(Bootstrap.class.getResource("test.policy")) .map(policy -> Security.readPolicy(policy, codebases)); final Policy opensearchPolicy = new OpenSearchPolicy(codebases, perms, getPluginPermissions(), true, new Permissions()); - Policy.setPolicy(new Policy() { + AgentPolicy.setPolicy(new Policy() { @Override public boolean implies(ProtectionDomain domain, Permission permission) { // implements union @@ -176,10 +176,13 @@ public boolean implies(ProtectionDomain domain, Permission permission) { || testFramework.implies(domain, permission) || testPolicy.map(policy -> policy.implies(domain, permission)).orElse(false /* no policy */); } - }); + }, getTrustedHosts()); // Create access control context for mocking PriviledgedMockMaker.createAccessControlContext(); - System.setSecurityManager(SecureSM.createTestSecureSM(getTrustedHosts())); + // System.setSecurityManager(SecureSM.createTestSecureSM(getTrustedHosts())); + if (!AgentAttach.agentIsAttached()) { + throw new RuntimeException("the security agent is not attached"); + } Security.selfTest(); // guarantee plugin classes are initialized first, in case they have one-time hacks. diff --git a/test/framework/src/test/resources/org/opensearch/bootstrap/test.policy b/test/framework/src/test/resources/org/opensearch/bootstrap/test.policy new file mode 100644 index 0000000000000..07c9fe160e985 --- /dev/null +++ b/test/framework/src/test/resources/org/opensearch/bootstrap/test.policy @@ -0,0 +1,16 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +grant codeBase "${codebase.opensearch-nio}" { + permission java.net.NetPermission "accessUnixDomainSocket"; +}; + +grant { + permission java.net.NetPermission "accessUnixDomainSocket"; + permission java.net.SocketPermission "*", "accept,connect"; +};