ASP.NET WebApi SelfHost service fails on HTTP URL registration - c#

I am trying to host an ASP.NET WebApi endpoint on an Azure worker role using the new Microsoft.AspNet.WebApi.SelfHost NuGet package. My worker's Run() code looks roughly like this:
// Endpoint is defined as in ServiceDefinition.csdef as
// HTTP, external port 8080, internal port 8080 (or 8081 - error both ways)
RoleInstanceEndpoint externalEndPoint =
RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["Endpoint"];
string baseAddress= String.Format("http://{0}", externalEndPoint.IPEndpoint);
var maxsize = 1024 * 1024;
var config = new HttpSelfHostConfiguration(baseAddress)
{
MaxBufferSize = maxsize, MaxReceivedMessageSize = maxsize
};
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// Create and open the server
var server = new HttpSelfHostServer(config);
server.OpenAsync().Wait();
// keep the worker thread alive
while (true)
Thread.Sleep(Timeout);
This works fine in the dev fabric, but when deploying to Azure, I get an AggregateException from the server.OpenAsync() call, containing the following exception stack:
[0] One or more errors occurred.
[1] HTTP could not register URL http://+:8081/. Your process does not have access rights to this namespace (see http://go.microsoft.com/fwlink/?LinkId=70353 for details).
[2] Access is denied
I'm just running a vanilla worker role and this seems to be the "hello world" of self-host...
The endpoint part of my ServiceDefinition.csdef looks like this:
<Endpoints>
<InputEndpoint name="Endpoint" protocol="http" port="8080" localPort="8081" />
</Endpoints>
The baseAddress that I get from the RoleEnvironment InstanceEndpoint looks legit - http://10.115.[X].[Y]:8081
I see the failure whether I use the same port/localPort (8080) or when I do a mapping, like the above.
It's clear that it's possible to host a conventional WCF service in a worker role in this way - is there any reason why ASP.NET WebApi SelfHost wouldn't work in this configuration?

By default, the RoleEntryPoint runs under a very minimal permission user account for security. As the error indicates, it is unable to reserve that port due to those permissions. You have two options here:
Run the Worker process as SYSTEM by adding the Runtime element to your role definition (i.e <Runtime executionContext="elevated"/>).
Create a startup script that runs elevated and reserves that port for you.
For playing around (and troubleshooting if it is a permission issue), doing #1 is a quick way to test it.
Edit: I seem to recall a permission issue with WCF and Windows Azure when doing wildcard reservations. It used to work fine when using the full hostname e.g.
host.AddServiceEndpoint(
typeof(IEchoService), new BasicHttpBinding(BasicHttpSecurityMode.None) { HostNameComparisonMode = HostNameComparisonMode.Exact }, "echo");

After half a dev-day experimenting with invoking netsh.exe from an elevated startup script to no avail, I gave up and ended up using the big hammer and taking Ryan's initial suggestion of running the entire worker role elevated:
<WorkerRole name="WorkerRole" vmsize="ExtraSmall">
<Runtime executionContext="elevated">
</Runtime>
</WorkerRole>
That solved all issues.
For reference, here is how I tried to allow HTTP registration for non-elevated user accounts (which never really worked):
In ServiceDefinition.csdef:
<Startup>
<Task executionContext="elevated" commandLine="startup\Install.cmd">
<Environment>
<Variable name="ENDPOINTPORT ">
<RoleInstanceValue xpath="/RoleEnvironment/CurrentInstance/Endpoints/Endpoint[#name='Endpoint']/#port" />
</Variable>
</Environment>
</Task>
</Startup>
<Endpoints>
<InputEndpoint name="Endpoint" protocol="http" port="8080" localPort="8080" />
</Endpoints>
And in the startup script (startup\Install.cmd in my case):
netsh.exe http add urlacl url=http://+:%ENDPOINTPORT%/api user=everyone listen=yes delegate=yes
This is basically the solution that was recommended by the good folks working on AspNetWebApi (just a shorter way of doing what they recommend here), but unfortunately it didn't work for me - while the netsh command executed successfully and I was able to verify that the urlacl on the URL I am self-hosting on (http://+:8080/api/) is allowed by \Everyone, I was still getting the same permission error. If anyone figures out how to make this work when running the worker-role non-elevated, please post!

