loic-sharma / BaGet

A lightweight NuGet and symbol server
https://loic-sharma.github.io/BaGet/
MIT License
2.62k stars 676 forks source link

Cannot push nuget packages to BaGet behind an IIS Reverse Proxy #276

Open marcBalta opened 5 years ago

marcBalta commented 5 years ago

Describe the bug

I'm using BaGet inside a windows docker image. IIS 10 is used as a reverse proxy. This is the web.config file used to proxy queries with the pattern domain.de/nuget/ to the BaGet server which is running in a Windows Docher container on another machine with the IP 192.168.178.2.

         <rule name="nuget" stopProcessing="true">
                    <match url="nuget/(.*)" />
                    <action type="Rewrite" url="http://192.168.178.2:5555/{R:1}" />
                    <serverVariables>
                        <set name="HTTP_FORWARDED" value="for={REMOTE_ADDR};by={LOCAL_ADDR};host=&quot;{HTTP_HOST}&quot;;proto=&quot;https&quot;" />
            <set name="HTTP_X_FORWARDED_HOST" value="{HTTP_HOST}" />
            <set name="HTTP_X_FORWARDED_SCHEMA" value="https" />
            <set name="HTTP_X_FORWARDED_PROTO" value="https" />
                    </serverVariables>
                </rule>

This is my baget.env :

ApiKey=NUGET-SERVER-API-KEY
PathBase=/nuget
Storage__Type=FileSystem
Storage__Path=c:\packages
Database__Type=Sqlite
Database__ConnectionString=Data Source=C:\packages\baget.db
Search__Type=Database

When Im running however the nuget push command it uses the internal IP address (the url of docker container) and not the one of the proxy.

$ dotnet nuget push  -s https://proxydomain.de/nuget/v3/index.json -k NUGET-SERVER-API-KEY package.nupkg
info : Pushing package.nupkg to 'https://192.168.178.2:5555/api/v2/package'...
info :   PUT https://192.168.178.2:5555/api/v2/package/
info : An error was encountered when fetching 'PUT https://192.168.178.2:5555/api/v2/package/'. The request will now be retried.
info : A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond
info :   PUT https://192.168.178.2:5555/api/v2/package/
info : An error was encountered when fetching 'PUT https://192.168.178.2:5555/api/v2/package/'. The request will now be retried.
info : A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond
info :   PUT https://192.168.178.2:5555/api/v2/package/
error: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond

The angular UI isnt running at all. Thats seems to be however the typical problem with the base element not set correctly.

Roemer commented 5 years ago

I got the same issue (with an nginx reverse proxy).

marcBalta commented 5 years ago

In my opionion the problem is that BaGet doesnt configure the Forward Header Engine to use also XForwardedHost:

  var fordwardedHeaderOptions = new ForwardedHeadersOptions
            {
                ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost
            };
  app.UseForwardedHeaders(fordwardedHeaderOptions);
Roemer commented 5 years ago

Pitty. Is there any workaround possible? This is pretty much a blocker as it is not possible to use a nginx to forward to baget in a docker container.

marcBalta commented 5 years ago

The workaround could be maybe to rewrite the domain in the http repsonses. But I didnt try that. If you got that working im keen to hear about your solution.

Roemer commented 5 years ago

It seems that those headers were once added, see https://github.com/loic-sharma/BaGet/pull/65/files I guess they were missed while some migration took place and now they're gone. I am currently testing if that would fix that issue.

marcBalta commented 5 years ago

Ok interesting. But the important header is missing, which is XForwardedHost. This contains contains the reverse proxy domain.

Roemer commented 5 years ago

yes, basically

services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
});

is missing.

Roemer commented 5 years ago

Ah, ForwardedHeaders.XForwardedHost should probably be added as well.

Roemer commented 5 years ago

Well that part seems to work now. Now I also need to figure out how to change the base-path (I am running it in /nuget/ subfolder) when running from a docker container.

Roemer commented 5 years ago

