Authentication of RUM event post from browser

Hi,

We are working to implement RUM agent for application pages performance monitoring.
But, if i am not wrong the RUM agent is sending data from browser to APM server by publicly exposing in internet as below format ,

https://<apm_server_ip>:8200/intake/v2/rum/events

As apm server is hosted private cloud to our company, i believe it can cause a security threat.

Can you suggest how we can handle this ??

Regards,
Rijash

Hi,

There are multiple options for you depending on what you need.

  1. If you want to use authentication on the APM endpoint you can watch this issue: https://github.com/elastic/apm-server/issues/1718
  2. If you do not care about authentication and just want to keep the APM endpoint from being exposed you could run a proxy on your application server where you point the RUM agent to which just routes the traffic to the real APM server.
  3. if you want to combine that you could implement a proxy in your application which does the redirect but also checks that the sender is already authenticated in your application.

There might be more but this is what we came up with. We are going with number 3 for now while closely watching the issue above. We have a Java Spring webapp where we created an endpoint /app/apm/route which is secured by Spring Security so only authenticated users can send to it.

  • The RUM agent is pointed to https://host/app/apm/route
  • The Request Mapping of the Controller is defined as /app/apm/route/**
  • We read the requested path of the apm url
 String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
 String bestMatchPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
 PathMatcher pathMatcher = new AntPathMatcher();
 String finalPath = pathMatcher.extractPathWithinPattern(bestMatchPattern, path);
  • Then we redirect the Url
   private String buildTargetUrl(String path,HttpServletRequest req) {
      if(req.getQueryString() != null ) {
         return String.format("%s/%s?%s", SettingProvider.getApmServerUrl(),path,req.getQueryString());
      }else {
         return String.format("%s/%s", SettingProvider.getApmServerUrl(),path);
      }
   }
     private void forwardRequest(String path, HttpServletRequest req, HttpServletResponse resp) {
     final boolean isPost = ("POST".equals(req.getMethod()));
     HttpURLConnection conn = null;
     try {
     String targetUrl=buildTargetUrl(path,req);
     
     //initialize Connection
      final URL url = new URL(targetUrl);
      conn = (HttpURLConnection) url.openConnection();
      conn.setRequestMethod(req.getMethod());
      
      //copy HTTP request headers
      for(String header : Collections.list(req.getHeaderNames())) {
         for(String value : Collections.list(req.getHeaders(header))) {
            conn.addRequestProperty(header, value);
         }
      }

      //open connection
      conn.setUseCaches(false);
      conn.setDoInput(true);
      conn.setDoOutput(isPost);
      conn.connect();
      
      //copy HTTP Request Body
      if(isPost) {
         IOUtils.copy(req.getInputStream(), conn.getOutputStream());
      }

      //copy HTTP Response Status
      resp.setStatus(conn.getResponseCode());
      
      //Copy HTTP response headers
      for(var header : conn.getHeaderFields().entrySet()) {
         for(String value : header.getValue()) {
            if(header.getKey() != null) {
               resp.setHeader(header.getKey(), value);
            }
         }
      }
      
      //copy HTTP response Body
      if(HttpStatus.valueOf(conn.getResponseCode()).is2xxSuccessful()) {
         IOUtils.copy(conn.getInputStream(), resp.getOutputStream());
      }
  } catch (Exception e) {
      log.debug("failed to send APM data",e);
  }finally {
     if(conn != null) {
        conn.disconnect();
     }
  }

}

Hope this helps...

Best regards
Wolfram

1 Like

Hi Wolfram,

Thanks for your prompt response here.

As of now we have created the proxy for the APM server & planning to use the same in RUM agent configuration.

But, could you please help/guide us on the piece of configuration for sender authentication check.

What could be the configuration we need to put for the approach 3 ??

Regards,
Rijash

Hi Rijash,

This depends on the application you have. What Programming language do you use? What frameworks do you use?

If you already have the RUM agent running:

  • define a new endpoint in your application for the routing like https://host/myApp/apm/route
  • create controller for this endpoint. Important: Controller must capture all sub-Urls as we define the Url as https://host/myApp/apm/route but the Agent will call Urls like https://host/myApp/apm/route/intake/v2/rum/events
  • implement controller to parse the last part of the Url and to redirect the request headers, request body to the correct target Url
  • update authentication framework config to secure the endpoint
  • replace the serverUrl in the elasticApm.init() call by the newly implemented endpoint

Best regards
Wolfram