Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions manifests/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ refs:
- &ref_5_64_0 '>=5.64.0' # Endpoints collection
- &ref_5_66_0 '>=5.66.0' # fastify rasp support
- &ref_5_71_0 '>=5.71.0'
- &ref_5_73_0 '>=5.73.0' # downstream request analysis with http
tests/:
apm_tracing_e2e/:
test_otel.py:
Expand Down Expand Up @@ -513,16 +514,16 @@ tests/:
rasp/:
test_api10.py:
Test_API10_all: missing_feature
Test_API10_downstream_request_tag: missing_feature
Test_API10_downstream_ssrf_telemetry: missing_feature
Test_API10_downstream_request_tag: *ref_5_73_0
Test_API10_downstream_ssrf_telemetry: *ref_5_73_0
Test_API10_request_body: missing_feature
Test_API10_request_headers: missing_feature
Test_API10_request_method: missing_feature
Test_API10_response_body: missing_feature
Test_API10_response_headers: missing_feature
Test_API10_response_status: missing_feature
Test_API10_without_downstream_body_analysis_using_max: missing_feature
Test_API10_without_downstream_body_analysis_using_sample_rate: missing_feature
Test_API10_request_headers: *ref_5_73_0
Test_API10_request_method: *ref_5_73_0
Test_API10_response_body: *ref_5_73_0
Test_API10_response_headers: *ref_5_73_0
Test_API10_response_status: *ref_5_73_0
Test_API10_without_downstream_body_analysis_using_max: *ref_5_73_0
Test_API10_without_downstream_body_analysis_using_sample_rate: *ref_5_73_0
test_cmdi.py:
Test_Cmdi_BodyJson:
'*': *ref_5_30_0
Expand Down
8 changes: 5 additions & 3 deletions tests/appsec/rasp/test_api10.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import json
import urllib.parse

from utils import features, weblog, interfaces, scenarios, rfc
from utils import features, weblog, interfaces, scenarios, rfc, context

from tests.appsec.rasp.utils import (
find_series,
Expand Down Expand Up @@ -87,7 +87,8 @@ class Test_API10_request_method(API10):
TAGS_EXPECTED = [("_dd.appsec.trace.req_method", "TAG_API10_REQ_METHOD")]

def setup_api10_req_method(self):
self.r = weblog.request("TRACE", "/external_request")
method = "PUT" if context.weblog_variant == "nextjs" else "TRACE" # Next.js doesn't support TRACE method
self.r = weblog.request(method, "/external_request")

def test_api10_req_method(self):
assert self.r.status_code == 200
Expand Down Expand Up @@ -232,7 +233,8 @@ class Test_API10_downstream_request_tag(API10):
]

def setup_api10_req_method(self):
self.r = weblog.request("TRACE", "/external_request")
method = "PUT" if context.weblog_variant == "nextjs" else "TRACE" # Next.js doesn't support TRACE method
self.r = weblog.request(method, "/external_request")

def test_api10_req_method(self):
assert self.r.status_code == 200
Expand Down
47 changes: 47 additions & 0 deletions utils/build/docker/nodejs/express/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,53 @@ app.get('/add_event', (req, res) => {
res.status(200).json({ message: 'Event added' })
})

app.all('/external_request', (req, res) => {
const status = req.query.status || '200'
const urlExtra = req.query.url_extra || ''

const headers = {}
for (const [key, value] of Object.entries(req.query)) {
headers[key] = String(value)
}

let body = null
if (req.body && Object.keys(req.body).length > 0) {
body = JSON.stringify(req.body)
headers['Content-Type'] = req.headers['content-type'] || 'application/json'
}

const options = {
hostname: 'internal_server',
port: 8089,
path: `/mirror/${status}${urlExtra}`,
method: req.method,
headers
}

const request = http.request(options, (response) => {
let responseBody = ''
response.on('data', (chunk) => {
responseBody += chunk
})

response.on('end', () => {
const payload = JSON.parse(responseBody)
res.status(200).json({
status: response.statusCode,
payload,
headers: response.headers
})
})
})

// Write body if present
if (body) {
request.write(body)
}

request.end()
})

