├── appveyor.yml
├── LICENSE.md
├── README.md
└── code
└── ReverseProxy.cs
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: '{build}'
2 | build_script:
3 | - cmd: build.cmd
4 | artifacts:
5 | - path: out\*.*
6 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 1998 - 2009, Paul Johnston & Contributors
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | 3. Neither the name of the copyright holder nor the names of its contributors
15 | may be used to endorse or promote products derived from this software without
16 | specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | IIS Reverse Proxy
3 | =================
4 |
5 | [](https://ci.appveyor.com/project/kveretennicov/iisproxy/branch/master)
6 |
7 | By Paul Johnston, http://pajhome.org.uk/
8 |
9 | This is a simple C# script to add reverse proxy functionality to the IIS web
10 | server. It has been very useful for hosting TurboGears applications behind
11 | IIS, and could be used for other frameworks like Ruby on Rails and Django.
12 |
13 |
14 | Using The Script
15 | ----------------
16 |
17 | 1. Create the directory you want to be proxied, within the webroot. We'll call
18 | this the "proxy directory".
19 | 2. Copy the "bin" subdirectory from the IIS proxy souce into the proxy
20 | directory.
21 | 3. In IIS Manager, locate the proxy directory and select "Properties"
22 | 4. In the "Directory" tab, under "Application Settings", select "Create"
23 | 5. Click "Configuration"
24 | 6. In the "Mappings" tab, under "Wildcard application maps", select "Insert"
25 | 7. Enter "C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\aspnet_isapi.dll"
26 | 8. Uncheck "Verify that file exists"
27 | 9. Click "Ok" three times to return to IIS Manager
28 | 10. In the proxy directory, create a file web.config with the following
29 | content, replacing {URL} with the URL of the back-end site. Note that the
30 | URL must not have any path component, or a trailing slash.
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | Directory Layout
45 | ----------------
46 |
47 | The directory layout on the back-end site must exactly match that on the
48 | front-end site. For example, if the front-end site is configured so that
49 | http://front/mydir is proxied to http://back/ then a request for
50 | http://front/mydir/myfile is proxies to http://back/mydir/myfile
51 | The reason for this is to avoid the proxy doing any link rewriting. While
52 | some proxies attempt this, I've found it unreliable. Reconfiguring the
53 | back-end is a better option.
54 |
55 |
56 | User Identity
57 | -------------
58 |
59 | By default, the script uses its default credentials for authentication to the
60 | back-end. If the directory is configured to use Windows authentication and
61 | impersonation, the script will authenticate to the back-end with the identity
62 | of the client user. Otherwise, it will be the user the script is running as.
63 |
64 | There is an option to enable a different authentication model.
65 |
66 |
67 |
68 | If this option is specified, the script with present basic authentication to
69 | the back-end. The user name will be the user the script is running as, and
70 | the password the value specified. This can be useful for fronting a legacy
71 | application with a single sign-on interface.
72 |
73 | The also adds the current user name into the back-end request, as the
74 | Remote-User header. Relying on this for security can carry some risks - a
75 | header is potentially spoofable. The back-end application must be deployed so
76 | it can only be accessed from trusted sources, usually by making it only listen
77 | on the 127.0.0.1 interface.
78 |
79 |
80 | Tracing Redirects
81 | -----------------
82 |
83 | To aid debugging, the script has the ability to record redirect locations
84 | generated by the back end. These are recorded in the event log, in the
85 | application section and with the source "iisproxy". To enable this option, add
86 | the following to the appSettings:
87 |
88 |
89 |
90 | iisproxy may need to be given permission to access the error log. If you
91 | encounter problems, see http://support.microsoft.com/kb/329291
92 |
93 | Why IIS?
94 | --------
95 |
96 | Serving behind IIS is useful for IIS-specific features, and also if an
97 | existing server is already using IIS. I found the IIS implementation of
98 | integrated Windows authentication to be very reliable, while mod_auth_sspi
99 | for Apache has some problems, including issues with inter-forest trusts.
100 |
101 |
102 | Aims of Script
103 | --------------
104 |
105 | The script aims to be simple and to get in the way as little as possible. It
106 | does not do any link rewriting. The proxy passes nearly everything in both
107 | directions, including POST data, headers (e.g. Content-Type), cookies, etc.
108 |
109 |
110 | Credits
111 | -------
112 |
113 | This script has taken inspiration from two existing scripts:
114 |
115 | 1. Simple HTTP Reverse Proxy with ASP.NET and IIS, by Vincent Brossier
116 | (http://www.123aspx.com/redir.aspx?res=32037)
117 |
118 | 2. IIS Reverse Proxy, by John Pierce, john@pierce.name
119 | (no longer online)
120 |
--------------------------------------------------------------------------------
/code/ReverseProxy.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Configuration;
3 | using System.Web;
4 | using System.Net;
5 | using System.Text;
6 | using System.IO;
7 | using System.Diagnostics;
8 |
9 | namespace ReverseProxy
10 | {
11 | public class ReverseProxy: IHttpHandler
12 | {
13 | EventLog event_log;
14 | public ReverseProxy()
15 | {
16 | // Create the event logging stream
17 | if(ConfigurationManager.AppSettings.Get("traceRedirect") != null)
18 | {
19 | string source = "iisproxy";
20 | if (!EventLog.SourceExists(source)) {
21 | EventLog.CreateEventSource(source, "Application");
22 | }
23 | event_log = new EventLog();
24 | event_log.Source = source;
25 | }
26 | }
27 |
28 | public void ProcessRequest(HttpContext context)
29 | {
30 | // Create the web request to communicate with the back-end site
31 | string remoteUrl = ConfigurationManager.AppSettings["ProxyUrl"] +
32 | context.Request.Path;
33 | if (context.Request.QueryString.ToString() != "")
34 | remoteUrl += "?" + context.Request.QueryString;
35 | HttpWebRequest request = (HttpWebRequest) WebRequest.Create(remoteUrl);
36 | request.AllowAutoRedirect = false;
37 | request.Method = context.Request.HttpMethod;
38 | request.ContentType = context.Request.ContentType;
39 | request.UserAgent = context.Request.UserAgent;
40 | string basicPwd = ConfigurationManager.AppSettings.Get("basicPwd");
41 | request.Credentials = basicPwd == null ?
42 | CredentialCache.DefaultCredentials :
43 | new NetworkCredential(HttpContext.Current.User.Identity.Name, basicPwd);
44 | request.PreAuthenticate = true;
45 | // The Remote-User header is non-ideal; included for compatibility
46 | request.Headers["Remote-User"] = HttpContext.Current.User.Identity.Name;
47 | foreach(String each in context.Request.Headers)
48 | if (!WebHeaderCollection.IsRestricted(each) && each != "Remote-User")
49 | request.Headers.Add(each, context.Request.Headers.Get(each));
50 | if (context.Request.HttpMethod == "POST")
51 | {
52 | Stream outputStream = request.GetRequestStream();
53 | CopyStream(context.Request.InputStream, outputStream);
54 | outputStream.Close();
55 | }
56 |
57 | HttpWebResponse response;
58 | try
59 | {
60 | response = (HttpWebResponse) request.GetResponse();
61 | }
62 | catch(System.Net.WebException we)
63 | {
64 | response = (HttpWebResponse) we.Response;
65 | if(response == null)
66 | {
67 | context.Response.StatusCode = 13;
68 | context.Response.Write("Could not contact back-end site");
69 | context.Response.End();
70 | return;
71 | }
72 | }
73 |
74 | // Copy response from server back to client
75 | context.Response.StatusCode = (int) response.StatusCode;
76 | context.Response.StatusDescription = response.StatusDescription;
77 | context.Response.ContentType = response.ContentType;
78 | if(response.Headers.Get("Location") != null)
79 | {
80 | if(ConfigurationManager.AppSettings.Get("traceRedirect") != null)
81 | event_log.WriteEntry("Back-end redirecting to: " + response.Headers.Get("Location"), EventLogEntryType.Information);
82 | string urlSuffix = response.Headers.Get("Location");
83 | if(urlSuffix.ToLower().StartsWith(ConfigurationManager.AppSettings["ProxyUrl"].ToLower()))
84 | urlSuffix = urlSuffix.Substring(ConfigurationManager.AppSettings["ProxyUrl"].Length);
85 | context.Response.AddHeader("Location", context.Request.Url.GetLeftPart(UriPartial.Authority) + urlSuffix);
86 | }
87 | foreach(String each in response.Headers)
88 | if(each != "Location" && !WebHeaderCollection.IsRestricted(each))
89 | context.Response.AddHeader(each, response.Headers.Get(each));
90 | CopyStream(response.GetResponseStream(), context.Response.OutputStream);
91 | response.Close();
92 | context.Response.End();
93 | }
94 |
95 | static public void CopyStream(Stream input, Stream output) {
96 | Byte[] buffer = new byte[1024];
97 | int bytes = 0;
98 | while( (bytes = input.Read(buffer, 0, 1024) ) > 0 )
99 | output.Write(buffer, 0, bytes);
100 | }
101 |
102 | public bool IsReusable
103 | {
104 | get
105 | {
106 | return true;
107 | }
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------