Add reference 'System.ServiceModel' to your project and with the 'config.HostNameComparisonMode = System.ServiceModel.HostNameComparisonMode.Exact;' you don't get the administrator privileges exception (access denied).

Related

Azure cloud service embedded FTP server

I want to host an embedded FTP server inside an Azure cloud service worker role.
To provide passive access to the FTP server, it uses port range 20000-21000.
Inside the ServiceDefinition.csdef I define all needed ports (see screenshot).
The main problem is the huge number of ports. If I try to upload the service into the cloud I get the following error.
Validation error: Invalid number of input endpoints - current 1002,
max. 25
How can I get this work with cloud service?
Here is a solution based on Azure support answer.
You will need to define a public IP in the .cscfg file and upload it the cloud service.
<?xml version="1.0" encoding="utf-8"?>
<ServiceConfiguration serviceName="ILPIPSample" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="4" osVersion="*" schemaVersion="2014-01.2.3">
<Role name="WebRole1">
<Instances count="1" />
<ConfigurationSettings>
<Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="UseDevelopmentStorage=true" />
</ConfigurationSettings>
</Role>
<NetworkConfiguration>
<AddressAssignments>
<InstanceAddress roleName="WebRole1">
<PublicIPs>
<PublicIP name="MyPublicIP" domainNameLabel="WebPublicIP" />
</PublicIPs>
</InstanceAddress>
</AddressAssignments>
</NetworkConfiguration>
</ServiceConfiguration>
More info: https://learn.microsoft.com/en-us/azure/virtual-network/virtual-networks-instance-level-public-ip#manage-an-ilpip-for-a-cloud-services-role-instance
After that you can use nslookup to get the public IP assigned to the instance. If you have multiple instances, you need to change the 0 to 1, 2, 3 etc.
nslookup WebPublicIP.0.<Cloud Service Name>.cloudapp.net
Then you can open the local ports in Windows Firewall of the instance and you will be able to connect the local ports directly from the internet.
You can create a startup task to open the local ports in the cloud service firewall.
Following is an example of how to configure firewall rules. The startup task is executed every time the instance is rebooted/reimaged.
https://learn.microsoft.com/en-us/azure/cloud-services/cloud-services-startup-tasks-common#add-firewall-rules
Something like below:
netsh advfirewall firewall add rule name="TCP ports" protocol=TCP dir=in localport=1000-2000 action=allow
When a client connects to an FTP server using passive mode, it will make 2 connections.
One using port 21, and another for transferring data.
So it looks like you need to open a single port in ServiceDefinition.csdef and then create a port forwarding rule on the firewall (load balancer) to redirect all of the passive ports to that single port.
<Endpoints>
<InputEndpoint name="FTP2Azure.Command" protocol="tcp" port="21" localPort="9003" />
<InstanceInputEndpoint name="FTP2Azure.Passive" protocol="tcp" localPort="9002">
<AllocatePublicPortFrom>
<FixedPortRange max="21000" min="20000" />
</AllocatePublicPortFrom>
</InstanceInputEndpoint>
</Endpoints>
This is untested, but might help.

Why does console apps use proxy by default?

I am currently having some issues with class library that doesn't behave as its console app.
The purpose of the console app is to send messages to an azure queue, which it does without any problem, and can see in ressource monitor that it makes calls through our web proxy and to our azure queue. This is done by default, I haven't told it anyway that it should use this proxy.
The class library on the other hand, does the same thing, but does not use the proxy, and therefore not able to send its data.
Both projects are identical, in the way they make the call to send a message, but for some reason is the console app, which intention is only to send a message, and library which intention is the same, act differently - why does the console app try to use proxy, and how do i force the class library to forcefully use the proxy?
conclusion:
How do i force a Microsoft.ServiceBus.Messaging.QueueClient.send
to use a proxy and not port 443
Per my knowledge, It is impossible to set the proxy while using service bus client.
The only connection options for Service Bus client are as following:
HTTP - port 80
HTTPS - port 443
TCP - 9350 to 9354
For more information we can refer to:
ConnectivityMode Enum
In your class library, please set the connectivity mode to Http as below code and try again:
ServiceBusEnvironment.SystemConnectivity.Mode = ConnectivityMode.Http;
Similar thread as yours: Azure Service Bus working behind proxy
To answer my own question.
I resolved the issue by creating a proxy.config , and add it to my app.config.
proxy.config format:
<?xml version="1.0"?>
<defaultProxy enabled="true">
<proxy autoDetect="False" proxyaddress="http://<proxyaddress>:<port>" />
<bypasslist>
<add address="localhost" />
</bypasslist>
</defaultProxy>
and add the proxy.config into my app.config as a system.net configuration:
<system.net>
<defaultProxy configSource="proxy.config" />
</system.net>

