@@ -3,13 +3,17 @@ package agent
33import (
44 "bytes"
55 "encoding/json"
6+ "errors"
67 "fmt"
78 "io/ioutil"
89 "net"
910 "net/http"
1011 "net/http/httptest"
1112 "net/url"
13+ "os"
1214 "strings"
15+ "sync"
16+ "syscall"
1317 "testing"
1418 "time"
1519
@@ -1233,3 +1237,229 @@ func TestHTTP_AgentHealth_BadClient(t *testing.T) {
12331237 }
12341238 })
12351239}
1240+
1241+ var (
1242+ errorPipe = & net.OpError {
1243+ Op : "write" ,
1244+ Net : "tcp" ,
1245+ Source : & net.TCPAddr {},
1246+ Addr : & net.TCPAddr {},
1247+ Err : & os.SyscallError {
1248+ Syscall : "write" ,
1249+ Err : syscall .EPIPE ,
1250+ },
1251+ }
1252+ )
1253+
1254+ // fakeRW is a fake response writer to ease polling streaming responses in a
1255+ // data-race-free way.
1256+ type fakeRW struct {
1257+ Code int
1258+ HeaderMap http.Header
1259+ buf * bytes.Buffer
1260+ closed bool
1261+ mu sync.Mutex
1262+
1263+ // Written is ticked whenever a Write occurs and on WriteHeaders if it
1264+ // is explicitly called
1265+ Written chan int
1266+
1267+ // ClosedErr is the error Write will return once the writer is closed.
1268+ // Defaults to EPIPE. Must not be mutated concurrently with writes.
1269+ ClosedErr error
1270+ }
1271+
1272+ // Header is for setting headers before writing a response. Tests should check
1273+ // the HeaderMap field directly.
1274+ func (f * fakeRW ) Header () http.Header {
1275+ f .mu .Lock ()
1276+ defer f .mu .Unlock ()
1277+
1278+ if f .Code != 0 {
1279+ panic ("cannot set headers after WriteHeader has been called" )
1280+ }
1281+
1282+ return f .HeaderMap
1283+ }
1284+
1285+ func (f * fakeRW ) Write (p []byte ) (int , error ) {
1286+ f .mu .Lock ()
1287+ defer f .mu .Unlock ()
1288+
1289+ if f .closed {
1290+ // Mimic an EPIPE error
1291+ return 0 , f .ClosedErr
1292+ }
1293+
1294+ if f .Code == 0 {
1295+ f .Code = 200
1296+ }
1297+
1298+ n , err := f .buf .Write (p )
1299+ select {
1300+ case f .Written <- 1 :
1301+ default :
1302+ }
1303+ return n , err
1304+ }
1305+
1306+ // WriteHeader sets Code and FinalHeaders
1307+ func (f * fakeRW ) WriteHeader (statusCode int ) {
1308+ f .mu .Lock ()
1309+ defer f .mu .Unlock ()
1310+
1311+ if f .Code != 0 {
1312+ panic ("cannot call WriteHeader more than once" )
1313+ }
1314+
1315+ f .Code = statusCode
1316+ select {
1317+ case f .Written <- 1 :
1318+ default :
1319+ }
1320+ }
1321+
1322+ // Bytes returns the body bytes written to the buffer. Safe for calling
1323+ // concurrent with writes.
1324+ func (f * fakeRW ) Bytes () []byte {
1325+ f .mu .Lock ()
1326+ defer f .mu .Unlock ()
1327+
1328+ return f .buf .Bytes ()
1329+ }
1330+
1331+ // Close the writer causing an EPIPE error on future writes. Safe to call
1332+ // concurrently with other methods. Safe to call more than once.
1333+ func (f * fakeRW ) Close () {
1334+ f .mu .Lock ()
1335+ defer f .mu .Unlock ()
1336+ f .closed = true
1337+ }
1338+
1339+ func NewFakeRW () * fakeRW {
1340+ return & fakeRW {
1341+ HeaderMap : make (map [string ][]string ),
1342+ buf : & bytes.Buffer {},
1343+ Written : make (chan int , 1 ),
1344+ ClosedErr : errorPipe ,
1345+ }
1346+ }
1347+
1348+ // TestHTTP_XSS_Monitor asserts /v1/agent/monitor is safe against XSS attacks
1349+ // even when log output contains HTML+Javascript.
1350+ func TestHTTP_XSS_Monitor (t * testing.T ) {
1351+ t .Parallel ()
1352+
1353+ cases := []struct {
1354+ Name string
1355+ Logline string
1356+ JSON bool
1357+ }{
1358+ {
1359+ Name : "Plain" ,
1360+ Logline : "--TEST 123--" ,
1361+ JSON : false ,
1362+ },
1363+ {
1364+ Name : "JSON" ,
1365+ Logline : "--TEST 123--" ,
1366+ JSON : true ,
1367+ },
1368+ {
1369+ Name : "XSSPlain" ,
1370+ Logline : "<script>alert(document.domain);</script>" ,
1371+ JSON : false ,
1372+ },
1373+ {
1374+ Name : "XSSJson" ,
1375+ Logline : "<script>alert(document.domain);</script>" ,
1376+ JSON : true ,
1377+ },
1378+ }
1379+
1380+ for i := range cases {
1381+ tc := cases [i ]
1382+ t .Run (tc .Name , func (t * testing.T ) {
1383+ t .Parallel ()
1384+ s := makeHTTPServer (t , nil )
1385+ defer s .Shutdown ()
1386+
1387+ path := fmt .Sprintf ("%s/v1/agent/monitor?error_level=error&plain=%t" , s .HTTPAddr (), ! tc .JSON )
1388+ req , err := http .NewRequest ("GET" , path , nil )
1389+ require .NoError (t , err )
1390+ resp := NewFakeRW ()
1391+ closedErr := errors .New ("sentinel error" )
1392+ resp .ClosedErr = closedErr
1393+ defer resp .Close ()
1394+
1395+ errCh := make (chan error , 1 )
1396+ go func () {
1397+ _ , err := s .Server .AgentMonitor (resp , req )
1398+ errCh <- err
1399+ }()
1400+
1401+ deadline := time .After (3 * time .Second )
1402+
1403+ OUTER:
1404+ for {
1405+ // Log a needle and look for it in the response haystack
1406+ s .Server .logger .Error (tc .Logline )
1407+
1408+ select {
1409+ case <- time .After (30 * time .Millisecond ):
1410+ // Give AgentMonitor handler goroutine time to start
1411+ case <- resp .Written :
1412+ // Something was written, check it
1413+ case <- deadline :
1414+ t .Fatalf ("timed out waiting for expected log line; body:\n %s" , string (resp .Bytes ()))
1415+ case err := <- errCh :
1416+ t .Fatalf ("AgentMonitor exited unexpectedly: err=%v" , err )
1417+ }
1418+
1419+ if ! tc .JSON {
1420+ if bytes .Contains (resp .Bytes (), []byte (tc .Logline )) {
1421+ // Found needle!
1422+ break
1423+ } else {
1424+ // Try again
1425+ continue
1426+ }
1427+ }
1428+
1429+ // Decode JSON
1430+ r := bytes .NewReader (resp .Bytes ())
1431+ dec := json .NewDecoder (r )
1432+ for {
1433+ data := struct { Data []byte }{}
1434+ if err := dec .Decode (& data ); err != nil {
1435+ // Probably a partial write, continue
1436+ continue OUTER
1437+ }
1438+
1439+ if bytes .Contains (data .Data , []byte (tc .Logline )) {
1440+ // Found needle!
1441+ break OUTER
1442+ }
1443+ }
1444+
1445+ }
1446+
1447+ // Assert default logs are application/json
1448+ ct := "text/plain"
1449+ if tc .JSON {
1450+ ct = "application/json"
1451+ }
1452+ require .Equal (t , []string {ct }, resp .HeaderMap .Values ("Content-Type" ))
1453+
1454+ // Close response writer and log to make AgentMonitor exit
1455+ resp .Close ()
1456+ s .Server .logger .Error ("log again to force a write that detects the closed connection" )
1457+ select {
1458+ case err := <- errCh :
1459+ require .EqualError (t , closedErr , err .Error ())
1460+ case <- deadline :
1461+ t .Fatalf ("timed out waiting for closing error from handler" )
1462+ }
1463+ })
1464+ }
1465+ }
0 commit comments