diff --git a/command/agent/fs_endpoint_test.go b/command/agent/fs_endpoint_test.go index 910a8253f..62cba0f29 100644 --- a/command/agent/fs_endpoint_test.go +++ b/command/agent/fs_endpoint_test.go @@ -20,6 +20,7 @@ import ( const ( defaultLoggerMockDriverStdout = "Hello from the other side" + xssLoggerMockDriverStdout = "" ) var ( @@ -27,6 +28,10 @@ var ( "run_for": "2s", "stdout_string": defaultLoggerMockDriverStdout, } + xssLoggerMockDriver = map[string]interface{}{ + "run_for": "2s", + "stdout_string": xssLoggerMockDriverStdout, + } ) type clientAllocWaiter int @@ -322,6 +327,31 @@ func TestHTTP_FS_ReadAt(t *testing.T) { }) } +// TestHTTP_FS_ReadAt_XSS asserts that the readat API is safe from XSS. +func TestHTTP_FS_ReadAt_XSS(t *testing.T) { + t.Parallel() + httpTest(t, nil, func(s *TestAgent) { + a := mockFSAlloc(s.client.NodeID(), xssLoggerMockDriver) + addAllocToClient(s, a, terminalClientAlloc) + + path := fmt.Sprintf("%s/v1/client/fs/readat/%s?path=alloc/logs/web.stdout.0&offset=0&limit=%d", + s.HTTPAddr(), a.ID, len(xssLoggerMockDriverStdout)) + resp, err := http.DefaultClient.Get(path) + require.NoError(t, err) + defer resp.Body.Close() + + buf, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, xssLoggerMockDriverStdout, string(buf)) + + require.Equal(t, []string{"text/plain"}, resp.Header.Values("Content-Type")) + require.Equal(t, []string{"nosniff"}, resp.Header.Values("X-Content-Type-Options")) + require.Equal(t, []string{"1; mode=block"}, resp.Header.Values("X-XSS-Protection")) + require.Equal(t, []string{"default-src 'none'; style-src 'unsafe-inline'; sandbox"}, + resp.Header.Values("Content-Security-Policy")) + }) +} + func TestHTTP_FS_Cat(t *testing.T) { t.Parallel() require := require.New(t) @@ -343,6 +373,30 @@ func TestHTTP_FS_Cat(t *testing.T) { }) } +// TestHTTP_FS_Cat_XSS asserts that the cat API is safe from XSS. +func TestHTTP_FS_Cat_XSS(t *testing.T) { + t.Parallel() + httpTest(t, nil, func(s *TestAgent) { + a := mockFSAlloc(s.client.NodeID(), xssLoggerMockDriver) + addAllocToClient(s, a, terminalClientAlloc) + + path := fmt.Sprintf("%s/v1/client/fs/cat/%s?path=alloc/logs/web.stdout.0", s.HTTPAddr(), a.ID) + resp, err := http.DefaultClient.Get(path) + require.NoError(t, err) + defer resp.Body.Close() + + buf, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, xssLoggerMockDriverStdout, string(buf)) + + require.Equal(t, []string{"text/plain"}, resp.Header.Values("Content-Type")) + require.Equal(t, []string{"nosniff"}, resp.Header.Values("X-Content-Type-Options")) + require.Equal(t, []string{"1; mode=block"}, resp.Header.Values("X-XSS-Protection")) + require.Equal(t, []string{"default-src 'none'; style-src 'unsafe-inline'; sandbox"}, + resp.Header.Values("Content-Security-Policy")) + }) +} + func TestHTTP_FS_Stream_NoFollow(t *testing.T) { t.Parallel() require := require.New(t) @@ -387,6 +441,26 @@ func TestHTTP_FS_Stream_NoFollow(t *testing.T) { }) } +// TestHTTP_FS_Stream_NoFollow_XSS asserts that the stream API is safe from XSS. +func TestHTTP_FS_Stream_NoFollow_XSS(t *testing.T) { + t.Parallel() + httpTest(t, nil, func(s *TestAgent) { + a := mockFSAlloc(s.client.NodeID(), xssLoggerMockDriver) + addAllocToClient(s, a, terminalClientAlloc) + + path := fmt.Sprintf("%s/v1/client/fs/stream/%s?path=alloc/logs/web.stdout.0&follow=false", + s.HTTPAddr(), a.ID) + resp, err := http.DefaultClient.Get(path) + require.NoError(t, err) + defer resp.Body.Close() + + buf, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + expected := `{"Data":"PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pOzwvc2NyaXB0Pg==","File":"alloc/logs/web.stdout.0","Offset":40}` + require.Equal(t, expected, string(buf)) + }) +} + func TestHTTP_FS_Stream_Follow(t *testing.T) { t.Parallel() require := require.New(t) @@ -466,6 +540,30 @@ func TestHTTP_FS_Logs(t *testing.T) { }) } +// TestHTTP_FS_Logs_XSS asserts that the logs endpoint always returns +// text/plain or application/json content regardless of whether the logs are +// HTML+Javascript or not. +func TestHTTP_FS_Logs_XSS(t *testing.T) { + t.Parallel() + httpTest(t, nil, func(s *TestAgent) { + a := mockFSAlloc(s.client.NodeID(), xssLoggerMockDriver) + addAllocToClient(s, a, terminalClientAlloc) + + // Must make a "real" request to ensure Go's default content + // type detection does not detect text/html + path := fmt.Sprintf("%s/v1/client/fs/logs/%s?type=stdout&task=web&plain=true", s.HTTPAddr(), a.ID) + resp, err := http.DefaultClient.Get(path) + require.NoError(t, err) + defer resp.Body.Close() + + buf, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, xssLoggerMockDriverStdout, string(buf)) + + require.Equal(t, []string{"text/plain"}, resp.Header.Values("Content-Type")) + }) +} + func TestHTTP_FS_Logs_Follow(t *testing.T) { t.Parallel() require := require.New(t)