Copilot commented on code in PR #4471: URL: https://github.com/apache/solr/pull/4471#discussion_r3316213352
########## solr/server/etc/agent-security-extra.policy: ########## @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ + +// Solr Security Agent — operator extension policy +// +// This file is loaded in addition to the bundled default policy (agent-security.policy). +// Add entries here to permit additional file paths or network endpoints required by your +// specific deployment, plugins, or custom components. +// +// This file's location can be overridden via: +// - Environment variable: SOLR_SECURITY_AGENT_EXTRA_POLICY=/path/to/custom.policy +// - System property: -Dsolr.security.agent.extra.policy=/path/to/custom.policy +// +// An absent file is silently skipped — it is not a startup error. +// +// Syntax: JDK-style .policy files (same as agent-security.policy). +// Supported variables: ${solr.home}, ${solr.data.dir}, ${solr.log.dir}, ${solr.install.dir}, +// ${java.io.tmpdir}, ${java.home}, ${solr.port}, ${solr.zk.port} Review Comment: The list of "Supported variables" here does not match what `PolicyPropertyExpander` actually resolves; the same incorrect names appear (`${solr.home}`, `${solr.data.dir}`, `${solr.log.dir}`, `${solr.port}`). The default policy file at `solr/server/etc/agent-security.policy` uses `${solr.solr.home}`, `${solr.data.home}`, `${solr.logs.dir}`, `${solr.port.listen}`. Please reconcile this comment with the actual supported variable names so operators copying these examples don't hit "Unresolved policy variable" errors. ########## solr/agent-sm/src/java/org/apache/solr/security/agent/FileInterceptor.java: ########## @@ -0,0 +1,277 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ +package org.apache.solr.security.agent; + +import java.lang.reflect.Method; +import java.nio.file.LinkOption; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.nio.file.spi.FileSystemProvider; +import java.util.Collection; +import java.util.Locale; +import java.util.Set; +import net.bytebuddy.asm.Advice; + +/** + * ByteBuddy {@link Advice} interceptor for file-system operations. + * + * <p>This file was derived from the OpenSearch project and modified. See {@code NOTICE.txt} for + * attribution. + */ +public class FileInterceptor { + + /** FileInterceptor */ + public FileInterceptor() {} + + /** + * Intercepts file operations. + * + * @param args arguments + * @param method method + * @throws Exception exceptions + */ + @Advice.OnMethodEnter + public static void intercept(@Advice.AllArguments Object[] args, @Advice.Origin Method method) + throws Exception { + if (!AgentPolicy.isInitialized()) return; + final AgentPolicy policy = AgentPolicy.getInstance(); + + FileSystemProvider provider = null; + String filePath = null; + if (args.length > 0 && args[0] instanceof String pathStr) { + filePath = Path.of(pathStr).toAbsolutePath().toString(); + } else if (args.length > 0 && args[0] instanceof Path path) { + filePath = path.toAbsolutePath().toString(); + provider = path.getFileSystem().provider(); + } + + if (filePath == null) { + return; // No valid file path found + } + + if (provider != null && policy.trustedFileSystems().contains(provider.getScheme())) { + return; + } + + final StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); + final String caller = walker.getCallerClass().getName(); + + final String name = method.getName(); + boolean isMutating = name.equals("move") || name.equals("write") || name.startsWith("create"); + final boolean isDelete = isMutating == false ? name.startsWith("delete") : false; + + // This is Windows implementation of UNIX Domain Sockets (close) + boolean isUnixSocketCaller = false; + if (isDelete == true) { + final Collection<Class<?>> chain = walker.walk(StackCallerClassChainExtractor.INSTANCE); + for (final Class<?> cls : chain) { + if (cls.getName().equalsIgnoreCase("sun.nio.ch.PipeImpl$Initializer$LoopbackConnector")) { + isUnixSocketCaller = true; + break; + } + } + } + + if (isDelete == true && isUnixSocketCaller == true) { + // Unix domain socket cleanup — local IPC, always allow + return; + } else { + String targetFilePath = null; + if (isMutating == false && isDelete == false) { + if (name.equals("newByteChannel") == true || name.equals("open") == true) { + if (args.length > 1) { + if (args instanceof OpenOption[] opts) { Review Comment: Bug: this `instanceof` check uses `args` (the full `Object[]` argument array from `@Advice.AllArguments`) instead of `args[1]`. An `Object[]` can never be an `OpenOption[]`, so this branch is effectively unreachable. The subsequent branches correctly test `args[1]`. This means callers passing a varargs `OpenOption[]` directly will fall through to the `Object[]` branch (which is fine since arrays are `Object[]`), but the dedicated typed branch never fires. The check should be `args[1] instanceof OpenOption[] opts`. ########## changelog/unreleased/SOLR-15868-java-agent.yml: ########## @@ -0,0 +1,9 @@ +title: > + Introduce a Java agent (solr-agent-sm) that enforces file access, outbound network, + System.exit(), and process-spawn controls via ByteBuddy instrumentation. +type: added +authors: + - name: Jan Høydahl +links: + - name: SOLR-15868 + url: https://issues.apache.org/jira/browse/SOLR-15868 Review Comment: JIRA ticket mismatch: this changelog file uses SOLR-15868 in its name and link, but the PR title, description, and `@Deprecated` documentation throughout the rest of the change reference SOLR-17767. One of the two is incorrect — please align the changelog filename and link with the actual JIRA issue. ########## solr/solr-ref-guide/modules/deployment-guide/pages/security-agent.adoc: ########## @@ -0,0 +1,166 @@ += Security Agent +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF 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. + +Solr includes a Java agent (`solr-agent-sm`) that aims to fill the gaps left by the removal of Java Security Manager (JSM) in JDK 24. +The agent intercepts file access, network connections, `System.exit()`, and process spawning at the bytecode level, enforcing a configurable policy. + +== Protections Active by Default + +When the agent JAR is present, the startup scripts automatically activate it. +The following protections apply to all code running inside the Solr JVM: + +* **File access** — reads and writes are restricted to permitted directories (Solr home, data dir, log dir, temp dir). +* **Outbound network** — connections are restricted to loopback addresses and intra-cluster ports. +* **`System.exit()` / `Runtime.halt()`** — only approved caller classes may shut down the JVM. +* **Process spawning** — `ProcessBuilder.start()` and `Runtime.exec()` are blocked by default. + +== Enforcement Modes + +The agent operates in two modes, controlled by the `SOLR_SECURITY_AGENT_MODE` environment variable: + +`warn` (default):: +Violations are logged at `WARN` level and counted in `/admin/metrics`, but the operation is allowed to continue. +This is the safe default for existing deployments — operators can observe violations without risk. + +`enforce`:: +Violations throw a `SecurityException`, blocking the operation. +Enable this mode once you have verified that your deployment produces no unexpected violations in `warn` mode. + +Set the mode in `solr.in.sh` or `solr.in.cmd`: + +[source,bash] +---- +SOLR_SECURITY_AGENT_MODE=enforce +---- + +== Policy File Format + +Solr uses JDK-style `.policy` files with Solr-specific variable substitution. +Two files are loaded: + +`server/etc/agent-security.policy` (default):: +The bundled production policy. +Do not edit this file — it will be overwritten on upgrade. + +`server/etc/agent-security-extra.policy` (operator extension):: +Add your custom rules here. +This file is silently skipped if absent. +Its location can be overridden via `SOLR_SECURITY_AGENT_EXTRA_POLICY`. + +=== Variable Substitution + +Any java property will be expanded in policy entries, here are the most common. Environment variables are expanded if they adhere to the mapping convention `SOLR_FOO --> solr.foo`: Review Comment: The env-var mapping convention is described backwards. `PolicyPropertyExpander.getPropertyOrEnv` derives the env var from the system property by upper-casing and replacing `.` with `_` (e.g. `solr.foo` → `SOLR_FOO`), not the other way around. The current phrasing ("if they adhere to the mapping convention `SOLR_FOO --> solr.foo`") is ambiguous about direction and inverts how a reader would expect to use it. Consider rephrasing as "system property `solr.foo.bar` is also looked up as environment variable `SOLR_FOO_BAR`". ########## solr/core/src/java/org/apache/solr/security/AgentViolationBridge.java: ########## @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ +package org.apache.solr.security; + +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Wires the Solr security agent's violation reporter to SLF4J once Log4j2 is available. Uses + * reflection because {@code solr:agent-sm} and {@code solr:core} have no compile-time dependency on + * each other. Call {@link #wire()} from {@code CoreContainer} after Log4j2 is initialised; from + * that point violations appear in {@code solr.log} rather than {@code System.err}. Safe to call + * when the agent JAR is absent — {@link ClassNotFoundException} is silently ignored. + */ +public final class AgentViolationBridge { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private AgentViolationBridge() {} + + /** + * Wires the security agent violation reporter to SLF4J. No-op if the agent JAR is not present. + */ + public static void wire() { + try { + // SecurityViolationLogger is in the bootstrap classloader via Boot-Class-Path. + Class<?> cls = Class.forName("org.apache.solr.security.agent.SecurityViolationLogger"); + Field f = cls.getField("reporter"); + Consumer<String> bridge = msg -> log.warn("SECURITY VIOLATION {}", msg); + f.set(null, bridge); Review Comment: `AgentViolationBridge.wire()` uses `Field.set(null, bridge)` to assign to a `public volatile` static field via reflection. With the JPMS module system this works because both classes are in `org.apache.solr.security.agent` / `org.apache.solr.security`, but since the agent JAR is loaded into the bootstrap classloader (per `Boot-Class-Path` manifest), some JDKs enforce additional access checks. Consider adding `f.setAccessible(true)` before `f.set(...)` as a defensive measure to avoid `IllegalAccessException` in restricted environments. Alternatively, expose a `setReporter(Consumer<String>)` static method on `SecurityViolationLogger` and invoke that via reflection, which would also avoid relying on a public mutable field. ########## solr/solr-ref-guide/modules/deployment-guide/pages/security-agent.adoc: ########## @@ -0,0 +1,166 @@ += Security Agent +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF 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. + +Solr includes a Java agent (`solr-agent-sm`) that aims to fill the gaps left by the removal of Java Security Manager (JSM) in JDK 24. +The agent intercepts file access, network connections, `System.exit()`, and process spawning at the bytecode level, enforcing a configurable policy. + +== Protections Active by Default + +When the agent JAR is present, the startup scripts automatically activate it. +The following protections apply to all code running inside the Solr JVM: + +* **File access** — reads and writes are restricted to permitted directories (Solr home, data dir, log dir, temp dir). +* **Outbound network** — connections are restricted to loopback addresses and intra-cluster ports. +* **`System.exit()` / `Runtime.halt()`** — only approved caller classes may shut down the JVM. +* **Process spawning** — `ProcessBuilder.start()` and `Runtime.exec()` are blocked by default. + +== Enforcement Modes + +The agent operates in two modes, controlled by the `SOLR_SECURITY_AGENT_MODE` environment variable: + +`warn` (default):: +Violations are logged at `WARN` level and counted in `/admin/metrics`, but the operation is allowed to continue. +This is the safe default for existing deployments — operators can observe violations without risk. + +`enforce`:: +Violations throw a `SecurityException`, blocking the operation. +Enable this mode once you have verified that your deployment produces no unexpected violations in `warn` mode. + +Set the mode in `solr.in.sh` or `solr.in.cmd`: + +[source,bash] +---- +SOLR_SECURITY_AGENT_MODE=enforce +---- + +== Policy File Format + +Solr uses JDK-style `.policy` files with Solr-specific variable substitution. +Two files are loaded: + +`server/etc/agent-security.policy` (default):: +The bundled production policy. +Do not edit this file — it will be overwritten on upgrade. + +`server/etc/agent-security-extra.policy` (operator extension):: +Add your custom rules here. +This file is silently skipped if absent. +Its location can be overridden via `SOLR_SECURITY_AGENT_EXTRA_POLICY`. + +=== Variable Substitution + +Any java property will be expanded in policy entries, here are the most common. Environment variables are expanded if they adhere to the mapping convention `SOLR_FOO --> solr.foo`: + +[cols="1,2",options="header"] +|=== +|Variable|Resolved Value +|`${solr.home}`|Solr home directory +|`${solr.data.dir}`|Solr data directory +|`${solr.log.dir}`|Solr log directory +|`${solr.install.dir}`|Solr installation root (parent of `server/`) +|`${java.io.tmpdir}`|JVM temporary directory +|`${java.home}`|JDK installation directory +|`${solr.port}`|Solr HTTP port +|`${solr.zk.port}`|ZooKeeper port (defaults to `solr.port + 1000` for embedded ZK) +|=== + +=== Default Intra-Cluster Network Policy + +The bundled policy permits connections on `*:${solr.port}` and `*:${solr.zk.port}` from any host, so all cluster nodes can communicate without any operator action. +If your ZooKeeper runs on a non-standard port, set `-Dsolr.zk.port=<port>` at startup or configure it explicitly in the policy. + +=== Adding Custom Policy Entries + +To permit additional paths or endpoints, add entries to `agent-security-extra.policy`: + +[source,text] +---- +grant { + // permit a plugin that reads from an NFS mount + permission java.io.FilePermission "/mnt/nfs-data/-", "read"; +}; + +grant { + // permit outbound connection to an internal analytics service + permission java.net.SocketPermission "analytics.internal:443", "connect,resolve"; +}; +---- + +To override the extra-policy file location (useful for read-only installations or containers): + +[source,bash] +---- +SOLR_SECURITY_AGENT_EXTRA_POLICY=/etc/solr/custom-security.policy +---- + +== Diagnosing Violations + +Violations appear in the Solr log in this format: + +[source,text] +---- +SECURITY VIOLATION [FILE] target=/etc/passwd caller=com.example.Plugin mode=warn source=DEFAULT +---- + +Fields: + +* `[TYPE]` — `FILE`, `NETWORK`, `EXIT`, or `EXEC` +* `target` — the resource that was accessed or attempted +* `caller` — the class that triggered the access +* `mode` — `warn` or `enforce` +* `source` — `DEFAULT` (bundled policy) or `OPERATOR` (extra policy) + +Violation counts are also available in `/admin/metrics` under: + +* `solr.security.agent.violations.file` +* `solr.security.agent.violations.network` +* `solr.security.agent.violations.exit` +* `solr.security.agent.violations.exec` + +== Disabling the Agent + +To temporarily disable the agent for troubleshooting, set in `solr.in.sh` or `solr.in.cmd`: + +[source,bash] +---- +SOLR_SECURITY_AGENT_SKIP=true +---- + +[WARNING] +==== +Disabling the agent removes all runtime security controls. +Use this only for temporary troubleshooting of if you intend to provide similar protection on the operating system level. Review Comment: Typo: "of if you intend" should be "or if you intend". ########## solr/agent-sm/src/java/org/apache/solr/security/agent/SolrAgentEntryPoint.java: ########## @@ -0,0 +1,221 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ +package org.apache.solr.security.agent; + +import java.lang.instrument.Instrumentation; +import java.net.Socket; +import java.nio.channels.FileChannel; +import java.nio.channels.SocketChannel; +import java.nio.file.Path; +import java.nio.file.spi.FileSystemProvider; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.matcher.ElementMatchers; + +/** + * Java agent entry point for the Solr security agent. + * + * <p>The JVM invokes {@link #premain(String, Instrumentation)} before the application main method + * when the agent JAR is listed on the command line as {@code -javaagent:solr-agent-sm-*.jar}. The + * {@link #agentmain(String, Instrumentation)} method supports attach-based loading (not used by + * Solr's startup scripts but retained for tooling compatibility). + * + * <h2>Startup sequence</h2> + * + * <ol> + * <li>Locate and parse {@code agent-security.policy} (and the optional {@code + * agent-security-extra.policy}) via {@link PolicyLoader}. + * <li>Initialize the {@link AgentPolicy} singleton. + * <li>Register all four ByteBuddy interceptors with the JVM instrumentation API. + * <li>If policy loading fails and enforcement mode is {@code ENFORCE}, halt the JVM; in {@code + * WARN} mode, log the error and continue without protection. + * </ol> + * + * <h2>Bootstrap classloader</h2> + * + * The agent JAR is placed on {@code Boot-Class-Path} so all interceptor and policy classes are + * visible to the bootstrap classloader. SLF4J is intentionally absent from the fat JAR to avoid + * poisoning the SLF4J binding before Log4j2 is loaded. + */ +public final class SolrAgentEntryPoint { + + private SolrAgentEntryPoint() {} + + /** + * Called by the JVM before the application main class is loaded, when the agent JAR is specified + * via {@code -javaagent:}. + */ + public static void premain(String agentArgs, Instrumentation inst) { + bootAgent(inst); + } + + /** Called by the JVM when the agent is attached dynamically; delegates to {@link #premain}. */ + public static void agentmain(String agentArgs, Instrumentation inst) { + premain(agentArgs, inst); + } + + // --------------------------------------------------------------------------- + // Internal bootstrap logic + // --------------------------------------------------------------------------- + + @SuppressForbidden( + reason = + "System.err is the only safe output during premain; System.exit(1) is required to halt " + + "the JVM on fatal policy-load failure in enforce mode.") + private static void bootAgent(Instrumentation inst) { + // Locate the default policy file next to the agent JAR. + Path defaultPolicyPath = resolveDefaultPolicyPath(); + + AgentPolicy policy = null; + try { + PolicyLoader loader = new PolicyLoader(); + policy = loader.load(defaultPolicyPath); + } catch (IllegalStateException e) { + String modeStr = PolicyPropertyExpander.getPropertyOrEnv("solr.security.agent.mode"); + if (modeStr == null) modeStr = "warn"; + if ("enforce".equalsIgnoreCase(modeStr.trim())) { + System.err.println( + "[Solr SecurityAgent] FATAL: Cannot load security policy in enforce mode. " + + "Solr will not start. Error: " + + e.getMessage()); + System.exit(1); + } else { + System.err.println( + "[Solr SecurityAgent] WARNING: Cannot load security policy (" + + e.getMessage() + + "). Security controls are inactive."); + return; + } + } + + AgentPolicy.initialize(policy); + + try { + installInterceptors(inst); + } catch (Exception e) { + System.err.println("[Solr SecurityAgent] Failed to install interceptors: " + e); + } + + System.err.println( + "[Solr SecurityAgent] Security agent active — mode=" + + policy.enforcementMode() + + ", permitted paths=" + + policy.permittedPaths().size() + + ", permitted endpoints=" + + policy.permittedEndpoints().size()); + } + + private static final String[] FILE_INTERCEPTED_METHODS = { + "write", + "createFile", + "createDirectories", + "createLink", + "copy", + "move", + "newByteChannel", + "delete", + "deleteIfExists", + "read", + "open" + }; + + private static void installInterceptors(Instrumentation inst) { + // AgentBuilder configuration for JDK class instrumentation: + // - Implementation.Context.Disabled: required for REDEFINE so ByteBuddy does not try to + // add auxiliary types (forbidden when redefining already-loaded JDK classes). + // - InitializationStrategy.NoOp: skip static initializer injection (same reason). + // - RedefinitionStrategy.REDEFINITION: redefine already-loaded bootstrap classes (Files, + // etc.) + // in-place rather than scheduling a retransformation pass. + final ByteBuddy byteBuddy = + new ByteBuddy().with(Implementation.Context.Disabled.Factory.INSTANCE); + + new AgentBuilder.Default(byteBuddy) + .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE) + .with(AgentBuilder.RedefinitionStrategy.REDEFINITION) + .with(AgentBuilder.TypeStrategy.Default.REDEFINE) + // Override the default ignore filter so we can instrument bootstrap-loaded JDK classes + // in named modules (java.lang, java.nio, etc.). Without this, ByteBuddy 1.18.x silently + // skips those classes due to module-visibility checks in its default ignore rules. + // Also exclude ByteBuddy's own classes to prevent circular instrumentation at startup. + .ignore(ElementMatchers.isSynthetic().or(ElementMatchers.nameStartsWith("net.bytebuddy."))) + // Intercept FileSystemProvider subclasses, java.nio.file.Files, and FileChannel subtypes. + .type( + ElementMatchers.isSubTypeOf(FileSystemProvider.class) + .or(ElementMatchers.named("java.nio.file.Files")) + .or(ElementMatchers.isSubTypeOf(FileChannel.class))) + .transform( + (builder, type, classLoader, module, domain) -> + builder.visit( + Advice.to(FileInterceptor.class) + .on( + ElementMatchers.namedOneOf(FILE_INTERCEPTED_METHODS) + .and(ElementMatchers.not(ElementMatchers.isAbstract()))))) + // Intercept SocketChannel / Socket outbound connections → SocketChannelInterceptor + .type( + ElementMatchers.isSubTypeOf(SocketChannel.class) + .or(ElementMatchers.isSubTypeOf(Socket.class))) + .transform( + (builder, type, classLoader, module, domain) -> + builder.visit( + Advice.to(SocketChannelInterceptor.class) + .on( + ElementMatchers.named("connect") + .and(ElementMatchers.not(ElementMatchers.isAbstract()))))) + // Intercept System.exit(int) → SystemExitInterceptor + .type(ElementMatchers.is(System.class)) + .transform( + (builder, type, classLoader, module, domain) -> + builder.visit( + Advice.to(SystemExitInterceptor.class).on(ElementMatchers.named("exit")))) + // Intercept Runtime.halt(int) → RuntimeHaltInterceptor + .type(ElementMatchers.is(Runtime.class)) + .transform( + (builder, type, classLoader, module, domain) -> + builder.visit( + Advice.to(RuntimeHaltInterceptor.class).on(ElementMatchers.named("halt")))) + // Intercept ProcessBuilder.start() → ProcessExecInterceptor + .type(ElementMatchers.is(ProcessBuilder.class)) + .transform( + (builder, type, classLoader, module, domain) -> + builder.visit( + Advice.to(ProcessExecInterceptor.class).on(ElementMatchers.named("start")))) + // Intercept Runtime.exec(String[]) → ProcessExecInterceptor + .type(ElementMatchers.is(Runtime.class)) + .transform( + (builder, type, classLoader, module, domain) -> + builder.visit( + Advice.to(ProcessExecInterceptor.class).on(ElementMatchers.named("exec")))) Review Comment: `AgentBuilder` registers two separate `.type(ElementMatchers.is(Runtime.class))` chains — one for `halt` and one for `exec`. With ByteBuddy's fluent builder, the second `.type(...)` starts a fresh top-level transformation rather than adding to the previous one, but more importantly applying two redefinitions to the same JDK class (`java.lang.Runtime`) via separate transformation chains can yield surprising interaction (only one set of advice applied, or duplicate work depending on REDEFINITION semantics). Consider combining the `halt` and `exec` matchers into a single `.type(ElementMatchers.is(Runtime.class))` clause whose transform installs both advices, or verify with an integration test that both interceptors fire when both methods are called in the same JVM. ########## solr/solr-ref-guide/modules/deployment-guide/pages/security-agent.adoc: ########## @@ -0,0 +1,166 @@ += Security Agent +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF 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. + +Solr includes a Java agent (`solr-agent-sm`) that aims to fill the gaps left by the removal of Java Security Manager (JSM) in JDK 24. +The agent intercepts file access, network connections, `System.exit()`, and process spawning at the bytecode level, enforcing a configurable policy. + +== Protections Active by Default + +When the agent JAR is present, the startup scripts automatically activate it. +The following protections apply to all code running inside the Solr JVM: + +* **File access** — reads and writes are restricted to permitted directories (Solr home, data dir, log dir, temp dir). +* **Outbound network** — connections are restricted to loopback addresses and intra-cluster ports. +* **`System.exit()` / `Runtime.halt()`** — only approved caller classes may shut down the JVM. +* **Process spawning** — `ProcessBuilder.start()` and `Runtime.exec()` are blocked by default. + +== Enforcement Modes + +The agent operates in two modes, controlled by the `SOLR_SECURITY_AGENT_MODE` environment variable: + +`warn` (default):: +Violations are logged at `WARN` level and counted in `/admin/metrics`, but the operation is allowed to continue. +This is the safe default for existing deployments — operators can observe violations without risk. + +`enforce`:: +Violations throw a `SecurityException`, blocking the operation. +Enable this mode once you have verified that your deployment produces no unexpected violations in `warn` mode. + +Set the mode in `solr.in.sh` or `solr.in.cmd`: + +[source,bash] +---- +SOLR_SECURITY_AGENT_MODE=enforce +---- + +== Policy File Format + +Solr uses JDK-style `.policy` files with Solr-specific variable substitution. +Two files are loaded: + +`server/etc/agent-security.policy` (default):: +The bundled production policy. +Do not edit this file — it will be overwritten on upgrade. + +`server/etc/agent-security-extra.policy` (operator extension):: +Add your custom rules here. +This file is silently skipped if absent. +Its location can be overridden via `SOLR_SECURITY_AGENT_EXTRA_POLICY`. + +=== Variable Substitution + +Any java property will be expanded in policy entries, here are the most common. Environment variables are expanded if they adhere to the mapping convention `SOLR_FOO --> solr.foo`: + +[cols="1,2",options="header"] +|=== +|Variable|Resolved Value +|`${solr.home}`|Solr home directory +|`${solr.data.dir}`|Solr data directory +|`${solr.log.dir}`|Solr log directory +|`${solr.install.dir}`|Solr installation root (parent of `server/`) +|`${java.io.tmpdir}`|JVM temporary directory +|`${java.home}`|JDK installation directory +|`${solr.port}`|Solr HTTP port +|`${solr.zk.port}`|ZooKeeper port (defaults to `solr.port + 1000` for embedded ZK) +|=== + +=== Default Intra-Cluster Network Policy + +The bundled policy permits connections on `*:${solr.port}` and `*:${solr.zk.port}` from any host, so all cluster nodes can communicate without any operator action. +If your ZooKeeper runs on a non-standard port, set `-Dsolr.zk.port=<port>` at startup or configure it explicitly in the policy. Review Comment: The variable names documented here do not match the names actually used in the bundled `agent-security.policy` and recognised by `PolicyPropertyExpander`. The policy file uses `${solr.solr.home}`, `${solr.data.home}`, `${solr.logs.dir}`, `${solr.port.listen}` (with special handling for `solr.zk.port`, `solr.install.symDir`, and `solr.data.home`), not `${solr.home}`, `${solr.data.dir}`, `${solr.log.dir}`, `${solr.port}`. Operators following this table verbatim will see "Unresolved policy variable" startup failures. Please align the table (and the example `${solr.port}` reference in the "Default Intra-Cluster Network Policy" section) with the actual property names. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