There is a app.UsePathBase(options.PathBase); in Startup.cs but I cannot figure out how that options object is filled.

marcBalta commented 5 years ago

Cool. Will it be part of the next release? In IIS I used a rewrite rule to solve this problem. It seemed that this made the nuget protocol working. But I didnt came so far to say that for sure. If not the following could help:

  if (Configuration["URL_BASE_PATH"] != null)
            {
                app.Use(async (ctx, next) =>
                {
                    ctx.Request.PathBase = Configuration["URL_BASE_PATH"];// "/schlemihl";
                    await next.Invoke();
                });
            }

URL_BASE_PATH is than an environment variable which has to be set from outside.

For the angular UI, however, there has to be done some more work:

marcBalta commented 5 years ago

Well actually for my asp.net core apps UseBapthBase didnt work.

Roemer commented 5 years ago

Adding

services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;
});

to IServiceCollectionExtensions.cs and

string basePath = Environment.GetEnvironmentVariable("ASPNETCORE_BASEPATH");
if (!string.IsNullOrEmpty(basePath))
{
    app.Use(async (context, next) =>
    {
        context.Request.PathBase = basePath;
        await next.Invoke();
    });
    app.UsePathBase(options.PathBase);
}

to Startup.cs

Seems to fix the api entirely when I then provide the environment variable ASPNETCORE_BASEPATH in the docker container. The React UI still does not work.

marcBalta commented 5 years ago

Yeah I know. Please mind https://github.com/loic-sharma/BaGet/issues/276#issuecomment-499067303

Roemer commented 5 years ago

Yup but I don't know React. It is easy to hardcode the base path into it but it should be something dynamic so it can be controlled when running the docker container.

marcBalta commented 5 years ago

And will your changes be part of the next release?

Roemer commented 5 years ago

That you need to ask the author of this project ;) I could create a PR for it. I pushed a docker container with my changes here: https://hub.docker.com/r/roemer/baget In case you want to test it as well.

marcBalta commented 5 years ago

A PR would be great. Thx!

Roemer commented 5 years ago

Would be great to have the frontend fixed as well. Let's see if @loic-sharma has some hints / comments.

Roemer commented 5 years ago

Just for your info, I switched over to Nexus as that one works seamlessly with subdomains and reverse proxies.

marcBalta commented 5 years ago

Thx for the hint. I already switched to Nuget.Server but didnt had the time to put it into docker and behind a reverse proxy. I think I will have a look at nexus.

DoCode commented 5 years ago

@roman, @marcBalta thanks for the hints! Probably we should solve the root problem here.

Roemer commented 5 years ago

Yes that would be great. The code discussed above fix the issue that the headers passed from the reverse proxy are not handled correctly. With that, it generally works behind a reverse proxy. The second issue is that the application should be able to run in a subfolder. It seems to be easy to add a hardcoded subpath on build time but I have no idea how to do that on runtime. That needs some investigation.

loic-sharma commented 5 years ago

Hello, I apologize for the long response time! It seems like there's two problems:

  1. BaGet doesn't support reverse proxies well. This is a complete blocker and should be fixed ASAP
  2. BaGet's UI doesn't work well in such scenarios. This is annoying, but fixing this is more a "nice to have"

To fix the first problem, it seems that ForwardedHeaders.XForwardedHost option should be added to this line. Adding this option has security implications (see CVE-2018-0787), so we should add configs to BaGet to lock down forwarding. I suggest we add the following configs:

{
  ...

  "ForwardHeaders": {
    "Include": ["XForwardedFor", "XForwardedProto", "XForwardedHost" ],

    "AllowedHosts": [ "*.contoso.com" ],
    "KnownNetworks": [ "10.0.0.1" ],
    "KnownProxies": [ "10.0.0.1" ],
  },

  ...
}

These would map to their equivalent ASP.NET Core configurations. Does this seem like a good approach?

/cc @marcBalta @Roemer @DoCode

Roemer commented 5 years ago