send message to azure service bus queue from web api

I am trying to get started with Azure Service Bus queues. following this article
https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-get-started-with-queues
the only difference is that I am trying to do this from within a web api.
The error I get is:
No connection could be made because the target machine actively refused it 40.84.xxx.xx:443
I'd appreciate any help or pointers!
Note: Console app works just fine following the above guide.
Updated 7/24, this is the code in my action method:
try
{
var connectionString =
"Endpoint=sb://xxxxx-test.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=shared_access_key";
var queueName = "testqueue1";
var client =
QueueClient.CreateFromConnectionString(connectionString, queueName);
var message = new BrokeredMessage(eventMessage);
client.Send(message);
}
catch (Exception e)
{
//log exception
}
Update 7/25. I was able to make it work by setting defaultConfig entry as enabled in web.config:
<system.net>
<defaultProxy enabled="true"/>
</system.net>
No connection could be made because the target machine actively refused it 40.84.xxx.xx:443
Please check whether the outbound 443 port is blocked by your firewall.
Note: Console app works just fine following the above guide.
The default value of connectivity mode for Service Bus is AutoDetect. It will automatically selects between the Tcp and Http modes based on an auto-detection mechanism that probes whether either connectivity option is available for the current network environment. It maybe choose different modes for your Console App and Web API application. Try to set it to TCP explicitly in your Web API application before using the Service Bus Queue.
ServiceBusEnvironment.SystemConnectivity.Mode = ConnectivityMode.Tcp;
I was able to make it work by setting defaultConfig entry as enabled in web.config:
<system.net>
<defaultProxy enabled="true"/>
</system.net>

WCF service:400 bad request when accessing from local machine

I have a WCF Service hosted and the client is hosted on the same server. I try to show some error message via a dialog box. its working as expected when i try to perform some action which throws that error in server but its showing Bad request when i try to work on Local machine.
Can't post the configuration file. Client machine So :) . Hosting is IIS
I found that in the controllers catch block where i am calling the wcf service whenever we are having an exception we are setting
Respose.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(execption.Message);
Does this have anything to do ?
I added this setting in the web config and i am able to see the actual message in my local machine too
<httpErrors existingResponse="PassThrough" />
Is there any other way of doing it from coding part

ServiceHost is binding to multiple hostnames/base addresses

I'm running Windows 7 and Visual C# Express 2010.
I have the following rule in my ACL:
Reserved URL : http://www.example.com:8020/gamerecords/
User: Myricae\Dario
Listen: Yes
Delegate: No
SDDL: D:(A;;GX;;;S-1-5-21-3389095862-38437692-3014067205-1001)
Not that it really matters, but I also have an appropriate entry in my hosts file:
127.0.0.1 www.example.com
I'm trying to self-host a WCF service within a console application:
var baseAddress = new Uri("http://www.example.com:8020/gamerecords/");
using (ServiceHost host = new ServiceHost(typeof(Acme.Gaming.GameRecordsImpl), baseAddress))
{
...
}
I get the following exception:
Unhandled Exception: System.ServiceModel.AddressAccessDeniedException: HTTP could not register URL http://+:8020/gamerecords/. Your process does not have access rights to this namespace (see http://go.microsoft.com/fwlink/?LinkId=70353 for details). ---> System.Net.HttpListenerException: Access is denied
The reason is that ServiceHost tries to bind to any hostname (+), but the ACL rule only authorizes the current user for hostname www.example.com. If I change the rule to:
Reserved URL : http://+:8020/gamerecords/
User: Myricae\Dario
Listen: Yes
Delegate: No
SDDL: D:(A;;GX;;;S-1-5-21-3389095862-38437692-3014067205-1001)
everything works fine. However I don't think that ServiceHost should really try to bind to any hostname; instead it should only publish the service at www.example.com. Why WCF attempts to bind to multiple hostnames?.
I found a page which describes a similar issue.
Did you start your application via "Run-as-Administrator"?

Categories

Resources