diff --git a/instrumentation-security/jcache-1.0.0/build.gradle b/instrumentation-security/jcache-1.0.0/build.gradle new file mode 100644 index 000000000..486a7b10b --- /dev/null +++ b/instrumentation-security/jcache-1.0.0/build.gradle @@ -0,0 +1,21 @@ + +dependencies { + implementation(project(":newrelic-security-api")) + implementation("com.newrelic.agent.java:newrelic-api:${nrAPIVersion}") + implementation("com.newrelic.agent.java:newrelic-weaver-api:${nrAPIVersion}") + implementation("javax.cache:cache-api:1.0.0") + testImplementation("com.hazelcast:hazelcast:4.2.8") +} + +jar { + manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.security.jcache-1.0.0' } +} + +verifyInstrumentation { + passes 'javax.cache:cache-api:[1.0.0,)' +} + +site { + title 'JCache API' + type 'Framework' +} \ No newline at end of file diff --git a/instrumentation-security/jcache-1.0.0/src/main/java/com/newrelic/agent/security/instrumentation/jcache_1_0_0/JCacheHelper.java b/instrumentation-security/jcache-1.0.0/src/main/java/com/newrelic/agent/security/instrumentation/jcache_1_0_0/JCacheHelper.java new file mode 100644 index 000000000..5ebbbdf87 --- /dev/null +++ b/instrumentation-security/jcache-1.0.0/src/main/java/com/newrelic/agent/security/instrumentation/jcache_1_0_0/JCacheHelper.java @@ -0,0 +1,56 @@ +package com.newrelic.agent.security.instrumentation.jcache_1_0_0; + +import com.newrelic.api.agent.security.NewRelicSecurity; +import com.newrelic.api.agent.security.instrumentation.helpers.GenericHelper; +import com.newrelic.api.agent.security.schema.AbstractOperation; +import com.newrelic.api.agent.security.schema.exceptions.NewRelicSecurityException; +import com.newrelic.api.agent.security.schema.operation.JCacheOperation; + +import java.util.List; + +public class JCacheHelper { + public static final String READ = "read"; + public static final String WRITE = "write"; + public static final String DELETE = "delete"; + public static final String UPDATE = "update"; + public static final String NR_SEC_CUSTOM_ATTRIB_NAME = "JCACHE-OPERATION-LOCK-"; + + public static AbstractOperation preprocessSecurityHook(String command, List args, String klass, String method) { + try { + if (!NewRelicSecurity.isHookProcessingActive() || NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty()){ + return null; + } + JCacheOperation operation = new JCacheOperation(klass, method, command, args); + NewRelicSecurity.getAgent().registerOperation(operation); + return operation; + } catch (Throwable e) { + if (e instanceof NewRelicSecurityException) { + throw e; + } + } + return null; + } + + public static void registerExitOperation(boolean isProcessingAllowed, AbstractOperation operation) { + try { + if (operation == null || !isProcessingAllowed || !NewRelicSecurity.isHookProcessingActive() || + NewRelicSecurity.getAgent().getSecurityMetaData().getRequest().isEmpty()) { + return; + } + NewRelicSecurity.getAgent().registerExitEvent(operation); + } catch (Throwable ignored){} + } + + public static void releaseLock(int hashcode) { + try { + GenericHelper.releaseLock(NR_SEC_CUSTOM_ATTRIB_NAME, hashcode); + } catch (Throwable ignored) {} + } + + public static boolean acquireLockIfPossible(int hashcode) { + try { + return GenericHelper.acquireLockIfPossible(NR_SEC_CUSTOM_ATTRIB_NAME, hashcode); + } catch (Throwable ignored) {} + return false; + } +} diff --git a/instrumentation-security/jcache-1.0.0/src/main/java/javax/cache/Cache_Instrumentation.java b/instrumentation-security/jcache-1.0.0/src/main/java/javax/cache/Cache_Instrumentation.java new file mode 100644 index 000000000..76f6a94f5 --- /dev/null +++ b/instrumentation-security/jcache-1.0.0/src/main/java/javax/cache/Cache_Instrumentation.java @@ -0,0 +1,286 @@ +package javax.cache; + +import com.newrelic.agent.security.instrumentation.jcache_1_0_0.JCacheHelper; +import com.newrelic.api.agent.security.schema.AbstractOperation; +import com.newrelic.api.agent.weaver.MatchType; +import com.newrelic.api.agent.weaver.Weave; +import com.newrelic.api.agent.weaver.Weaver; + +import javax.cache.integration.CompletionListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Weave(type = MatchType.Interface, originalName = "javax.cache.Cache") +public abstract class Cache_Instrumentation { + public V get(K key) { + boolean isLockAcquired = JCacheHelper.acquireLockIfPossible(this.hashCode()); + AbstractOperation operation = null; + if (isLockAcquired) { + operation = JCacheHelper.preprocessSecurityHook(JCacheHelper.READ, Collections.singletonList(key), this.getClass().getName(), "get"); + } + V returnValue = null; + try { + returnValue = Weaver.callOriginal(); + } finally { + if(isLockAcquired) { + JCacheHelper.releaseLock(this.hashCode()); + } + } + JCacheHelper.registerExitOperation(isLockAcquired, operation); + return returnValue; + } + + public Map getAll(Set keys) { + boolean isLockAcquired = JCacheHelper.acquireLockIfPossible(this.hashCode()); + AbstractOperation operation = null; + if (isLockAcquired) { + operation = JCacheHelper.preprocessSecurityHook(JCacheHelper.READ, new ArrayList() { { addAll(keys); } }, this.getClass().getName(), "getAll"); + } + Map returnValue = null; + try { + returnValue = Weaver.callOriginal(); + } finally { + if(isLockAcquired) { + JCacheHelper.releaseLock(this.hashCode()); + } + } + JCacheHelper.registerExitOperation(isLockAcquired, operation); + return returnValue; + } + + public boolean containsKey(K key) { + boolean isLockAcquired = JCacheHelper.acquireLockIfPossible(this.hashCode()); + AbstractOperation operation = null; + if (isLockAcquired) { + operation = JCacheHelper.preprocessSecurityHook(JCacheHelper.READ, Collections.singletonList(key), this.getClass().getName(), "containsKey"); + } + boolean returnValue; + try { + returnValue = Weaver.callOriginal(); + } finally { + if(isLockAcquired) { + JCacheHelper.releaseLock(this.hashCode()); + } + } + JCacheHelper.registerExitOperation(isLockAcquired, operation); + return returnValue; + } + + public void loadAll(Set keys, boolean replaceExistingValues, CompletionListener completionListener) { + boolean isLockAcquired = JCacheHelper.acquireLockIfPossible(this.hashCode()); + AbstractOperation operation = null; + if (isLockAcquired) { + operation = JCacheHelper.preprocessSecurityHook(JCacheHelper.READ, new ArrayList() { { addAll(keys); } }, this.getClass().getName(), "loadAll"); + } + try { + Weaver.callOriginal(); + } finally { + if(isLockAcquired) { + JCacheHelper.releaseLock(this.hashCode()); + } + } + JCacheHelper.registerExitOperation(isLockAcquired, operation); + } + + public void put(K key, V value) { + boolean isLockAcquired = JCacheHelper.acquireLockIfPossible(this.hashCode()); + AbstractOperation operation = null; + if (isLockAcquired) { + operation = JCacheHelper.preprocessSecurityHook(JCacheHelper.WRITE, Arrays.asList(key, value), this.getClass().getName(), "put"); + } + try { + Weaver.callOriginal(); + } finally { + if(isLockAcquired) { + JCacheHelper.releaseLock(this.hashCode()); + } + } + JCacheHelper.registerExitOperation(isLockAcquired, operation); + } + + public V getAndPut(K key, V value) { + boolean isLockAcquired = JCacheHelper.acquireLockIfPossible(this.hashCode()); + AbstractOperation operation = null; + if (isLockAcquired) { + operation = JCacheHelper.preprocessSecurityHook(JCacheHelper.WRITE, Arrays.asList(key, value), this.getClass().getName(), "getAndPut"); + } + V returnValue; + try { + returnValue = Weaver.callOriginal(); + } finally { + if(isLockAcquired) { + JCacheHelper.releaseLock(this.hashCode()); + } + } + JCacheHelper.registerExitOperation(isLockAcquired, operation); + return returnValue; + } + + public void putAll(Map map) { + boolean isLockAcquired = JCacheHelper.acquireLockIfPossible(this.hashCode()); + AbstractOperation operation = null; + if (isLockAcquired) { + List argList = new ArrayList<>(); + for (Map.Entry entry : map.entrySet()) { + argList.add(entry.getKey()); + argList.add(entry.getValue()); + } + // do not call register exit operation method, this will lead to a verify error + // Type 'java/lang/Object' (current frame, stack[0]) is not assignable to 'com/newrelic/api/agent/security/schema/AbstractOperation' + operation = JCacheHelper.preprocessSecurityHook(JCacheHelper.WRITE, argList, this.getClass().getName(), "putAll"); + } + try { + Weaver.callOriginal(); + } finally { + if(isLockAcquired) { + JCacheHelper.releaseLock(this.hashCode()); + } + } + } + + public boolean putIfAbsent(K key, V value) { + boolean isLockAcquired = JCacheHelper.acquireLockIfPossible(this.hashCode()); + AbstractOperation operation = null; + if (isLockAcquired) { + operation = JCacheHelper.preprocessSecurityHook(JCacheHelper.WRITE, Arrays.asList(key, value), this.getClass().getName(), "putIfAbsent"); + } + boolean returnValue; + try { + returnValue = Weaver.callOriginal(); + } finally { + if(isLockAcquired) { + JCacheHelper.releaseLock(this.hashCode()); + } + } + JCacheHelper.registerExitOperation(isLockAcquired, operation); + return returnValue; + } + + public boolean remove(K key) { + boolean isLockAcquired = JCacheHelper.acquireLockIfPossible(this.hashCode()); + AbstractOperation operation = null; + if (isLockAcquired) { + operation = JCacheHelper.preprocessSecurityHook(JCacheHelper.DELETE, Collections.singletonList(key), this.getClass().getName(), "remove"); + } + boolean returnValue; + try { + returnValue = Weaver.callOriginal(); + } finally { + if(isLockAcquired) { + JCacheHelper.releaseLock(this.hashCode()); + } + } + JCacheHelper.registerExitOperation(isLockAcquired, operation); + return returnValue; + } + + public boolean remove(K key, V oldValue) { + boolean isLockAcquired = JCacheHelper.acquireLockIfPossible(this.hashCode()); + AbstractOperation operation = null; + if (isLockAcquired) { + operation = JCacheHelper.preprocessSecurityHook(JCacheHelper.DELETE, Arrays.asList(key, oldValue), this.getClass().getName(), "remove"); + } + boolean returnValue; + try { + returnValue = Weaver.callOriginal(); + } finally { + if(isLockAcquired) { + JCacheHelper.releaseLock(this.hashCode()); + } + } + JCacheHelper.registerExitOperation(isLockAcquired, operation); + return returnValue; + } + + public V getAndRemove(K key) { + boolean isLockAcquired = JCacheHelper.acquireLockIfPossible(this.hashCode()); + AbstractOperation operation = null; + if (isLockAcquired) { + operation = JCacheHelper.preprocessSecurityHook(JCacheHelper.DELETE, Collections.singletonList(key), this.getClass().getName(), "getAndRemove"); + } + V returnValue; + try { + returnValue = Weaver.callOriginal(); + } finally { + if(isLockAcquired) { + JCacheHelper.releaseLock(this.hashCode()); + } + } + JCacheHelper.registerExitOperation(isLockAcquired, operation); + return returnValue; + } + + public boolean replace(K key, V oldValue) { + boolean isLockAcquired = JCacheHelper.acquireLockIfPossible(this.hashCode()); + AbstractOperation operation = null; + if (isLockAcquired) { + operation = JCacheHelper.preprocessSecurityHook(JCacheHelper.UPDATE, Arrays.asList(key, oldValue), this.getClass().getName(), "replace"); + } + boolean returnValue; + try { + returnValue = Weaver.callOriginal(); + } finally { + if(isLockAcquired) { + JCacheHelper.releaseLock(this.hashCode()); + } + } + JCacheHelper.registerExitOperation(isLockAcquired, operation); + return returnValue; + } + + public boolean replace(K key, V oldValue, V newValue) { + boolean isLockAcquired = JCacheHelper.acquireLockIfPossible(this.hashCode()); + AbstractOperation operation = null; + if (isLockAcquired) { + operation = JCacheHelper.preprocessSecurityHook(JCacheHelper.UPDATE, Arrays.asList(key, oldValue, newValue), this.getClass().getName(), "replace"); + } + boolean returnValue; + try { + returnValue = Weaver.callOriginal(); + } finally { + if(isLockAcquired) { + JCacheHelper.releaseLock(this.hashCode()); + } + } + JCacheHelper.registerExitOperation(isLockAcquired, operation); + return returnValue; + } + + public V getAndReplace(K key, V value) { + boolean isLockAcquired = JCacheHelper.acquireLockIfPossible(this.hashCode()); + AbstractOperation operation = null; + if (isLockAcquired) { + operation = JCacheHelper.preprocessSecurityHook(JCacheHelper.UPDATE, Arrays.asList(key, value), this.getClass().getName(), "getAndReplace"); + } + V returnValue; + try { + returnValue = Weaver.callOriginal(); + } finally { + if(isLockAcquired) { + JCacheHelper.releaseLock(this.hashCode()); + } + } + JCacheHelper.registerExitOperation(isLockAcquired, operation); + return returnValue; + } + + public void removeAll(Set keys) { + boolean isLockAcquired = JCacheHelper.acquireLockIfPossible(this.hashCode()); + AbstractOperation operation = null; + if (isLockAcquired) { + operation = JCacheHelper.preprocessSecurityHook(JCacheHelper.DELETE, new ArrayList() { { addAll(keys); } }, this.getClass().getName(), "removeAll"); + } + try { + Weaver.callOriginal(); + } finally { + if(isLockAcquired) { + JCacheHelper.releaseLock(this.hashCode()); + } + } + JCacheHelper.registerExitOperation(isLockAcquired, operation); + } +} diff --git a/settings.gradle b/settings.gradle index af03c685c..993f8a4a6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -153,4 +153,5 @@ include 'instrumentation:commons-jxpath' //include 'instrumentation:apache-wicket-6.4' //include 'instrumentation:apache-wicket-7.0' //include 'instrumentation:apache-wicket-8.0' -include 'instrumentation:spring-data-redis' \ No newline at end of file +include 'instrumentation:spring-data-redis' +include 'instrumentation:jcache-1.0.0' \ No newline at end of file