Skip to content

Commit 683d27f

Browse files
committed
Appsec: basic grpc support
1 parent 7fa2f9b commit 683d27f

17 files changed

Lines changed: 406 additions & 29 deletions

File tree

dd-java-agent/appsec/src/main/java/com/datadog/appsec/event/data/KnownAddresses.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ public interface KnownAddresses {
8686
Address<CaseInsensitiveMap<List<String>>> HEADERS_NO_COOKIES =
8787
new Address<>("server.request.headers.no_cookies");
8888

89+
Address<Object> GRPC_SERVER_REQUEST_MESSAGE = new Address<>("grpc.server.request.message");
90+
8991
static Address<?> forName(String name) {
9092
switch (name) {
9193
case "server.request.body":
@@ -124,6 +126,8 @@ static Address<?> forName(String name) {
124126
return REQUEST_QUERY;
125127
case "server.request.headers.no_cookies":
126128
return HEADERS_NO_COOKIES;
129+
case "grpc.server.request.message":
130+
return GRPC_SERVER_REQUEST_MESSAGE;
127131
default:
128132
return null;
129133
}

dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/GatewayBridge.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public class GatewayBridge {
5959
private volatile EventProducerService.DataSubscriberInfo requestBodySubInfo;
6060
private volatile EventProducerService.DataSubscriberInfo pathParamsSubInfo;
6161
private volatile EventProducerService.DataSubscriberInfo respDataSubInfo;
62+
private volatile EventProducerService.DataSubscriberInfo grpcServerRequestMsgSubInfo;
6263

6364
public GatewayBridge(
6465
SubscriptionService subscriptionService,
@@ -282,6 +283,23 @@ public void init() {
282283
ctx.finishResponseHeaders();
283284
return maybePublishResponseData(ctx);
284285
});
286+
287+
subscriptionService.registerCallback(
288+
EVENTS.grpcServerRequestMessage(),
289+
(ctx_, obj) -> {
290+
AppSecRequestContext ctx = ctx_.getData();
291+
if (grpcServerRequestMsgSubInfo == null) {
292+
grpcServerRequestMsgSubInfo =
293+
producerService.getDataSubscribers(KnownAddresses.GRPC_SERVER_REQUEST_MESSAGE);
294+
}
295+
if (grpcServerRequestMsgSubInfo.isEmpty()) {
296+
return Flow.ResultFlow.empty();
297+
}
298+
Object convObj = ObjectIntrospection.convert(obj);
299+
DataBundle bundle =
300+
new SingletonDataBundle<>(KnownAddresses.GRPC_SERVER_REQUEST_MESSAGE, convObj);
301+
return producerService.publishDataEvent(grpcServerRequestMsgSubInfo, ctx, bundle, true);
302+
});
285303
}
286304

287305
// currently unused; doesn't do anything useful

dd-java-agent/appsec/src/main/java/com/datadog/appsec/powerwaf/PowerWAFModule.java

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -223,21 +223,14 @@ public void onDataAvailable(
223223
log.debug("Skipped; the WAF is not configured");
224224
return;
225225
}
226-
PowerwafContext powerwafContext = ctxAndAddr.ctx;
227226
try {
228227
StandardizedLogging.executingWAF(log);
229228
long start = 0L;
230229
if (log.isDebugEnabled()) {
231230
start = System.currentTimeMillis();
232231
}
233232

234-
Additive additive = reqCtx.getAdditive();
235-
if (additive == null) {
236-
additive = powerwafContext.openAdditive();
237-
reqCtx.setAdditive(additive);
238-
}
239-
actionWithData =
240-
additive.run(new DataBundleMapWrapper(ctxAndAddr.addressesOfInterest, newData), LIMITS);
233+
actionWithData = doRunPowerwaf(reqCtx, newData, ctxAndAddr);
241234

242235
if (log.isDebugEnabled()) {
243236
long elapsed = System.currentTimeMillis() - start;
@@ -262,6 +255,37 @@ public void onDataAvailable(
262255
reqCtx.reportEvents(events, null);
263256
}
264257
}
258+
259+
private Powerwaf.ActionWithData doRunPowerwaf(
260+
AppSecRequestContext reqCtx, DataBundle newData, CtxAndAddresses ctxAndAddr)
261+
throws AbstractPowerwafException {
262+
boolean isTransient =
263+
newData.getAllAddresses().stream().anyMatch(addr -> !reqCtx.hasAddress(addr));
264+
if (isTransient) {
265+
DataBundle bundle = DataBundle.unionOf(newData, reqCtx);
266+
return runPowerwafTransient(bundle, ctxAndAddr);
267+
} else {
268+
return runPowerwafAdditive(reqCtx, newData, ctxAndAddr);
269+
}
270+
}
271+
272+
private Powerwaf.ActionWithData runPowerwafAdditive(
273+
AppSecRequestContext reqCtx, DataBundle newData, CtxAndAddresses ctxAndAddr)
274+
throws AbstractPowerwafException {
275+
Additive additive = reqCtx.getAdditive();
276+
if (additive == null) {
277+
additive = ctxAndAddr.ctx.openAdditive();
278+
reqCtx.setAdditive(additive);
279+
}
280+
return additive.run(
281+
new DataBundleMapWrapper(ctxAndAddr.addressesOfInterest, newData), LIMITS);
282+
}
283+
}
284+
285+
private Powerwaf.ActionWithData runPowerwafTransient(
286+
DataBundle bundle, CtxAndAddresses ctxAndAddr) throws AbstractPowerwafException {
287+
return ctxAndAddr.ctx.runRules(
288+
new DataBundleMapWrapper(ctxAndAddr.addressesOfInterest, bundle), LIMITS);
265289
}
266290

267291
private Collection<AppSecEvent100> buildEvents(Powerwaf.ActionWithData actionWithData) {

dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/event/data/KnownAddressesSpecification.groovy

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@ class KnownAddressesSpecification extends Specification {
2727
"server.request.body.combined_file_size",
2828
"server.request.query",
2929
"server.request.headers.no_cookies",
30+
"grpc.server.request.message"
3031
]
3132
}
3233

3334
void 'number of known addresses is expected number'() {
3435
expect:
35-
Address.instanceCount() == 18
36-
KnownAddresses.HEADERS_NO_COOKIES.serial == Address.instanceCount() - 1
36+
Address.instanceCount() == 19
37+
KnownAddresses.GRPC_SERVER_REQUEST_MESSAGE.serial == Address.instanceCount() - 1
3738
}
3839
}

dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class GatewayBridgeSpecification extends DDSpecification {
6565
BiFunction<RequestContext, Integer, Flow<Void>> responseStartedCB
6666
TriConsumer<RequestContext, String, String> respHeaderCB
6767
Function<RequestContext, Flow<Void>> respHeadersDoneCB
68+
BiFunction<RequestContext, Object, Flow<Void>> grpcServerRequestMessageCB
6869

6970
void setup() {
7071
callInitAndCaptureCBs()
@@ -354,6 +355,7 @@ class GatewayBridgeSpecification extends DDSpecification {
354355
1 * ig.registerCallback(EVENTS.responseStarted(), _) >> { responseStartedCB = it[1]; null }
355356
1 * ig.registerCallback(EVENTS.responseHeader(), _) >> { respHeaderCB = it[1]; null }
356357
1 * ig.registerCallback(EVENTS.responseHeaderDone(), _) >> { respHeadersDoneCB = it[1]; null }
358+
1 * ig.registerCallback(EVENTS.grpcServerRequestMessage(), _) >> { grpcServerRequestMessageCB = it[1]; null }
357359
0 * ig.registerCallback(_, _)
358360

359361
bridge.init()
@@ -630,4 +632,23 @@ class GatewayBridgeSpecification extends DDSpecification {
630632
flow2.result == null
631633
flow2.action == Flow.Action.Noop.INSTANCE
632634
}
635+
636+
void 'grpc server message recv transforms object and publishes'() {
637+
setup:
638+
eventDispatcher.getDataSubscribers({ KnownAddresses.GRPC_SERVER_REQUEST_MESSAGE in it }) >> nonEmptyDsInfo
639+
DataBundle bundle
640+
641+
when:
642+
Flow<?> flow = grpcServerRequestMessageCB.apply(ctx, new Object() {
643+
@SuppressWarnings('UnusedPrivateField')
644+
private String foo = 'bar'
645+
})
646+
647+
then:
648+
1 * eventDispatcher.publishDataEvent(nonEmptyDsInfo, ctx.data, _ as DataBundle, true) >>
649+
{ a, b, db, c -> bundle = db; NoopFlow.INSTANCE }
650+
bundle.get(KnownAddresses.GRPC_SERVER_REQUEST_MESSAGE) == [foo: 'bar']
651+
flow.result == null
652+
flow.action == Flow.Action.Noop.INSTANCE
653+
}
633654
}

dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/powerwaf/PowerWAFModuleSpecification.groovy

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ class PowerWAFModuleSpecification extends DDSpecification {
2828
DataListener dataListener
2929
EventListener eventListener
3030

31+
def pwafAdditive
32+
3133
private void setupWithStubConfigService() {
3234
def service = new StubAppSecConfigService()
3335
service.init(false)
@@ -50,6 +52,28 @@ class PowerWAFModuleSpecification extends DDSpecification {
5052
eventListener.onEvent(ctx, EventType.REQUEST_END)
5153

5254
then:
55+
1 * ctx.hasAddress(KnownAddresses.HEADERS_NO_COOKIES) >> true
56+
1 * ctx.getAdditive() >> null
57+
1 * ctx.setAdditive(_) >> { pwafAdditive = it; null }
58+
1 * ctx.getAdditive() >> pwafAdditive
59+
1 * ctx.setAdditive(null)
60+
flow.blocking == true
61+
}
62+
63+
void 'can trigger a nonadditive waf run'() {
64+
setupWithStubConfigService()
65+
ChangeableFlow flow = new ChangeableFlow()
66+
67+
when:
68+
dataListener.onDataAvailable(flow, ctx, ATTACK_BUNDLE)
69+
70+
then:
71+
1 * ctx.size() >> 0
72+
1 * ctx.allAddresses >> []
73+
1 * ctx.hasAddress(KnownAddresses.HEADERS_NO_COOKIES) >> false
74+
1 * ctx.setBlocked(_)
75+
1 * ctx.reportEvents(*_)
76+
0 * ctx._(*_)
5377
flow.blocking == true
5478
}
5579

@@ -62,6 +86,7 @@ class PowerWAFModuleSpecification extends DDSpecification {
6286
eventListener.onEvent(ctx, EventType.REQUEST_END)
6387

6488
then:
89+
1 * ctx.hasAddress(KnownAddresses.HEADERS_NO_COOKIES) >> true
6590
ctx.reportEvents(_ as Collection<AppSecEvent100>, _) >> { event = it[0].iterator().next() }
6691

6792
event.rule.id == 'ua0-600-12x'
@@ -125,6 +150,7 @@ class PowerWAFModuleSpecification extends DDSpecification {
125150
thrown AppSecModule.AppSecModuleActivationException
126151

127152
when:
153+
1 * ctx.hasAddress(KnownAddresses.HEADERS_NO_COOKIES) >> true
128154
cfgService.listeners['waf'].onNewSubconfig(defaultConfig['waf'])
129155
dataListener = pwafModule.dataSubscriptions.first()
130156
eventListener = pwafModule.eventSubscriptions.first()

0 commit comments

Comments
 (0)