require('./rasp')(app)

const startServer = () => {
Expand Down
47 changes: 47 additions & 0 deletions utils/build/docker/nodejs/express4-typescript/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,53 @@ app.get('/set_cookie', (req: Request, res: Response) => {
res.send('OK')
})

app.all('/external_request', (req: Request, res: Response) => {
const status = req.query.status || '200'
const urlExtra = req.query.url_extra || ''

const headers: any = {}
for (const [key, value] of Object.entries(req.query)) {
headers[key] = String(value)
}

let body = null
if (req.body && Object.keys(req.body).length > 0) {
body = JSON.stringify(req.body)
headers['Content-Type'] = req.headers['content-type'] || 'application/json'
}

const options = {
hostname: 'internal_server',
port: 8089,
path: `/mirror/${status}${urlExtra}`,
method: req.method,
headers
}

const request = http.request(options, (response) => {
let responseBody = ''
response.on('data', (chunk) => {
responseBody += chunk
})

response.on('end', () => {
const payload = JSON.parse(responseBody)
res.status(200).json({
status: response.statusCode,
payload,
headers: response.headers
})
})
})

// Write body if present
if (body) {
request.write(body)
}

request.end()
})

require('./rasp')(app)

require('./graphql')(app).then(() => {
Expand Down
52 changes: 52 additions & 0 deletions utils/build/docker/nodejs/fastify/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,58 @@ fastify.get('/add_event', async (request, reply) => {
return { message: 'Event added' }
})

fastify.all('/external_request', async (request, reply) => {
const status = request.query.status || '200'
const urlExtra = request.query.url_extra || ''

const headers = {}
for (const [key, value] of Object.entries(request.query)) {
if (key !== 'status' && key !== 'url_extra') {
headers[key] = String(value)
}
}

let body = null
if (request.body && Object.keys(request.body).length > 0) {
body = JSON.stringify(request.body)
headers['Content-Type'] = request.headers['content-type'] || 'application/json'
}

const options = {
hostname: 'internal_server',
port: 8089,
path: `/mirror/${status}${urlExtra}`,
method: request.method,
headers
}

return new Promise((resolve, reject) => {
const httpRequest = http.request(options, (response) => {
let responseBody = ''
response.on('data', (chunk) => {
responseBody += chunk
})

response.on('end', () => {
const payload = JSON.parse(responseBody)
reply.status(200)
resolve({
payload,
status: response.statusCode,
headers: response.headers
})
})
})

// Write body if present
if (body) {
httpRequest.write(body)
}

httpRequest.end()
})
})

require('./rasp')(fastify)

const startServer = async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { NextResponse } from 'next/server'
import http from 'http'

export const dynamic = 'force-dynamic'

async function handleExternalRequest (request) {
const searchParams = request.nextUrl.searchParams
const status = searchParams.get('status') || '200'
const urlExtra = searchParams.get('url_extra') || ''

const headers = {}
for (const [key, value] of searchParams.entries()) {
if (key !== 'status' && key !== 'url_extra') {
headers[key] = String(value)
}
}

let body = null
const contentType = request.headers.get('content-type')
if (contentType && contentType.includes('application/json')) {
const requestBody = await request.json()
if (requestBody && Object.keys(requestBody).length > 0) {
body = JSON.stringify(requestBody)
headers['Content-Type'] = contentType || 'application/json'
}
}

const options = {
hostname: 'internal_server',
port: 8089,
path: `/mirror/${status}${urlExtra}`,
method: request.method,
headers
}

return new Promise((resolve) => {
const httpRequest = http.request(options, (response) => {
let responseBody = ''
response.on('data', (chunk) => {
responseBody += chunk
})

response.on('end', () => {
const payload = JSON.parse(responseBody)
resolve(NextResponse.json({
payload,
status: response.statusCode,
headers: response.headers
}))
})
})

// Write body if present
if (body) {
httpRequest.write(body)
}

httpRequest.end()
})
}

export async function GET (request) {
return handleExternalRequest(request)
}

export async function POST (request) {
return handleExternalRequest(request)
}

export async function PUT (request) {
return handleExternalRequest(request)
}
Loading