Because requests in Kentico are always coming from the firewall and all request are always HTTP this causes several problems for Kentico:
- Functionalities like IP Banning and GeoLocation for our contacts are not working because Kentico only sees the proxy servers IP Address.
- Because Kentico only receives HTTP requests, all links created by Kentico will will be http links. This causes additional redirects when clients request images or files from Kentico (which will be redirected to https again). Also browsers may complain about 'mixed content'
- Because all requests in Kentico arrive non-SSL, setting the 'Requires SSL' option on a page to 'Yes' causes a redirect loop.
The Solution
Most reverse proxy firewalls can add the X-Forwarded-For and x-Forwarded_Proto headers to the request making it possible to detect the orignal Ip address and the original protocol that arrived at the proxy. Using this information we can use the 'RequestEvents.Prepare.Execute' event in Kentico to fix the RequestContext.The following example code does exacly this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | using System; using System.Web; using System.Linq; using CMS; using CMS.DataEngine; using CMS.Base; using CMS.EventLog; using CMS.Helpers; // Registers the custom module into the system [assembly: RegisterModule(typeof(ReverseProxyModule))] class ReverseProxyModule : Module { // Module class constructor, the system registers the module under the name "ReverseProxyModule" public ReverseProxyModule() : base("ReverseProxy") { } /// <summary> /// Initialize the module. Bind to the corect event handler to handle reverse proxy requests /// </summary> protected override void OnInit() { base.OnInit(); // Assigns a handler called before each request is processed RequestEvents.Prepare.Execute += HandleReverseProxyRequests; } /// <summary> /// If Kentico is behind a reverse proxy, fix SSL and UserHostAddress settings in the context /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private static void HandleReverseProxyRequests(object sender, EventArgs e) { if (HttpContext.Current != null) { HandleUserHostAddress(); HandleIsSsl(); LogHeaders(); } } private static void LogHeaders() { try { if (RequestContext.CurrentQueryString.Contains("logheaders")) { var values = HttpContext.Current.Request.Headers.AllKeys.Select(e => $"{e} = {HttpContext.Current.Request.Headers[e]}"); EventLogProvider.LogInformation("ReverseProxyModule", "LogHeaders", string.Join("<br/>", values)); } } catch (Exception ex) { EventLogProvider.LogException("ReverseProxyModule", "LogHeaders", ex); } } /// <summary> /// Handles if the request was forwarded from a SSL Offloading proxy /// </summary> private static void HandleIsSsl() { try { // Gets the value from the X-Forwarded-Ssl header var xForwardedProto = HttpContext.Current.Request.Headers.Get("X-Forwarded-Proto"); RequestContext.IsSSL = false; // Checks if the original request used HTTPS if (string.Equals(xForwardedProto, "https", StringComparison.OrdinalIgnoreCase)) { RequestContext.IsSSL = true; URLHelper.SSLUrlPort = 443; } } catch (Exception ex) { EventLogProvider.LogException("ReverseProxyModule", "HandleIsSsl", ex); } } /// <summary> /// Provides Kentico with the correct Host Address if the original address is hidden by using a Reverse proxy /// </summary> private static void HandleUserHostAddress() { try { // Gets the value from the X-forwarded-for header and pass it to the request context var xForwardFor = HttpContext.Current.Request.Headers.Get("X-Forwarded-For"); if (!string.IsNullOrEmpty(xForwardFor)) RequestContext.UserHostAddress = xForwardFor.Split(',')[0]; } catch (Exception ex) { EventLogProvider.LogException("ReverseProxyModule", "HandleUserHostAddress", ex); } } } |
Also note the method 'LogHeaders' which ouputs all headers to the EventLog if the 'logheaders' querystring parameter is present. This proved to be usefull when debugging the headers and checking of the X-Forward-* headers are added by the proxy or not.
1 comment:
Hi thanks for posting thiss
Post a Comment