diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/config/SecurityAgentConfig.java b/newrelic-agent/src/main/java/com/newrelic/agent/config/SecurityAgentConfig.java index 219ce36cbe..456af0db1e 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/config/SecurityAgentConfig.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/config/SecurityAgentConfig.java @@ -7,9 +7,13 @@ package com.newrelic.agent.config; +import com.google.common.collect.Sets; import com.newrelic.api.agent.Config; import com.newrelic.api.agent.NewRelic; +import java.util.Collections; +import java.util.Set; + /* Default config should look like: * * security: @@ -48,6 +52,8 @@ public class SecurityAgentConfig { private static final Config config = NewRelic.getAgent().getConfig(); private static final String ENABLED = "enabled"; private static final String DISABLED = "disabled"; + public static final Set SECURITY_AGENT_CLASS_TRANSFORMER_EXCLUDES_TO_IGNORE = Collections.unmodifiableSet( + Sets.newHashSet("^java/security/.*", "^javax/crypto/.*", "^net/sf/saxon.*")); /** * Create supportability metrics showing the enabled status of the security agent. diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/ClassNameFilter.java b/newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/ClassNameFilter.java index 3133cca71c..23c215dd56 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/ClassNameFilter.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/ClassNameFilter.java @@ -23,6 +23,9 @@ import java.util.Set; import java.util.regex.Pattern; +import static com.newrelic.agent.config.SecurityAgentConfig.SECURITY_AGENT_CLASS_TRANSFORMER_EXCLUDES_TO_IGNORE; +import static com.newrelic.agent.config.SecurityAgentConfig.shouldInitializeSecurityAgent; + /** * This filter is used to skip certain classes during the classloading transform callback based on the class name. */ @@ -38,8 +41,13 @@ public ClassNameFilter(IAgentLogger logger) { } /** - * Is the class excluded. - * + * Check if a class should be excluded from class transformation based on an excludes rule. + *

+ * excludePatterns is a list of exclude rules merged together from user config and the default META-INF/excludes file. + * Exclude rules are evaluated when determining whether to transform weaved classes in the InstrumentationContextManager + * or pointcuts in the PointCutClassTransformer. + * + * @param className name of the class to check exclusion rule for * @return true if this is an excluded class, false if not */ public boolean isExcluded(String className) { @@ -53,7 +61,7 @@ public boolean isExcluded(String className) { /** * Is the class included. - * + * * @return true if this is an included class, false if not */ public boolean isIncluded(String className) { @@ -79,6 +87,10 @@ public void addConfigClassFilters(AgentConfig config) { for (String exclude : excludes) { sb.append("\n").append(exclude); addExclude(exclude); + if (SECURITY_AGENT_CLASS_TRANSFORMER_EXCLUDES_TO_IGNORE.contains(exclude)) { + logger.finer(exclude + " class_transformer exclude explicitly added by user config. The user configured exclude rule will" + + " take precedence and will not be ignored due to the security agent being enabled."); + } } logger.finer(sb.toString()); } @@ -89,7 +101,7 @@ public void addConfigClassFilters(AgentConfig config) { } /** - * Add excluded classes in the excludes file. + * Add excluded classes in the META-INF/excludes file. */ public void addExcludeFileClassFilters() { InputStream iStream = this.getClass().getResourceAsStream(EXCLUDES_FILE); @@ -112,15 +124,34 @@ public void addExcludeFileClassFilters() { // ignore } } + boolean ignoreExcludeForSecurityAgent = false; + String formattedIgnoredExcludes = String.join(",", SECURITY_AGENT_CLASS_TRANSFORMER_EXCLUDES_TO_IGNORE); + boolean shouldInitializeSecurityAgent = shouldInitializeSecurityAgent(); + for (String exclude : excludeList) { - addExclude(exclude); + ignoreExcludeForSecurityAgent = shouldInitializeSecurityAgent && SECURITY_AGENT_CLASS_TRANSFORMER_EXCLUDES_TO_IGNORE.contains(exclude); + if (ignoreExcludeForSecurityAgent) { + logger.finer("Ignored " + exclude + " class_transformer exclude defined in META-INF/excludes because the security agent is enabled. " + + "This can be overridden by explicitly setting newrelic.config.class_transformer.excludes=" + formattedIgnoredExcludes + + " or NEW_RELIC_CLASS_TRANSFORMER_EXCLUDES=" + formattedIgnoredExcludes); + } else { + addExclude(exclude); + } + } + + if (ignoreExcludeForSecurityAgent) { + for (String exclude : SECURITY_AGENT_CLASS_TRANSFORMER_EXCLUDES_TO_IGNORE) { + // Remove the default exclude rule if the security agent is enabled. If a user explicitly adds one of these exclude rules via agent config + // then it will be added back to the list via ClassNameFilter.addConfigClassFilters, effectively taking precedence over this removal logic. + excludeList.remove(exclude); + } } logger.finer("Excludes initialized: " + excludeList); } /** * Include matching classes. - * + * * @param include either a class name or a regular expression */ public void addInclude(String include) { @@ -133,7 +164,7 @@ public void addInclude(String include) { /** * Include the given class. - * + * * @param className the name of the class to include */ public void addIncludeClass(String className) { @@ -143,7 +174,7 @@ public void addIncludeClass(String className) { /** * Include classes matching the regular expression. - * + * * @param regex a regular expression matching classes to include */ public void addIncludeRegex(String regex) { @@ -155,7 +186,7 @@ public void addIncludeRegex(String regex) { /** * Exclude matching classes. - * + * * @param exclude either a class name or a regular expression */ public void addExclude(String exclude) { @@ -168,7 +199,7 @@ public void addExclude(String exclude) { /** * Exclude the given class. - * + * * @param className the name of the class to exclude */ public void addExcludeClass(String className) { @@ -178,7 +209,7 @@ public void addExcludeClass(String className) { /** * Exclude classes matching the regular expression. - * + * * @param regex a regular expression matching classes to include */ public void addExcludeRegex(String regex) { @@ -190,7 +221,7 @@ public void addExcludeRegex(String regex) { /** * Create a regular expression for a class. - * + * * @return a regular expression that matches only the given class */ private String classNameToRegex(String className) { @@ -199,7 +230,7 @@ private String classNameToRegex(String className) { /** * Compile a regular expression. - * + * * @return a pattern or null if the regular expression could not be compiled */ private Pattern regexToPattern(String regex) { @@ -214,7 +245,7 @@ private Pattern regexToPattern(String regex) { /** * Is the argument a regular expression (rather than a class name). - * + * * @param value * @return true if this is a regular expression, false if not */ diff --git a/newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/context/InstrumentationContextManager.java b/newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/context/InstrumentationContextManager.java index 5b6a2f03bc..395fadd321 100644 --- a/newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/context/InstrumentationContextManager.java +++ b/newrelic-agent/src/main/java/com/newrelic/agent/instrumentation/context/InstrumentationContextManager.java @@ -31,7 +31,6 @@ import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; -import java.text.MessageFormat; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -220,11 +219,6 @@ public boolean shouldTransform(final String internalClassName, final ClassLoader Agent.LOG.log(Level.FINEST, "Skipping transform of {0}. Classloader {1} is excluded.", internalClassName, classloader); return false; } - if (internalClassName.startsWith("javax/crypto/")) { - // crypto classes can cause class circularity errors if they get too far along in the class transformer - Agent.LOG.finest(MessageFormat.format("Instrumentation skipped by ''javax crypto'' rule: {0}", internalClassName)); - return false; - } if (classNameFilter.isIncluded(internalClassName)) { Agent.LOG.log(Level.FINEST, "Class {0} is explicitly included", internalClassName); return true; diff --git a/newrelic-agent/src/main/resources/META-INF/excludes b/newrelic-agent/src/main/resources/META-INF/excludes index 5ff4474e5e..391ffdbc99 100644 --- a/newrelic-agent/src/main/resources/META-INF/excludes +++ b/newrelic-agent/src/main/resources/META-INF/excludes @@ -57,10 +57,6 @@ # adding exclusions for obfuscated jar - see ticket 197106 ^asposewobfuscated/.* ^com/aspose/words/.* -# saxon xlst classes - see ticket 195949 -^net/sf/saxon.* -# exclude java.security and sun.reflect classes from transformation -^java/security/.* ^sun/reflect/.* # exclude lambdas by excluding java.lang.invoke.* classes ^java/lang/invoke/.* @@ -94,3 +90,10 @@ ^java/util/stream/MatchOps\$MatchOp ^java/util/stream/MatchOps\$BooleanTerminalSink ^javax/security/auth/Subject\$SecureSet\$1 +### All default excludes listed below get ignored when the security agent is enabled +# exclude java.security and sun.reflect classes from transformation +^java/security/.* +# crypto classes can cause class circularity errors if they get too far along in the class transformer +^javax/crypto/.* +# saxon xlst classes - see ticket 195949 +^net/sf/saxon.* diff --git a/newrelic-agent/src/test/java/com/newrelic/agent/instrumentation/IncludeExcludeTest.java b/newrelic-agent/src/test/java/com/newrelic/agent/instrumentation/IncludeExcludeTest.java index 36c9867f10..b6cdaa4d6f 100644 --- a/newrelic-agent/src/test/java/com/newrelic/agent/instrumentation/IncludeExcludeTest.java +++ b/newrelic-agent/src/test/java/com/newrelic/agent/instrumentation/IncludeExcludeTest.java @@ -11,22 +11,26 @@ import com.newrelic.agent.config.AgentConfigImpl; import com.newrelic.agent.config.ConfigService; import com.newrelic.agent.config.ConfigServiceFactory; +import com.newrelic.agent.config.SecurityAgentConfig; import com.newrelic.agent.instrumentation.context.ClassMatchVisitorFactory; import com.newrelic.agent.instrumentation.context.InstrumentationContextManager; import com.newrelic.agent.service.ServiceFactory; import com.newrelic.agent.service.module.JarCollectorService; import org.junit.Assert; import org.junit.Test; +import org.mockito.MockedStatic; import java.util.HashMap; import java.util.Map; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; /** * Tests to ensure that InstrumentationContextManager has the correct include/exclude logic. */ public class IncludeExcludeTest { + private static MockedStatic mockSecurityAgentConfig; private static final ClassLoader SOME_CLASSLOADER = new ClassLoader() { }; @@ -62,17 +66,6 @@ public void testClassLoader() throws Exception { Assert.assertFalse(manager.shouldTransform("a/normal/class/Foo", badClassLoader)); } - @Test - public void testCrypto() throws Exception { - createServiceManager(createConfigMap()); - InstrumentationContextManager manager = new InstrumentationContextManager(null); - - Assert.assertTrue("Only javax/crypto/ classes should be skipped.", manager.shouldTransform("javax/cryptonotactuallycrypto", SOME_CLASSLOADER)); - Assert.assertFalse("javax/crypto/ classes should be skipped.", manager.shouldTransform("javax/crypto/SomeCryptoClass", SOME_CLASSLOADER)); - Assert.assertFalse("javax/crypto/ classes should be skipped.", - manager.shouldTransform("javax/crypto/foo/bar/baz/AnotherCryptoClass", SOME_CLASSLOADER)); - } - @Test public void testIncludes() throws Exception { Map configMap = createConfigMap(); @@ -104,4 +97,237 @@ public void testExcludes() throws Exception { Assert.assertFalse("config exclude should be skipped", manager.shouldTransform("my/custom/exclude", SOME_CLASSLOADER)); } + /* + * The META-INF/excludes file contains default rules for packages/classes that should be excluded from class transformation. + * The exclude rules for ^javax/crypto/.*, ^java/security/.*, and ^net/sf/saxon.* interfere with functionality of the security agent + * and thus are treated differently than other exclude rules depending on whether the security agent is enabled. + * + * When the security agent is enabled, these default excludes will be ignored and class transformation will be allowed for any classes in + * these packages. When the security agent is disabled (as is the default agent behavior) the class_transformer excludes will be applied + * and will prevent transformation of any classes in these packages. + * + * Users can override this behavior by explicitly setting the class_transformer excludes via one of the following config options. + * This will have the effect applying the exclude rules and preventing transformation of any classes in these packages. + * + * YAML: + * class_transformer: + * excludes: ^javax/crypto/.*,^java/security/.*,^net/sf/saxon.* + * + * System property: + * -Dnewrelic.config.class_transformer.excludes=^javax/crypto/.*,^java/security/.*,^net/sf/saxon.* + * + * Environment variable: + * NEW_RELIC_CLASS_TRANSFORMER_EXCLUDES=^javax/crypto/.*,^java/security/.*,^net/sf/saxon.* + * + * The tests below represent these special cases. + */ + + @Test + public void testJavaxCrypto() throws Exception { + // The default ^javax/crypto/.* exclude in META-INF/excludes should apply. This is default agent behavior. + createServiceManager(createConfigMap()); + InstrumentationContextManager manager = new InstrumentationContextManager(null); + + Assert.assertTrue("Only javax/crypto/ classes should be skipped.", manager.shouldTransform("javax/cryptonotactuallycrypto", SOME_CLASSLOADER)); + Assert.assertFalse("javax/crypto/ classes should be skipped.", manager.shouldTransform("javax/crypto/SomeCryptoClass", SOME_CLASSLOADER)); + Assert.assertFalse("javax/crypto/ classes should be skipped.", + manager.shouldTransform("javax/crypto/foo/bar/baz/AnotherCryptoClass", SOME_CLASSLOADER)); + } + + @Test + public void testShouldTransformJavaxCryptoWithSecurityAgentEnabled() throws Exception { + // The default ^javax/crypto/.* exclude in META-INF/excludes should be ignored when the security agent is enabled + mockSecurityAgentConfig = mockStatic(SecurityAgentConfig.class); + mockSecurityAgentConfig.when(SecurityAgentConfig::shouldInitializeSecurityAgent).thenReturn(true); + + createServiceManager(createConfigMap()); + InstrumentationContextManager manager = new InstrumentationContextManager(null); + + Assert.assertTrue("javax/crypto/ classes should be transformed when the security agent is enabled.", + manager.shouldTransform("javax/crypto/SomeCryptoClass", SOME_CLASSLOADER)); + Assert.assertTrue("javax/crypto/ classes should be transformed when the security agent is enabled.", + manager.shouldTransform("javax/crypto/foo/bar/baz/AnotherCryptoClass", SOME_CLASSLOADER)); + + mockSecurityAgentConfig.close(); + } + + @Test + public void testShouldNotTransformJavaxCryptoWithSecurityAgentDisabled() throws Exception { + // The default ^javax/crypto/.* exclude in META-INF/excludes should apply when the security agent is disabled + mockSecurityAgentConfig = mockStatic(SecurityAgentConfig.class); + mockSecurityAgentConfig.when(SecurityAgentConfig::shouldInitializeSecurityAgent).thenReturn(false); + + createServiceManager(createConfigMap()); + InstrumentationContextManager manager = new InstrumentationContextManager(null); + + Assert.assertFalse("javax/crypto/ classes should not be transformed when the security agent is disabled.", + manager.shouldTransform("javax/crypto/SomeCryptoClass", SOME_CLASSLOADER)); + Assert.assertFalse("javax/crypto/ classes should not be transformed when the security agent is disabled.", + manager.shouldTransform("javax/crypto/foo/bar/baz/AnotherCryptoClass", SOME_CLASSLOADER)); + + mockSecurityAgentConfig.close(); + } + + @Test + public void testShouldNotTransformJavaxCryptoWithSecurityAgentEnabledAndExplicitConfigExclude() throws Exception { + // The default ^javax/crypto/.* exclude in META-INF/excludes should not be ignored when the security agent is enabled + // and the exclude rule is explicitly added via user config. User config takes precedence. + mockSecurityAgentConfig = mockStatic(SecurityAgentConfig.class); + mockSecurityAgentConfig.when(SecurityAgentConfig::shouldInitializeSecurityAgent).thenReturn(true); + + Map configMap = createConfigMap(); + Map classTransformerConfigMap = new HashMap<>(); + // This simulates setting class_transformer.excludes setting via config + classTransformerConfigMap.put("excludes", "^javax/crypto/.*"); + configMap.put("class_transformer", classTransformerConfigMap); + + createServiceManager(configMap); + InstrumentationContextManager manager = new InstrumentationContextManager(null); + + Assert.assertFalse("javax/crypto/ classes should be not transformed when the exclude is explicitly set via config, even if security agent is enabled.", + manager.shouldTransform("javax/crypto/SomeCryptoClass", SOME_CLASSLOADER)); + Assert.assertFalse("javax/crypto/ classes should be not transformed when the exclude is explicitly set via config, even if security agent is enabled.", + manager.shouldTransform("javax/crypto/foo/bar/baz/AnotherCryptoClass", SOME_CLASSLOADER)); + + mockSecurityAgentConfig.close(); + } + + @Test + public void testJavaSecurity() throws Exception { + // The default ^java/security/.* exclude in META-INF/excludes should apply. This is default agent behavior. + createServiceManager(createConfigMap()); + InstrumentationContextManager manager = new InstrumentationContextManager(null); + + Assert.assertTrue("Only java/security/ classes should be skipped.", manager.shouldTransform("java/securitynotactuallysecurity", SOME_CLASSLOADER)); + Assert.assertFalse("java/security/ classes should be skipped.", manager.shouldTransform("java/security/SomeSecurityClass", SOME_CLASSLOADER)); + Assert.assertFalse("java/security/ classes should be skipped.", + manager.shouldTransform("java/security/foo/bar/baz/AnotherSecurityClass", SOME_CLASSLOADER)); + } + + @Test + public void testShouldTransformJavaSecurityWithSecurityAgentEnabled() throws Exception { + // The default ^java/security/.* exclude in META-INF/excludes should be ignored when the security agent is enabled + mockSecurityAgentConfig = mockStatic(SecurityAgentConfig.class); + mockSecurityAgentConfig.when(SecurityAgentConfig::shouldInitializeSecurityAgent).thenReturn(true); + + createServiceManager(createConfigMap()); + InstrumentationContextManager manager = new InstrumentationContextManager(null); + + Assert.assertTrue("java/security/ classes should be transformed when the security agent is enabled.", + manager.shouldTransform("java/security/SomeSecurityClass", SOME_CLASSLOADER)); + Assert.assertTrue("java/security/ classes should be transformed when the security agent is enabled.", + manager.shouldTransform("java/security/foo/bar/baz/AnotherSecurityClass", SOME_CLASSLOADER)); + + mockSecurityAgentConfig.close(); + } + + @Test + public void testShouldNotTransformJavaSecurityWithSecurityAgentDisabled() throws Exception { + // The default ^java/security/.* exclude in META-INF/excludes should apply when the security agent is disabled + mockSecurityAgentConfig = mockStatic(SecurityAgentConfig.class); + mockSecurityAgentConfig.when(SecurityAgentConfig::shouldInitializeSecurityAgent).thenReturn(false); + + createServiceManager(createConfigMap()); + InstrumentationContextManager manager = new InstrumentationContextManager(null); + + Assert.assertFalse("java/security/ classes should not be transformed when the security agent is disabled.", + manager.shouldTransform("java/security/SomeSecurityClass", SOME_CLASSLOADER)); + Assert.assertFalse("java/security/ classes should not be transformed when the security agent is disabled.", + manager.shouldTransform("java/security/foo/bar/baz/AnotherSecurityClass", SOME_CLASSLOADER)); + + mockSecurityAgentConfig.close(); + } + + @Test + public void testShouldNotTransformJavaSecurityWithSecurityAgentEnabledAndExplicitConfigExclude() throws Exception { + // The default ^java/security/.* exclude in META-INF/excludes should not be ignored when the security agent is enabled + // and the exclude rule is explicitly added via user config. User config takes precedence. + mockSecurityAgentConfig = mockStatic(SecurityAgentConfig.class); + mockSecurityAgentConfig.when(SecurityAgentConfig::shouldInitializeSecurityAgent).thenReturn(true); + + Map configMap = createConfigMap(); + Map classTransformerConfigMap = new HashMap<>(); + // This simulates setting class_transformer.excludes setting via config + classTransformerConfigMap.put("excludes", "^java/security/.*"); + configMap.put("class_transformer", classTransformerConfigMap); + + createServiceManager(configMap); + InstrumentationContextManager manager = new InstrumentationContextManager(null); + + Assert.assertFalse("java/security/ classes should be not transformed when the exclude is explicitly set via config, even if security agent is enabled.", + manager.shouldTransform("java/security/SomeSecurityClass", SOME_CLASSLOADER)); + Assert.assertFalse("java/security/ classes should be not transformed when the exclude is explicitly set via config, even if security agent is enabled.", + manager.shouldTransform("java/security/foo/bar/baz/AnotherSecurityClass", SOME_CLASSLOADER)); + + mockSecurityAgentConfig.close(); + } + + @Test + public void testNetSfSaxon() throws Exception { + // The default ^net/sf/saxon.* exclude in META-INF/excludes should apply. This is default agent behavior. + createServiceManager(createConfigMap()); + InstrumentationContextManager manager = new InstrumentationContextManager(null); + + Assert.assertTrue("Only net/sf/saxon/ classes should be skipped.", manager.shouldTransform("net/sfsaxonnotactuallysaxon", SOME_CLASSLOADER)); + Assert.assertFalse("net/sf/saxon/ classes should be skipped.", manager.shouldTransform("net/sf/saxon/SomeSaxonClass", SOME_CLASSLOADER)); + Assert.assertFalse("net/sf/saxon/ classes should be skipped.", manager.shouldTransform("net/sf/saxon/foo/bar/baz/AnotherSaxonClass", SOME_CLASSLOADER)); + } + + @Test + public void testShouldTransformNetSfSaxonWithSecurityAgentEnabled() throws Exception { + // The default ^net/sf/saxon.* exclude in META-INF/excludes should be ignored when the security agent is enabled + mockSecurityAgentConfig = mockStatic(SecurityAgentConfig.class); + mockSecurityAgentConfig.when(SecurityAgentConfig::shouldInitializeSecurityAgent).thenReturn(true); + + createServiceManager(createConfigMap()); + InstrumentationContextManager manager = new InstrumentationContextManager(null); + + Assert.assertTrue("net/sf/saxon/ classes should be transformed when the security agent is enabled.", + manager.shouldTransform("net/sf/saxon/SomeSaxonClass", SOME_CLASSLOADER)); + Assert.assertTrue("net/sf/saxon/ classes should be transformed when the security agent is enabled.", + manager.shouldTransform("net/sf/saxon/foo/bar/baz/AnotherSaxonClass", SOME_CLASSLOADER)); + + mockSecurityAgentConfig.close(); + } + + @Test + public void testShouldNotTransformNetSfSaxonWithSecurityAgentDisabled() throws Exception { + // The default ^net/sf/saxon.* exclude in META-INF/excludes should apply when the security agent is disabled + mockSecurityAgentConfig = mockStatic(SecurityAgentConfig.class); + mockSecurityAgentConfig.when(SecurityAgentConfig::shouldInitializeSecurityAgent).thenReturn(false); + + createServiceManager(createConfigMap()); + InstrumentationContextManager manager = new InstrumentationContextManager(null); + + Assert.assertFalse("net/sf/saxon/ classes should not be transformed when the security agent is disabled.", + manager.shouldTransform("net/sf/saxon/SomeSaxonClass", SOME_CLASSLOADER)); + Assert.assertFalse("net/sf/saxon/ classes should not be transformed when the security agent is disabled.", + manager.shouldTransform("net/sf/saxon/foo/bar/baz/AnotherSaxonClass", SOME_CLASSLOADER)); + + mockSecurityAgentConfig.close(); + } + + @Test + public void testShouldNotTransformNetSfSaxonWithSecurityAgentEnabledAndExplicitConfigExclude() throws Exception { + // The default ^net/sf/saxon.* exclude in META-INF/excludes should not be ignored when the security agent is enabled + // and the exclude rule is explicitly added via user config. User config takes precedence. + mockSecurityAgentConfig = mockStatic(SecurityAgentConfig.class); + mockSecurityAgentConfig.when(SecurityAgentConfig::shouldInitializeSecurityAgent).thenReturn(true); + + Map configMap = createConfigMap(); + Map classTransformerConfigMap = new HashMap<>(); + // This simulates setting class_transformer.excludes setting via config + classTransformerConfigMap.put("excludes", "^net/sf/saxon.*"); + configMap.put("class_transformer", classTransformerConfigMap); + + createServiceManager(configMap); + InstrumentationContextManager manager = new InstrumentationContextManager(null); + + Assert.assertFalse("java/security/ classes should be not transformed when the exclude is explicitly set via config, even if security agent is enabled.", + manager.shouldTransform("net/sf/saxon/SomeSaxonClass", SOME_CLASSLOADER)); + Assert.assertFalse("java/security/ classes should be not transformed when the exclude is explicitly set via config, even if security agent is enabled.", + manager.shouldTransform("net/sf/saxon/foo/bar/baz/AnotherSaxonClass", SOME_CLASSLOADER)); + + mockSecurityAgentConfig.close(); + } }