-
Notifications
You must be signed in to change notification settings - Fork 828
SOLR-17767: Add a Java Agent (SIP-24) #4471
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
janhoy
wants to merge
66
commits into
apache:main
Choose a base branch
from
janhoy:15868-java-agent
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
66 commits
Select commit
Hold shift + click to select a range
389bb68
SOLR-17767: Add a Java Agent
janhoy f25d06f
Delete speckit folder
janhoy 7d550cb
SOLR-17767: Wire agent JAR into distribution and fix metrics registra…
janhoy ae9fb97
Align interceptors with reference impl from Opensearch
janhoy 25519b6
SOLR-17767: Pre-compile BATS violation programs; add tests 5-7 for ex…
janhoy 3dc31c0
SOLR-17767: Use System.exit(123) sentinel in ExitViolation; assert st…
janhoy 8297df0
Fix exit bats test
janhoy 3d716f1
Merge branch 'main' into 15868-java-agent
janhoy 69499ca
Lockfiles
janhoy 0afc2ee
Fix changelog
janhoy dcee23a
SOLR-17767: Address code review findings — security correctness, chai…
janhoy 2fbd585
SOLR-17767: Prefix security agent metric names with solr.* per Solr c…
janhoy 8a906ba
Other fixes
janhoy 067ca3b
Remove untrue statement in solr-properties.adoc
janhoy 04624e5
SOLR-17767: Fix build issues — disable annotation processing for test…
janhoy 6e81113
Fix error-prone UnnecessarilyFullyQualified warnings in SocketChannel…
janhoy f63ab08
Fix error-prone UnnecessarilyFullyQualified warnings in SolrAgentEntr…
janhoy 8fa1276
Fix precommit and wrong docs
janhoy cfe0060
Fix error-prone UnnecessarilyFullyQualified warning in ProcessExecInt…
janhoy 761e951
Fix all remaining error-prone UnnecessarilyFullyQualified warnings in…
janhoy 0b97ae9
SOLR-17767: Policy and Docker fixes (symDir, proc/sys, cleanup user)
janhoy c33ad1e
Fix UnnecessarilyFullyQualified: import Field in AgentViolationBridge
janhoy 31d62cd
args[1] bug
janhoy e3f3505
Fix wrong JIRA in changelog file
janhoy 1601f31
Fix security-agent.adoc: correct policy variable names in docs table
janhoy 23f08e2
Fix agent-security-extra.policy: correct supported variable names in …
janhoy ab04af6
Fix security-agent.adoc: clarify env-var mapping direction in variabl…
janhoy e762056
Fix typo in security-agent.adoc: "of if" → "or if"
janhoy ce1bbac
Combine Runtime.class transform chains into one to avoid duplicate re…
janhoy 754c948
Use setReporter() method instead of reflective field write in AgentVi…
janhoy edb5ece
Add normalize() to FileInterceptor to prevent path-traversal bypass
janhoy 980d2c7
Remove dead 'read' interceptor branch: no java.nio.file.Files.read() …
janhoy 971a054
Use Path.startsWith(Path) in PermittedPath to fix Windows separator h…
janhoy 58a5c59
Preserve line number and cause in PolicyFileParser.expand() exception
janhoy 87fdd98
Fix test to actually verify that malformed extra policy throws ISE
janhoy 1d28b58
Throw ISE instead of silently logging to stderr when observableLongCo…
janhoy 9998924
Use null (bootstrap) classloader consistently for agent class lookups
janhoy 838903d
Catch Exception (not just ISE) in bootAgent to prevent NPE on unexpec…
janhoy c4b9569
Fail-fast on garbage policy content; allow empty/comment-only operato…
janhoy ec0f317
Use single labeled OTel counter for violation metrics instead of four…
janhoy e62e5ce
Move OTel metric registration to AgentViolationMetrics in solr:core
janhoy cf90b0c
Simplify javadoc and inline comments in agent-sm classes
janhoy 860251b
Fix FileInterceptor enforcement bugs found in self code review
janhoy 9f581a2
Move test-only check helpers out of production interceptors
janhoy 1689967
Fix BATS metric assertion to not depend on OTel label order
janhoy 43cdf13
Treat unknown OpenOption argument type as mutating instead of throwing
janhoy 1ee6cd7
Fix AgentPolicy Javadoc: class-name matching is prefix/exact, not regex
janhoy 95eeed2
Fix isCallerFromCodeBase: normalize path separators for Windows
janhoy 5e33e83
Fix FileInterceptor: resolve symlinks via toRealPath() in advice
janhoy 1db48be
Drop unused @Origin Method param from SocketChannelInterceptor.intercept
janhoy 69774c5
Fix codeBase-scoped exitVM/exec grants being silently no-ops
janhoy 2a92315
Improve agents instruction around code comments, which tend to be too…
janhoy 5122eab
Fix PermittedPath.permits: use Set membership instead of substring match
janhoy 05db202
Fix matchesEndpoint: strip brackets from IPv6 policy entries
janhoy a1bab4f
Fix FileInterceptor: catch SecurityException and normalize policy paths
janhoy 3533c70
Fix testDocker: use doNotTrackState to avoid AccessDeniedException
janhoy 7d51358
Fix FileInterceptor: make resolveRealPath and enforceFileAccess public
janhoy a5cad1a
Trim comments and Javadoc in agent-sm: remove obvious and redundant text
janhoy 1f7630c
precommit
janhoy 8a3b76f
Fix SocketChannelInterceptor: make enforceNetworkAccess public
janhoy 0d51afd
Document ByteBuddy advice visibility rule in package-info.java
janhoy f28ba3f
Tidy
janhoy 7a89fe8
Move agent wiring from CoreContainer to CoreContainerProvider
janhoy df1322e
Move SLF4J warning from Javadoc to build.gradle dependency comment
janhoy 94d05e8
Convert PermittedEndpoint, ApprovedCallSite, Raw*Permission to records
janhoy 8578fcd
Bump bin-solr-test workflow timeout from 40 to 70 minutes
janhoy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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-17767 | ||
| url: https://issues.apache.org/jira/browse/SOLR-17767 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| = Security Agent Developer Guide | ||
| // 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. | ||
|
|
||
| This document is for Solr developers who need to work with or extend the `solr/agent-sm` security agent. | ||
|
|
||
| == Module Overview | ||
|
|
||
| `solr/agent-sm` is a standalone Gradle subproject that produces a Java agent JAR. | ||
| The agent must be a standalone JAR (not a Solr module) because it is loaded by the JVM before Solr initializes. | ||
| It intercepts bytecode using ByteBuddy and enforces a policy derived from JDK-style `.policy` files. | ||
|
|
||
| Key classes: | ||
|
|
||
| * `SolrAgentEntryPoint` — `premain()` and `agentmain()` entry points; wires all ByteBuddy interceptors | ||
| * `PolicyLoader` — parses `.policy` files and performs variable substitution | ||
| * `AgentPolicy` — immutable singleton holding the merged policy | ||
| * `StackCallerClassChainExtractor` — `StackWalker`-based call chain analysis (virtual-thread safe) | ||
| * `SecurityViolationLogger` — emits structured SLF4J log entries for violations | ||
| * `ViolationMetricsReporter` — `LongAdder` counters per violation type; deferred registration with `SolrMetricManager` | ||
|
|
||
| == Adding an Approved Exit Caller | ||
|
|
||
| To allow a new class to call `System.exit()` or `Runtime.halt()`, add it to the approved callers list in `AgentPolicy`. | ||
| The list is populated from the policy file using `java.lang.RuntimePermission "exitVM"` entries. | ||
| Add an entry to `agent-security.policy`: | ||
|
|
||
| [source,text] | ||
| ---- | ||
| grant codeBase "file:${solr.install.dir}/..." { | ||
| permission java.lang.RuntimePermission "exitVM"; | ||
| }; | ||
| ---- | ||
|
|
||
| Alternatively, add the class name to the default approved list in `AgentPolicy` if it is always a trusted Solr internal class. | ||
|
|
||
| == Adding an Approved Exec Caller | ||
|
|
||
| The default exec allow-list is empty — `ProcessBuilder.start()` and `Runtime.exec()` are blocked for all callers. | ||
| To permit a specific class, add a `java.lang.RuntimePermission "exec"` entry to the policy or the `AgentPolicy` defaults. | ||
| Document the reason in a code comment. | ||
|
|
||
| == Adding a Trusted Filesystem Scheme | ||
|
|
||
| Paths on certain virtual filesystems (e.g., `jrt:/` for JDK resources) are excluded from file policy checks. | ||
| To add a trusted scheme, extend the `trustedFileSystems` set in `AgentPolicy`. | ||
| Trusted schemes bypass all file access checks. | ||
|
|
||
| == Deferred Metrics Registration | ||
|
|
||
| The agent starts before Solr and begins counting violations immediately via `ViolationMetricsReporter`. | ||
| Once `CoreContainer` initializes, it calls `ViolationMetricsReporter.registerWithSolrMetrics()` via reflection (so `solr:core` has no compile-time dependency on `solr:agent-sm`). | ||
| Counts accumulated before registration are preserved in the `LongAdder` instances and become visible in metrics after registration. | ||
|
|
||
| If you change the `registerWithSolrMetrics` method signature, update the reflective call in `CoreContainer.java` to match. | ||
|
|
||
| == Writing Tests Alongside the Agent | ||
|
|
||
| Tests for `solr:agent-sm` extend `SolrTestCase` (not `SolrTestCaseJ4`). | ||
|
|
||
| The agent-sm test suite runs with `SOLR_SECURITY_AGENT_MODE=enforce` so that blocking behaviour is tested directly. | ||
| The broader Solr test suite runs in `warn` mode — violations are logged but do not fail tests. | ||
|
|
||
| To run only the agent-sm tests: | ||
|
|
||
| [source,bash] | ||
| ---- | ||
| ./gradlew :solr:agent-sm:test | ||
| ---- | ||
|
|
||
| To reproduce a flaky test with a specific randomization seed: | ||
|
|
||
| [source,bash] | ||
| ---- | ||
| ./gradlew :solr:agent-sm:test --tests "org.apache.solr.security.agent.SolrAgentIntegrationTest" -Ptests.seed=HEXSEEDHERE | ||
| ---- | ||
|
|
||
| == Flipping the Broader Test Suite to Enforce Mode | ||
|
|
||
| The broader Solr test suite currently runs in warn mode. | ||
| Before flipping to enforce mode: | ||
|
|
||
| 1. Run the full suite in warn mode and collect all `SECURITY VIOLATION` log entries. | ||
| 2. Triage each violation — legitimate Solr operations need policy entries; plugin violations need plugin fixes or policy entries. | ||
| 3. Update `agent-security.policy` or test-specific extra-policy files as needed. | ||
|
|
||
| == No Compile-Time Dependency from solr:core on solr:agent-sm | ||
|
|
||
| `solr:core` must not declare a compile-time dependency on `solr:agent-sm`. | ||
| The agent JAR is loaded by the JVM bootstrap classloader before application classloaders exist. | ||
| All interaction from `solr:core` must go through reflection (`Class.forName` with bootstrap classloader). | ||
| See `CoreContainer.java` for the pattern. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| /* | ||
| * 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. | ||
| */ | ||
|
|
||
| apply plugin: 'java-library' | ||
|
|
||
| description = 'Apache Solr Security Agent' | ||
|
|
||
| dependencies { | ||
| // ByteBuddy references SpotBugs annotations in its class files; needed to suppress -Xlint:classfile | ||
| // Not included in the release so exclude from license/checksum checks. | ||
| compileOnly libs.spotbugs.annotations | ||
|
|
||
| // !! DO NOT ADD SLF4J OR OTHER LOGGING JARS !! | ||
| // They are on Boot-Class-Path and initialise the logging binding before Log4j2 loads, | ||
| // permanently caching a NOP logger and preventing solr.log from being written. | ||
|
|
||
| // ByteBuddy for bytecode instrumentation - bundled into the fat agent JAR | ||
| implementation libs.bytebuddy | ||
| // byte-buddy-agent is the Java agent JAR itself; not directly imported in compiled classes | ||
| // but required at runtime for agent loading and bundled into the fat JAR. | ||
| implementation libs.bytebuddy.agent | ||
| permitUnusedDeclared libs.bytebuddy.agent | ||
|
|
||
|
|
||
| // Test dependencies - NOT bundled | ||
| testImplementation project(':solr:test-framework') | ||
| testImplementation project(':solr:core') | ||
| testImplementation libs.apache.lucene.testframework | ||
| testImplementation libs.junit.junit | ||
| testImplementation libs.carrotsearch.randomizedtesting.runner | ||
| testImplementation libs.hamcrest.hamcrest | ||
| // These are used indirectly via SolrTestCase (test-framework) and randomized runner base classes; | ||
| // the static analyzer does not see direct usage in agent-sm test sources. | ||
| permitTestUnusedDeclared project(':solr:core') | ||
| permitTestUnusedDeclared libs.apache.lucene.testframework | ||
| permitTestUnusedDeclared libs.carrotsearch.randomizedtesting.runner | ||
| permitTestUnusedDeclared libs.hamcrest.hamcrest | ||
| } | ||
|
|
||
| // Build a self-contained fat JAR so the agent can run before Solr's classpath is set up. | ||
| // ByteBuddy is bundled; Solr classes are NOT included. | ||
| // | ||
| // Build a fat JAR so all agent classes (and ByteBuddy) are available on Boot-Class-Path before | ||
| // the application main class loads. SLF4J is intentionally excluded from this JAR's dependencies: | ||
| // placing SLF4J on Boot-Class-Path would initialise it before Log4j2 is loaded, permanently | ||
| // caching a NOP logger and preventing solr.log from being created. | ||
| jar { | ||
| manifest { | ||
| attributes( | ||
| 'Premain-Class': 'org.apache.solr.security.agent.SolrAgentEntryPoint', | ||
| 'Agent-Class': 'org.apache.solr.security.agent.SolrAgentEntryPoint', | ||
| 'Can-Redefine-Classes': 'true', | ||
| 'Can-Retransform-Classes': 'true', | ||
| 'Boot-Class-Path': archiveFileName | ||
| ) | ||
| } | ||
|
|
||
| // Bundle runtime dependencies (ByteBuddy) into the fat agent JAR. | ||
| // Test dependencies are excluded because they are not on runtimeClasspath. | ||
| from { | ||
| configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } | ||
| } | ||
| duplicatesStrategy = DuplicatesStrategy.EXCLUDE | ||
| } | ||
|
|
||
| // Copy the built agent JAR to server/lib/ext/ so startup scripts can find it at | ||
| // ${SOLR_SERVER_DIR}/lib/ext/solr-agent-sm-*.jar, and so the dev distribution works. | ||
| def agentJarTarget = file("${rootDir}/solr/server/lib/ext") | ||
|
|
||
| task copyAgentJar(type: Copy, dependsOn: jar) { | ||
| description = 'Copies the agent fat JAR to solr/server/lib/ext/ for startup-script detection' | ||
| group = 'build' | ||
| from jar.outputs | ||
| into agentJarTarget | ||
| // Keep only the current version; remove stale versions on each build. | ||
| doFirst { | ||
| delete fileTree(agentJarTarget) { include 'solr-agent-sm-*.jar' } | ||
| } | ||
| } | ||
|
|
||
| // spotbugs-annotations is compileOnly and not included in the release. | ||
| configurations.jarValidation { | ||
| exclude group: "com.github.spotbugs", module: "spotbugs-annotations" | ||
| } | ||
|
|
||
| assemble.dependsOn copyAgentJar | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Test programs — standalone Java programs compiled for BATS integration tests. | ||
| // These have no Solr/ByteBuddy dependencies; pure JDK APIs only. | ||
| // --------------------------------------------------------------------------- | ||
| sourceSets { | ||
| testPrograms { | ||
| java.srcDirs = ['src/test-programs/java'] | ||
| } | ||
| } | ||
|
|
||
| // These are tiny standalone programs with no annotation-processor needs. | ||
| // Disabling annotation processing prevents the auto-created testProgramsAnnotationProcessor | ||
| // configuration from pulling in error-prone and friends, which are not in the lock file. | ||
| tasks.named('compileTestProgramsJava') { | ||
| options.annotationProcessorPath = files() | ||
| } | ||
|
|
||
| // Error-prone must also be disabled for compileTestProgramsJava: its Gradle plugin uses | ||
| // tasks.withType(JavaCompile) and would inject -Xplugin:ErrorProne, but the | ||
| // testProgramsAnnotationProcessor configuration has no errorprone JAR on it. | ||
| plugins.withId(libs.plugins.ltgt.errorprone.get().pluginId) { | ||
| tasks.named('compileTestProgramsJava') { | ||
| options.errorprone.enabled = false | ||
| } | ||
| } | ||
|
|
||
| task testProgramsJar(type: Jar) { | ||
| description = 'JAR of standalone violation programs used by BATS integration tests' | ||
| group = 'build' | ||
| archiveClassifier = 'test-programs' | ||
| from sourceSets.testPrograms.output | ||
| } | ||
|
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.