diff --git a/inspectit-ocelot-config/src/main/resources/rocks/inspectit/ocelot/config/default/exporters.yml b/inspectit-ocelot-config/src/main/resources/rocks/inspectit/ocelot/config/default/exporters.yml index ebc822236c..f1efab50b2 100644 --- a/inspectit-ocelot-config/src/main/resources/rocks/inspectit/ocelot/config/default/exporters.yml +++ b/inspectit-ocelot-config/src/main/resources/rocks/inspectit/ocelot/config/default/exporters.yml @@ -118,6 +118,6 @@ inspectit: # Additional limitations: key-length -> 128, value-length -> 2048, attribute-count -> 128 session-limit: 100 # header, which will be read during browser-propagation to receive the session-ID - session-id-header: "Cookie" + session-id-header: "Session-Id" # how long the data should be stored in the server in seconds time-to-live: 300 diff --git a/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/exporter/BrowserPropagationHttpExporterService.java b/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/exporter/BrowserPropagationHttpExporterService.java index 1609987ff0..eb1de66cba 100644 --- a/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/exporter/BrowserPropagationHttpExporterService.java +++ b/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/exporter/BrowserPropagationHttpExporterService.java @@ -79,7 +79,7 @@ protected boolean doEnable(InspectitConfig configuration) { protected boolean doDisable() { if(server != null) { try { - log.info("Stopping Tags HTTP-Server"); + log.info("Stopping Tags HTTP-Server - All sessions will be removed"); server.stop(); sessionStorage.clearDataStorages(); sessionStorage.setExporterActive(false); @@ -101,7 +101,7 @@ protected boolean startServer(String host, int port, String path, HttpServlet se log.info("Starting Tags HTTP-Server on {}:{}{} ", host, port, path); server.start(); } catch (Exception e) { - log.warn("Starting of Tags HTTP-Server failed"); + log.error("Starting of Tags HTTP-Server failed", e); return false; } return true; diff --git a/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/exporter/BrowserPropagationServlet.java b/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/exporter/BrowserPropagationServlet.java index a024d32b83..047877d87e 100644 --- a/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/exporter/BrowserPropagationServlet.java +++ b/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/exporter/BrowserPropagationServlet.java @@ -29,12 +29,11 @@ public class BrowserPropagationServlet extends HttpServlet { /** * Header, which should be used to store the session-Ids - * Default-key: "Cookie" */ private final String sessionIdHeader; /** - * List of allowed Orgins, which are able to access the HTTP-server + * List of allowed Origins, which are able to access the HTTP-server */ private final List allowedOrigins; private final ObjectMapper mapper; @@ -54,7 +53,7 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) thro //If wildcard is used, allow every origin //Alternatively, check if current origin is allowed - if(this.allowedOrigins.contains("*") || this.allowedOrigins.contains(origin)) { + if(allowedOrigins.contains("*") || allowedOrigins.contains(origin)) { response.setHeader("Access-Control-Allow-Origin", origin); response.setHeader("Access-Control-Allow-Methods", "GET"); response.setHeader("Access-Control-Allow-Credentials", "true"); @@ -91,7 +90,7 @@ protected void doPut(HttpServletRequest request, HttpServletResponse response) { //If wildcard is used, allow every origin //Alternatively, check if current origin is allowed - if(this.allowedOrigins.contains("*") || this.allowedOrigins.contains(origin)) { + if(allowedOrigins.contains("*") || allowedOrigins.contains(origin)) { response.setHeader("Access-Control-Allow-Origin", origin); response.setHeader("Access-Control-Allow-Methods", "PUT"); response.setHeader("Access-Control-Allow-Credentials", "true"); @@ -127,13 +126,20 @@ protected void doOptions(HttpServletRequest request, HttpServletResponse respons log.debug("Tags HTTP-server received OPTIONS-request"); String origin = request.getHeader("Origin"); String accessControlRequestMethod = request.getHeader("Access-Control-Request-Method"); - - if (origin != null && accessControlRequestMethod != null && - (this.allowedOrigins.contains("*") || this.allowedOrigins.contains(origin)) + String accessControlRequestHeaders = request.getHeader("Access-Control-Request-Headers"); + + if ( + origin != null && + accessControlRequestMethod != null && + accessControlRequestHeaders != null && + (allowedOrigins.contains("*") || allowedOrigins.contains(origin)) && + accessControlRequestHeaders.equalsIgnoreCase(sessionIdHeader) ) { response.setHeader("Access-Control-Allow-Origin", origin); response.setHeader("Access-Control-Allow-Methods", "GET, PUT"); + response.setHeader("Access-Control-Allow-Headers", sessionIdHeader); response.setHeader("Access-Control-Allow-Credentials", "true"); + response.setHeader("Access-Control-Max-Age", "3600"); response.setStatus(HttpServletResponse.SC_OK); } else response.setStatus(HttpServletResponse.SC_FORBIDDEN); @@ -145,7 +151,7 @@ private Map getRequestBody(HttpServletRequest request) { return entrySet.stream() .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } catch (Exception e) { - log.info("Request to Tags HTTP-server failed"); + log.warn("Request to Tags HTTP-server failed", e); return null; } } diff --git a/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/instrumentation/browser/BrowserPropagationDataStorage.java b/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/instrumentation/browser/BrowserPropagationDataStorage.java index 62bfa61758..d173409a09 100644 --- a/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/instrumentation/browser/BrowserPropagationDataStorage.java +++ b/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/instrumentation/browser/BrowserPropagationDataStorage.java @@ -31,7 +31,7 @@ public BrowserPropagationDataStorage() { public void writeData(Map newPropagationData) { Map validatedData = validateEntries(newPropagationData); - if(!validateAttributeLength(validatedData)) { + if(!validateAttributeCount(validatedData)) { log.warn("Unable to write data: Data count limit was exceeded"); return; } @@ -42,6 +42,10 @@ public Map readData() { return new HashMap<>(propagationData); } + public int getStorageSize() { + return propagationData.size(); + } + public void updateTimestamp(long newTimestamp) { latestTimestamp = newTimestamp; } @@ -55,7 +59,7 @@ public long calculateElapsedTime(long currentTime) { return currentTime - latestTimestamp; } - private boolean validateAttributeLength(Map newPropagationData) { + private boolean validateAttributeCount(Map newPropagationData) { Set keySet = new HashSet<>(propagationData.keySet()); keySet.retainAll(newPropagationData.keySet()); //Add size of both maps and subtract the common keys diff --git a/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/instrumentation/browser/BrowserPropagationSessionStorage.java b/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/instrumentation/browser/BrowserPropagationSessionStorage.java index aa4a53fc7c..e30ecf6b18 100644 --- a/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/instrumentation/browser/BrowserPropagationSessionStorage.java +++ b/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/instrumentation/browser/BrowserPropagationSessionStorage.java @@ -67,7 +67,14 @@ public void cleanUpData(int timeToLive) { long currentTime = System.currentTimeMillis(); dataStorages.forEach((id, storage) -> { long elapsedTime = storage.calculateElapsedTime(currentTime) / 1000; - if(timeToLive < elapsedTime) dataStorages.remove(id); + if(timeToLive < elapsedTime) { + dataStorages.remove(id); + log.debug("Time to Live expired for the following session: " + id); + } + else { + int storageSize = storage.getStorageSize(); + log.debug("There are " + storageSize + " data entries stored in session: " + id); + } }); } diff --git a/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/instrumentation/context/propagation/BrowserPropagationUtil.java b/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/instrumentation/context/propagation/BrowserPropagationUtil.java index d6b7e920ff..1c8dcff6ff 100644 --- a/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/instrumentation/context/propagation/BrowserPropagationUtil.java +++ b/inspectit-ocelot-core/src/main/java/rocks/inspectit/ocelot/core/instrumentation/context/propagation/BrowserPropagationUtil.java @@ -15,7 +15,7 @@ /** * Class to regulate the currently used session-id-key. * The session-id-key is used to extract session-ids from http-request-headers to allow browser propagation. - * The session-id-key can change during runtime and needs to updated inside the PROPAGATION_FIELDS in ContextPropagationUtil. + * The session-id-key can change during runtime and needs to be updated inside the PROPAGATION_FIELDS in ContextPropagationUtil. */ @Slf4j @Component @@ -24,7 +24,7 @@ public class BrowserPropagationUtil { @Autowired private InspectitEnvironment env; @Getter - private static String sessionIdHeader = "Cookie"; + private static String sessionIdHeader = "Session-Id"; @PostConstruct public void initialize() { @@ -40,7 +40,7 @@ private void configEventListener(InspectitConfigChangedEvent event) { String oldSessionIdHeader = event.getOldConfig().getExporters().getTags().getHttp().getSessionIdHeader(); String newSessionIdHeader = event.getNewConfig().getExporters().getTags().getHttp().getSessionIdHeader(); - if(!oldSessionIdHeader.equals(newSessionIdHeader)) ContextPropagationUtil.setSessionIdHeader(newSessionIdHeader); + if(!oldSessionIdHeader.equals(newSessionIdHeader)) setSessionIdHeader(newSessionIdHeader); } } diff --git a/inspectit-ocelot-core/src/test/java/rocks/inspectit/ocelot/core/exporter/BrowserPropagationHttpExporterServiceIntTest.java b/inspectit-ocelot-core/src/test/java/rocks/inspectit/ocelot/core/exporter/BrowserPropagationHttpExporterServiceIntTest.java index c7b1d5ce6f..dcaf7f9330 100644 --- a/inspectit-ocelot-core/src/test/java/rocks/inspectit/ocelot/core/exporter/BrowserPropagationHttpExporterServiceIntTest.java +++ b/inspectit-ocelot-core/src/test/java/rocks/inspectit/ocelot/core/exporter/BrowserPropagationHttpExporterServiceIntTest.java @@ -36,7 +36,7 @@ public class BrowserPropagationHttpExporterServiceIntTest extends SpringTestBase private static String url; private static final String path = "/inspectit"; - private static final String sessionIDHeader = "Cookie"; + private static final String sessionIDHeader = "Session-Id"; private static final String allowedOrigin = "localhost"; @BeforeEach @@ -248,6 +248,7 @@ void verifyOptionsEndpointSuccessfulGet() throws IOException { HttpOptions optionsRequest = new HttpOptions(url); optionsRequest.setHeader("Origin", allowedOrigin); optionsRequest.setHeader("access-control-request-method", "GET"); + optionsRequest.setHeader("access-control-request-headers", sessionIDHeader); CloseableHttpResponse response = testClient.execute(optionsRequest); int statusCode = response.getStatusLine().getStatusCode(); @@ -261,6 +262,7 @@ void verifyOptionsEndpointSuccessfulPut() throws IOException { HttpOptions optionsRequest = new HttpOptions(url); optionsRequest.setHeader("Origin", allowedOrigin); optionsRequest.setHeader("access-control-request-method", "PUT"); + optionsRequest.setHeader("access-control-request-headers", sessionIDHeader); CloseableHttpResponse response = testClient.execute(optionsRequest); int statusCode = response.getStatusLine().getStatusCode(); @@ -270,7 +272,7 @@ void verifyOptionsEndpointSuccessfulPut() throws IOException { } @Test - void verifyOptionsEndpointWithMissingHeader() throws IOException { + void verifyOptionsEndpointWithMissingHeaders() throws IOException { HttpOptions optionsRequest = new HttpOptions(url); optionsRequest.setHeader("Origin", allowedOrigin); diff --git a/inspectit-ocelot-core/src/test/java/rocks/inspectit/ocelot/core/instrumentation/browser/BrowserPropagationDataStorageTest.java b/inspectit-ocelot-core/src/test/java/rocks/inspectit/ocelot/core/instrumentation/browser/BrowserPropagationDataStorageTest.java index b81179ad7a..3d715e7288 100644 --- a/inspectit-ocelot-core/src/test/java/rocks/inspectit/ocelot/core/instrumentation/browser/BrowserPropagationDataStorageTest.java +++ b/inspectit-ocelot-core/src/test/java/rocks/inspectit/ocelot/core/instrumentation/browser/BrowserPropagationDataStorageTest.java @@ -31,7 +31,7 @@ public class BrowserPropagationDataStorageTest extends SpringTestBase { Map headers; - private static final String sessionIdHeader = "Cookie"; + private static final String sessionIdHeader = "Session-Id"; private static final String sessionId = "test=83311527d6a6de76a60a72a041808a63;b0b2b4cf=ad9fef38-4942-453a-9243-7d8422803604"; @BeforeEach @@ -64,7 +64,7 @@ void verifyNoDataHasBeenWritten() { assertThat(dataStorage.readData()).isEmpty(); ctx.close(); - assertThat(dataStorage.readData()).isEmpty(); + assertThat(dataStorage.getStorageSize()).isZero(); assertThat(ContextUtil.currentInspectitContext()).isNull(); } @@ -135,7 +135,7 @@ void verifyAttributeCountLimit() { ctx.close(); assertThat(dataStorage.readData()).doesNotContainEntry("key1", "value321"); assertThat(dataStorage.readData()).doesNotContainEntry("keyABC", "valueABC"); - assertThat(dataStorage.readData().size()).isEqualTo(128); + assertThat(dataStorage.getStorageSize()).isEqualTo(128); } @Test diff --git a/inspectit-ocelot-core/src/test/java/rocks/inspectit/ocelot/core/instrumentation/context/propagation/BrowserPropagationUtilTest.java b/inspectit-ocelot-core/src/test/java/rocks/inspectit/ocelot/core/instrumentation/context/propagation/BrowserPropagationUtilTest.java index ef18331c5c..2008a08970 100644 --- a/inspectit-ocelot-core/src/test/java/rocks/inspectit/ocelot/core/instrumentation/context/propagation/BrowserPropagationUtilTest.java +++ b/inspectit-ocelot-core/src/test/java/rocks/inspectit/ocelot/core/instrumentation/context/propagation/BrowserPropagationUtilTest.java @@ -17,7 +17,7 @@ public class BrowserPropagationUtilTest { @InjectMocks BrowserPropagationUtil browserPropagationUtil; - final static String key = "Cookie"; + final static String key = "Session-Id"; @BeforeEach void setUp() { diff --git a/inspectit-ocelot-documentation/docs/tags/tags-exporters.md b/inspectit-ocelot-documentation/docs/tags/tags-exporters.md index 46674a8d63..6954f1e285 100644 --- a/inspectit-ocelot-documentation/docs/tags/tags-exporters.md +++ b/inspectit-ocelot-documentation/docs/tags/tags-exporters.md @@ -23,6 +23,11 @@ The Tags HTTP exporter does not provide any encryption of data and does not perf Thus, this server should not be exposed directly to the public in a production environment. It is recommended to set up a proxy in front of this server, which handles encryption and authentication. +Furthermore, please make sure to enable port forwarding, if you use servlet-containers like tomcat. +You should also set _-Dinspectit.expoters.tags.http.host=0.0.0.0_ as parameter in the tomcat start configuration. + +Additionally, make sure that your firewall is not blocking the HTTP-server address. + The server performs authorization with checking, whether the request origin is allowed to access the server. Additionally, every request has to provide a session-ID to access their own session data. @@ -33,8 +38,7 @@ The session-ID will be read from a specific request-header. The _**session-id-he to specify, which exact header should be used to read the session-ID from. The default-instrumentation of InspectIT will check the specified _session-id-header_ for a valid session-ID. -Thus, there is no additional configuration necessary to read session-ID from HTTP-headers. -The length of a session-ID is restricted to a minimum of 64 characters and a maximum of 512 characters. +Thus, there is no additional configuration necessary to read session-ID from HTTP-headers. Behind every session-ID, there is a data storage containing all data tags for this session, as long as they are enabled for browser propagation. These data storages can only be created, by sending requests to the target application, which the Ocelot-agent is attached to. @@ -42,12 +46,13 @@ You cannot create new data storages for example by pushing data into the HTTP-se If a request to the REST-API contains a session-ID, which does not exist in InspectIT, the API will always return 404. The HTTP-exporter can only store a specific amount of sessions, which can be configured in the configuration server. -Sessions will be deleted after their _time-to-live_ is expired. +Sessions will be deleted after their _time-to-live_ is expired. Their time-to-live will be reset everytime a request +the HTTP-server receives a successful request. #### Session limits There are some limitations for every session to prevent excessive memory consumption. -The length of the session-id is restricted to a minimum of 16 characters and a maximum of 512 characters. +The length of the session-ID is restricted to a minimum of 16 characters and a maximum of 512 characters. Furthermore, every session is able to contain up to **128 data keys**. The maximum length for data keys are **128 chars**. The maximum length for data values are **2048 chars**. @@ -58,16 +63,16 @@ restart, which also deletes all data currently stored in the server. The following properties are nested properties below the `inspectit.exporters.tags.http` property: -| Property | Default | Description -|----------------------|--------------|---| -| `.enabled` | `DISABLED` |If `ENABLED` or `IF_CONFIGURED`, the inspectIT Ocelot agent will try to start the exporter and HTTP server. -| `.host` | `127.0.0.1` |The hostname or network address to which the HTTP server should bind. -| `.port` | `9000` |The port the HTTP server should use. -| `.path` | `/inspectit` |The path on which the HTTP endpoints will be available. -| `.allowed-origins` | `["*"]` |A list of allowed origins, which are able to access the http-server. -| `.session-limit` | `100` |How many sessions can be stored in the server at the same time. -| `.session-id-header` | `Cookie` |The header, which will be read during propagation to extract the session-ID from -| `.time-to-live` | `300` |How long sessions should be stored in the server in seconds. +| Property | Default | Description | +|----------------------|--------------|-------------------------------------------------------------------------------------------------------------| +| `.enabled` | `DISABLED` | If `ENABLED` or `IF_CONFIGURED`, the inspectIT Ocelot agent will try to start the exporter and HTTP server. | +| `.host` | `127.0.0.1` | The hostname or network address to which the HTTP server should bind. | +| `.port` | `9000` | The port the HTTP server should use. | +| `.path` | `/inspectit` | The path on which the HTTP endpoints will be available. | +| `.allowed-origins` | `["*"]` | A list of allowed origins, which are able to access the http-server. | +| `.session-limit` | `100` | How many sessions can be stored in the server at the same time. | +| `.session-id-header` | `Session-Id` | The header, which will be read during propagation to extract the session-ID from | +| `.time-to-live` | `300` | How long sessions should be stored in the server in seconds. | The data of the HTTP exporter is stored inside internal data storages. Data tags will only be written to the storage, if they are enabled for [browser propagation](../instrumentation/rules.md#data-propagation). @@ -92,7 +97,7 @@ function getTags() { const url = "http://localhost:9000/inspectit"; xhr.open("GET", url); - xhr.withCredentials = true; // Send cookies with the request + xhr.setRequestHeader("Session-Id", "my-very-awesome-session-id"); xhr.onreadystatechange = () => { if (xhr.readyState === XMLHttpRequest.DONE) { @@ -115,7 +120,8 @@ function putTags() { const url = "http://localhost:9000/inspectit"; xhr.open("PUT", url); - xhr.withCredentials = true; // Send cookies with the request + xhr.setRequestHeader("Session-Id", "my-very-awesome-session-id"); + xhr.onreadystatechange = () => { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { @@ -147,7 +153,7 @@ info: All data tags created by this request, will be stored behind the provided session-ID as long as they are enabled for browser-propagation. To access specific data tags on the server via this API, requests will also need to contain the corresponding session-ID inside their header. - Which header should be used to read the session-ID, can be configured in the InspectIT configuration server. Per default, the Cookie-header will be used. + Which header should be used to read the session-ID, can be configured in the InspectIT configuration server. By default, the header "Session-Id" will be used. Data tags are only cached for a specific amount of time, which is also defined in the InspectIT configuration server. contact: name: Novatec-Consulting GmbH @@ -171,8 +177,17 @@ paths: Data tags will only be stored in the tags-server, if they are enabled for browser-propagation. Note that all data tags are only cached for a specific amount of time, which can be configured in the InspectIT configuration server. - security: - - session_id: [] + parameters: + - name: Session-Id + in: header + description: | + Custom header with the ID of the current session. The session-ID-header-name can be configured in the InspectIT configuration-server. + By default, "Session-Id" will be used as the session-ID-header-name. + + The length of the session-id is restricted to a minimum of 16 characters and a maximum of 512 characters. + required: true + schema: + type: string responses: '200': description: Success - Response contains all current data tags @@ -188,7 +203,7 @@ paths: '400': description: Failure - Missing session-ID-header '403': - description: Forbidden - Not allowed Origin header + description: Forbidden - Not allowed CORS headers '404': description: Failure - Session-ID not found in session-ID-header put: @@ -201,8 +216,17 @@ paths: It is not possible to create new data tag storages through this API, but only within the InspectIT-java-agent, by sending request to the target application. Note that these new data tags also have to be enabled for browser-propagation as well as down-propagation in the InspectIT configuration server. - security: - - session_id: [] + parameters: + - name: Session-Id + in: header + description: | + Custom header with the ID of the current session. The session-ID-header-name can be configured in the InspectIT configuration-server. + By default, "Session-Id" will be used as the session-ID-header-name. + + The length of the session-id is restricted to a minimum of 16 characters and a maximum of 512 characters. + required: true + schema: + type: string requestBody: description: data tags should be written or overwritten required: true @@ -221,7 +245,7 @@ paths: '400': description: Failure - Invalid request body or missing session-ID-header '403': - description: Forbidden - Not allowed Origin header + description: Forbidden - Not allowed CORS headers '404': description: Failure - Session-ID not found in session-ID-header options: @@ -239,6 +263,11 @@ paths: required: true schema: type: string + - name: Access-Control-Request-Headers + in: header + required: true + schema: + type: string responses: '200': description: Success - Pre-flight response with CORS headers @@ -249,22 +278,14 @@ paths: Access-Control-Allow-Methods: schema: type: string + Access-Control-Allow-Headers: + schema: + type: string Access-Control-Allow-Credentials: schema: type: boolean '403': description: Forbidden - Missing required headers -components: - securitySchemes: - session_id: - type: apiKey - description: | - ID of the current session. The session-ID-header-name can be configured in the InspectIT configuration-server. - Per default, the Cookie-Header will be used as the session-ID-header. - - The length of the session-id is restricted to a minimum of 16 characters and a maximum of 512 characters. - name: Cookie - in: header externalDocs: description: More information about the Exporter API url: https://inspectit.github.io/inspectit-ocelot/docs/tags/tags-exporters