mirror of
https://github.com/corda/corda.git
synced 2025-03-25 13:27:58 +00:00
Make command line cli stuff self instrument, and remove manual adding of agent in node capsule.
This commit is contained in:
parent
8eae615170
commit
9ec5cfcecb
@ -9,7 +9,6 @@ apply plugin: 'com.jfrog.artifactory'
|
||||
description 'Corda standalone node'
|
||||
|
||||
evaluationDependsOn(':node')
|
||||
evaluationDependsOn(':tools:aegis4j')
|
||||
|
||||
configurations {
|
||||
runtimeArtifacts.extendsFrom runtimeClasspath
|
||||
@ -38,11 +37,9 @@ capsule {
|
||||
}
|
||||
|
||||
def nodeProject = project(':node')
|
||||
def aegisProject = project(':tools:aegis4j')
|
||||
|
||||
task buildCordaJAR(type: FatCapsule, dependsOn: [
|
||||
nodeProject.tasks.named('jar'),
|
||||
aegisProject.tasks.named('shadowJar'),
|
||||
project(':core-deterministic').tasks.named('assemble'),
|
||||
project(':serialization-deterministic').tasks.named('assemble')
|
||||
]) {
|
||||
@ -54,7 +51,6 @@ task buildCordaJAR(type: FatCapsule, dependsOn: [
|
||||
applicationSource = files(
|
||||
nodeProject.configurations.runtimeClasspath,
|
||||
nodeProject.tasks.jar,
|
||||
aegisProject.tasks.named('shadowJar'),
|
||||
nodeProject.buildDir.toString() + '/resources/main/corda-reference.conf',
|
||||
nodeProject.buildDir.toString() + '/resources/main/mods.properties',
|
||||
"$rootDir/config/dev/log4j2.xml",
|
||||
@ -104,7 +100,6 @@ task buildCordaJAR(type: FatCapsule, dependsOn: [
|
||||
def quasarExcludeExpression = "x(antlr**;bftsmart**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;kotlin**;net.corda.djvm**;djvm**;net.bytebuddy**;net.i2p**;org.apache**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**;com.lmax**;picocli**;liquibase**;com.github.benmanes**;org.json**;org.postgresql**;nonapi.io.github.classgraph**)"
|
||||
def quasarClassLoaderExclusion = "l(net.corda.djvm.**;net.corda.core.serialization.internal.**)"
|
||||
javaAgents = quasar_classifier ? ["quasar-core-${quasar_version}-${quasar_classifier}.jar=${quasarExcludeExpression}${quasarClassLoaderExclusion}"] : ["quasar-core-${quasar_version}.jar=${quasarExcludeExpression}${quasarClassLoaderExclusion}"]
|
||||
javaAgents += "aegis4j-1.2.jar=path=mods.properties"
|
||||
systemProperties['visualvm.display.name'] = 'Corda'
|
||||
if (JavaVersion.current() == JavaVersion.VERSION_1_8) {
|
||||
minJavaVersion = '1.8.0'
|
||||
|
@ -36,41 +36,45 @@ public final class AegisAgent {
|
||||
*/
|
||||
public static void premain(String args, Instrumentation instr) {
|
||||
instrumentation = instr;
|
||||
Path path = null;
|
||||
boolean started = false;
|
||||
|
||||
if (args != null) {
|
||||
if (args.trim().equalsIgnoreCase("dynamic")) return;
|
||||
for (String arg : args.split(";")) {
|
||||
if (started) throw new IllegalArgumentException("Aegis4j ERROR: parameter ordering means patching already started");
|
||||
String normalisedaArg = arg.trim().toLowerCase();
|
||||
if (normalisedaArg.isEmpty() || normalisedaArg.startsWith("block=") || normalisedaArg.startsWith("unblock=")) {
|
||||
try {
|
||||
Patcher.start(instr, toBlockList(normalisedaArg, path), getModificationsInputStream(path));
|
||||
try {
|
||||
boolean started = false;
|
||||
Properties props = null;
|
||||
if (args != null) {
|
||||
if (args.trim().equalsIgnoreCase("dynamic")) return;
|
||||
for (String arg : args.split(";")) {
|
||||
if (started) throw new IllegalArgumentException("Aegis4j ERROR: parameter ordering means patching already started");
|
||||
String normalisedaArg = arg.trim().toLowerCase();
|
||||
if (normalisedaArg.isEmpty() || normalisedaArg.startsWith("block=") || normalisedaArg.startsWith("unblock=")) {
|
||||
Patcher.start(instr, toBlockList(normalisedaArg, props), getModificationsProperties(props));
|
||||
started = true;
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Aegis4j ERROR: Unable to process mods file", e);
|
||||
}
|
||||
} else if (normalisedaArg.startsWith("path=")) {
|
||||
String pathString = arg.trim().substring(5);
|
||||
if (pathString.startsWith(File.pathSeparator)) {
|
||||
path = Paths.get(pathString);
|
||||
} else if (normalisedaArg.startsWith("path=")) {
|
||||
String pathString = arg.trim().substring(5);
|
||||
Path path;
|
||||
if (pathString.startsWith(File.pathSeparator)) {
|
||||
path = Paths.get(pathString);
|
||||
} else {
|
||||
Path agentJar = Paths.get(AegisAgent.class.getProtectionDomain().getCodeSource().getLocation().getPath());
|
||||
path = agentJar.resolveSibling(pathString);
|
||||
}
|
||||
InputStream in = path.toUri().toURL().openStream();
|
||||
props = readPropertiesFromStream(in);
|
||||
System.out.println("Aegis4j patching from " + path + " mods file");
|
||||
} else if (normalisedaArg.startsWith("resource=")) {
|
||||
String pathString = arg.trim().substring(9);
|
||||
InputStream in = ClassLoader.getSystemResourceAsStream(pathString);
|
||||
if (in == null) throw new IOException("Unable to load mods resource " + pathString);
|
||||
props = readPropertiesFromStream(in);
|
||||
System.out.println("Aegis4j patching from " + pathString + " mods resource");
|
||||
} else {
|
||||
Path agentJar = Paths.get(AegisAgent.class.getProtectionDomain().getCodeSource().getLocation().getPath());
|
||||
path = agentJar.resolveSibling(pathString);
|
||||
throw new IllegalArgumentException("Aegis4j ERROR: unrecognised parameters " + arg);
|
||||
}
|
||||
System.out.println("Aegis4j patching from " + path + " mods file");
|
||||
} else {
|
||||
throw new IllegalArgumentException("Aegis4j ERROR: unrecognised parameters " + arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!started) {
|
||||
try {
|
||||
Patcher.start(instr, toBlockList("", path), getModificationsInputStream(path));
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Aegis4j ERROR: Unable to process mods file", e);
|
||||
if (!started) {
|
||||
Patcher.start(instr, toBlockList("", props), getModificationsProperties(props));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Aegis4j ERROR: Unable to process mods file", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,8 +99,8 @@ public final class AegisAgent {
|
||||
* @param args agent arguments
|
||||
* @return the block list derived from the agent arguments
|
||||
*/
|
||||
protected static Set<String> toBlockList(String args, Path path) {
|
||||
Set<String> all = loadFeaturesFromModifications(path);
|
||||
protected static Set<String> toBlockList(String args, Properties override) throws IOException {
|
||||
Set<String> all = loadFeaturesFromModifications(override);
|
||||
if (args == null || args.trim().isEmpty()) {
|
||||
// no arguments provided by user
|
||||
return all;
|
||||
@ -150,13 +154,8 @@ public final class AegisAgent {
|
||||
return Collections.unmodifiableSet(features);
|
||||
}
|
||||
|
||||
private static Set<String> loadFeaturesFromModifications(Path path) {
|
||||
Properties props = new Properties();
|
||||
try {
|
||||
props.load(getModificationsInputStream(path));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
private static Set<String> loadFeaturesFromModifications(Properties override) throws IOException {
|
||||
Properties props = getModificationsProperties(override);
|
||||
Set<String> features = new HashSet<String>();
|
||||
for (String key : props.stringPropertyNames()) {
|
||||
int first = key.indexOf('.');
|
||||
@ -166,8 +165,19 @@ public final class AegisAgent {
|
||||
return Collections.unmodifiableSet(features);
|
||||
}
|
||||
|
||||
public static InputStream getModificationsInputStream(Path path) throws IOException {
|
||||
if(path != null) return path.toUri().toURL().openStream();
|
||||
return AegisAgent.class.getResourceAsStream("mods.properties");
|
||||
public static Properties getModificationsProperties(Properties props) throws IOException {
|
||||
if (props != null) return props;
|
||||
return readPropertiesFromStream(Patcher.class.getResourceAsStream("mods.properties"));
|
||||
}
|
||||
|
||||
public static Properties readPropertiesFromStream(InputStream stream) throws IOException {
|
||||
if (stream == null) return null;
|
||||
try {
|
||||
Properties props = new Properties();
|
||||
props.load(stream);
|
||||
return props;
|
||||
} finally {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ import javassist.LoaderClassPath;
|
||||
import javassist.NotFoundException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.lang.instrument.UnmodifiableClassException;
|
||||
@ -40,8 +39,8 @@ public final class Patcher implements ClassFileTransformer {
|
||||
*
|
||||
* @param block the features to block
|
||||
*/
|
||||
public Patcher(Set<String> block, InputStream inputStream) {
|
||||
modifications = loadModifications(inputStream, block);
|
||||
public Patcher(Set<String> block, Properties props) {
|
||||
modifications = loadModifications(props, block);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,10 +49,10 @@ public final class Patcher implements ClassFileTransformer {
|
||||
* @param instr the instrumentation instance to add a new patcher to
|
||||
* @param block the features to block
|
||||
*/
|
||||
public static void start(Instrumentation instr, Set<String> block, InputStream inputStream) {
|
||||
public static void start(Instrumentation instr, Set<String> block, Properties props) {
|
||||
System.out.println("Aegis4j patching starting");
|
||||
if (patcher != null) instr.removeTransformer(patcher);
|
||||
patcher = new Patcher(block, inputStream);
|
||||
patcher = new Patcher(block, props);
|
||||
instr.addTransformer(patcher, true);
|
||||
|
||||
for (String className : patcher.modifications.keySet()) {
|
||||
@ -112,22 +111,15 @@ public final class Patcher implements ClassFileTransformer {
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, List<Modification>> loadModifications(InputStream inputStream, Set<String> block) {
|
||||
Properties props = new Properties();
|
||||
try {
|
||||
props.load(inputStream);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
private static Map<String, List<Modification>> loadModifications(Properties props, Set<String> block) {
|
||||
List<Modification> mods = new ArrayList<>();
|
||||
for (String key : props.stringPropertyNames()) {
|
||||
int first = key.indexOf('.');
|
||||
int last = key.lastIndexOf('.');
|
||||
String feature = key.substring(0, first).toLowerCase();
|
||||
//if (block.contains(feature)) {
|
||||
String className = key.substring(first + 1, last);
|
||||
String methodName = key.substring(last + 1);
|
||||
String className = key.substring(first + 1, last);
|
||||
String methodName = key.substring(last + 1);
|
||||
String newBody = props.getProperty(key);
|
||||
Modification mod = new Modification(className, methodName, newBody, block.contains(feature));
|
||||
mods.add(mod);
|
||||
|
@ -51,7 +51,7 @@ public class AegisAgentTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseBlockList() {
|
||||
public void testParseBlockList() throws IOException {
|
||||
assertEquals(TestUtils.setOf("jndi", "rmi", "process", "httpserver", "serialization", "scripting"), toBlockList("", null));
|
||||
assertEquals(TestUtils.setOf("jndi", "rmi", "process", "httpserver", "serialization", "scripting"), toBlockList(" ", null));
|
||||
assertEquals(TestUtils.setOf("jndi", "rmi", "process", "httpserver", "scripting"), toBlockList("unblock=serialization", null));
|
||||
|
@ -8,7 +8,7 @@ description 'CLI Utilities'
|
||||
dependencies {
|
||||
compile project(":core")
|
||||
compile project(":common-logging")
|
||||
|
||||
|
||||
compile "info.picocli:picocli:$picocli_version"
|
||||
compile "commons-io:commons-io:$commons_io_version"
|
||||
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
|
||||
@ -17,6 +17,12 @@ dependencies {
|
||||
compile "org.fusesource.jansi:jansi:$jansi_version"
|
||||
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
|
||||
// Need the JDK
|
||||
compile files(org.gradle.internal.jvm.Jvm.current().toolsJar)
|
||||
|
||||
// Aegis4J
|
||||
compile project(':tools:aegis4j')
|
||||
}
|
||||
|
||||
jar {
|
||||
|
@ -0,0 +1,48 @@
|
||||
package net.corda.cliutils
|
||||
|
||||
import com.sun.tools.attach.VirtualMachine
|
||||
import net.gredler.aegis4j.AegisAgent
|
||||
import java.lang.management.ManagementFactory
|
||||
import java.nio.file.Files
|
||||
import java.util.jar.JarEntry
|
||||
import java.util.jar.JarOutputStream
|
||||
import java.util.jar.Manifest
|
||||
|
||||
object AttachAegis4j {
|
||||
private fun toBytes(clazz: Class<*>): ByteArray? {
|
||||
val path = clazz.name.replace('.', '/') + ".class"
|
||||
val stream = clazz.classLoader.getResourceAsStream(path)
|
||||
return stream.readBytes()
|
||||
}
|
||||
|
||||
private fun createAgentJar(): String {
|
||||
val clazz: Class<*> = AegisAgent::class.java
|
||||
val jar = Files.createTempFile("aegis4j-", ".jar")
|
||||
jar.toFile().deleteOnExit()
|
||||
val manifest = Manifest()
|
||||
manifest.mainAttributes.putValue("Manifest-Version", "1.0")
|
||||
manifest.mainAttributes.putValue("Main-Class", clazz.name)
|
||||
manifest.mainAttributes.putValue("Agent-Class", clazz.name)
|
||||
manifest.mainAttributes.putValue("Premain-Class", clazz.name)
|
||||
manifest.mainAttributes.putValue("Can-Redefine-Classes", "true")
|
||||
manifest.mainAttributes.putValue("Can-Retransform-Classes", "true")
|
||||
manifest.mainAttributes.putValue("Can-Set-Native-Method-Prefix", "false")
|
||||
Files.newOutputStream(jar).use { os ->
|
||||
JarOutputStream(os, manifest).use { jos ->
|
||||
val entry = JarEntry(clazz.name.replace('.', '/') + ".class")
|
||||
entry.time = System.currentTimeMillis()
|
||||
jos.putNextEntry(entry)
|
||||
jos.write(toBytes(clazz))
|
||||
jos.closeEntry()
|
||||
}
|
||||
}
|
||||
return jar.toAbsolutePath().toString()
|
||||
}
|
||||
|
||||
init {
|
||||
val pid = ManagementFactory.getRuntimeMXBean().getName().substringBefore('@')
|
||||
var jvm = VirtualMachine.attach(pid)
|
||||
jvm.loadAgent(createAgentJar(), "resource=mods.properties")
|
||||
jvm.detach()
|
||||
}
|
||||
}
|
@ -5,7 +5,16 @@ import net.corda.core.utilities.contextLogger
|
||||
import org.fusesource.jansi.AnsiConsole
|
||||
import org.slf4j.event.Level
|
||||
import picocli.CommandLine
|
||||
import picocli.CommandLine.*
|
||||
import picocli.CommandLine.Command
|
||||
import picocli.CommandLine.DefaultExceptionHandler
|
||||
import picocli.CommandLine.ExecutionException
|
||||
import picocli.CommandLine.Help
|
||||
import picocli.CommandLine.ITypeConverter
|
||||
import picocli.CommandLine.Option
|
||||
import picocli.CommandLine.ParameterException
|
||||
import picocli.CommandLine.ParseResult
|
||||
import picocli.CommandLine.RunLast
|
||||
import picocli.CommandLine.TypeConversionException
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
@ -114,6 +123,10 @@ fun CordaCliWrapper.start(args: Array<String>) {
|
||||
optionListHeading = "%n@|bold,underline Options|@:%n%n",
|
||||
commandListHeading = "%n@|bold,underline Commands|@:%n%n")
|
||||
abstract class CliWrapperBase(val alias: String, val description: String) : Callable<Int> {
|
||||
init {
|
||||
AttachAegis4j
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val logger by lazy { contextLogger() }
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ HTTPSERVER.com.sun.net.httpserver.spi.HttpServerProvider.provider=throw new java
|
||||
# CVE-2022-1471
|
||||
SNAKEYAML.org.yaml.snakeyaml.constructor.Constructor.Constructor=throw new java.lang.RuntimeException("SnakeYAML Constructor blocked by aegis4j");
|
||||
# CVE-2020-29582
|
||||
KOTLIN.kotlin.io.FilesKt.createTempDir=throw new java.lang.RuntimeException("Kotlin createTempDir blocked by aegis4j");
|
||||
KOTLIN.kotlin.io.FilesKt.createTempFile=throw new java.lang.RuntimeException("Kotlin createTempFile blocked by aegis4j");
|
||||
KOTLIN.kotlin.io.FilesKt__UtilsKt.createTempDir=throw new java.lang.RuntimeException("Kotlin createTempDir blocked by aegis4j");
|
||||
KOTLIN.kotlin.io.FilesKt__UtilsKt.createTempFile=throw new java.lang.RuntimeException("Kotlin createTempFile blocked by aegis4j");
|
||||
# CVE-2020-8908
|
||||
GUAVA.com.google.common.io.Files.createTempDir=throw new java.lang.RuntimeException("Guava createTempDir blocked by aegis4j");
|
Loading…
x
Reference in New Issue
Block a user