@loic-sharma Yes you're right, these are two separate issues. The ForwardedHeaders.XForwardedHost should fix the first issue. The second is (in my opinion) not only just a nice to have. Usually when using a reverse proxy, you want to redirect multiple applications to different servers. For example:

nginx/app1 => appserver/app1
nginx/jenkins => buildserver/jenkins
nginx/nexus => nexusserver/nexus

Without the possibility to host an application under a subpath, it is (as far as I know) not possible to use it in a normal reverse proxy scenario.

loic-sharma commented 5 years ago

I see, that makes sense. Thanks for the information

szwenni commented 5 years ago

Hi

If the topic is still up to anyone I added full PathBase support. Refer to basePathSupport in my fork. It basically adds a new option to BaGetOptions called HostUrl which allows to specify the AllowedHosts value. As second part it rewrites all values in react to match the PathBase. This is done with a placeholder as homepage value in package.json and in config.tsx. For development use you have to specify the values by manually. For proxying with nginx keep in mind to set X-Forwarded-Host to $host:$server_port

@loic-sharma I will create a pull request for that.

chrisbecke commented 4 years ago

This is an IIS configuration issue not a BaGet issue. The first setting you need to check on your re-write server is the server level setting: system.webServer/proxy: preserveHostHeader. If this is set "True" then the request "Host" header will be passed through to the destination server. And you don't need to worry about X-Forwarded or any of that confusing misdirection. If it is set to "False" then ARR will rewrite the Host header to reflect the host hame of the server is is redirecting the request to. i.e. 192.168.etc. Usually not what you want. One way to fix this is clearly at this level but as the system.webServer/proxy preserverHostHeader setting applies server wide that might have unintended side effects if the server is hosting other loads making use of the proxy. In which case, a simple rewrite of the rewrite rules has you covered:

So, first add "HTTP_HOST" to the list of Allowed Server Variables, then ammend your rewrite rule to have a condition and server variable rule like this :-

            <rules>
                <rule name="test" stopProcessing="true">
                    <match url="(.*)" />
                    <conditions>
                        <add input="{HTTP_HOST}" pattern="(.*)" />
                    </conditions>
                    <serverVariables>
                        <set name="HTTP_HOST" value="{C:1}" />
                    </serverVariables>
                    <action type="Rewrite" url="http://example.com/{R:1}" />
                </rule>

As conditions are always processed before the rules are triggered, they captures the "Host" header as {C:1}, and then serverVariables are applied hopefully after ARR and Rewrite have done their other modifications, where we set it back to the original - passed in - value.

Again. No need to mess around with non standard X-Forwarded etc. headers.

trgiangvp3 commented 4 years ago

This is an IIS configuration issue not a BaGet issue.

This is the BaGet issue. When you run a push command from CLI, nuget.exe will request for a config json file that contains all necessaries urls. It then cache that config file at \AppData\Local\NuGet\v3-cache\<some random string>$_nuget.devmoba.com_v3_index.json and use the settings in that file to continue the process (of pushing packages). Changing some IIS config will definitely not help you to change this file 's content.

So, my work around is manually change all the URLs in that file to http://my.company.name instead of http://locahost:xxxx Hopefully the next release will let us change this by a setting line.

evgenyvalavin commented 3 years ago

+1

evgenyvalavin commented 3 years ago

Found a WA: Use outbound rule rewrite:

 <outboundRules>
       <rule name="Rewire internal host IP" preCondition="" patternSyntax="ECMAScript">
                <match filterByTags="None" pattern="https:\/\/IP:PORT\/" negate="false" />
                <action type="Rewrite" value="https://example.com/" />
       </rule>
</outboundRules>
chrisbecke commented 3 years ago

This is the BaGet issue. ... Changing some IIS config will definitely not help you to change this file 's content.

You are correct in that, if an api is returning fully qualified urls, then it does take on the task of supporting different base urls itself.

However, "some IIS config" defiantly will help you change that files content. Response rewrite rules perform regex based replacements on outgoing content for specifically the purpose of fixing urls in response documents.