Calling Bindings.Remove does not remove SSL cert from HTTP.sys - azure

The shortest version of my question is that calling ServerManager.Binding.Remove seems to remove a binding from IIS, but still leave it in HTTP.sys or wherever SSL bindings are set and breaks layers of my code further down.
I'm running an Azure Cloud Service that needs to use SNI to support multiple hostnames using SSL. Effectively what I'm doing is in OnStart removing the default binding using ServerManager.Binding.Remove(binding) and adding my own bindings using ServerManager.Binding.Add(binding). So for example:
ServerManager serverManager = new ServerManager();
Site site = serverManager.Sites[0];
// Add my site bindings.
foreach (string host in listOfHostsToBind)
{
X509Certificate2 cert = LookupCertificate(host.sslThumbprint);
var binding = site.Bindings.Add(":443:" + host, cert.GetCertHash(), "My");
binding.SetAttributeValue("sslFlags", 1); //Set SNI flag
}
// Remove the default binding
var bindingsToRemove = new List<Binding>();
foreach (Binding binding in site.Bindings)
{
if (binding.Protocol == "https" && Convert.ToInt64(binding.Attributes["sslFlags"].Value) != 1)
{
bindingsToRemove.Add(binding);
}
}
foreach (Binding binding in bindingsToRemove)
{
site.Bindings.Remove(binding);
serverManager.CommitChanges();
}
serverManager.CommitChanges();
What ends up happening is that the default IP:Port binding is removed from the list of IIS bindings, but it still shows up in the list of SSL bindings when I call netsh http show sslcert.
So, for example, here's the output from calling Get-WebBinding in Powershell. Notice that the default IP:Port binding is not there:
protocol bindingInformation sslFlags
-------- ------------------ --------
http 10.20.30.40:80: 0
https :443:myfirstaddedhost.com 1
https :443:mysecondaddedhost.com 1
Looks good, but it still doesn't work, because if I run netsh http show sslcert I get the following:
IP:port : 10.20.30.40:443
Certificate Hash : xxx
Application ID : {00000000-0000-0000-0000-000000000000}
Certificate Store Name : MY
...
Hostname:port : myfirstaddedhost.com:443
Certificate Hash : xxx
Application ID : {4dc3e181-e14b-4a21-b022-59fc669b0914}
Certificate Store Name : My
...
Hostname:port : mysecondaddedhost.com:443
Certificate Hash : xxx
Application ID : {4dc3e181-e14b-4a21-b022-59fc669b0914}
Certificate Store Name : My
...
Why would the SSL Cert binding still be there if I successfully removed the binding from IIS using ServerManager?

Turns out that configuring the role for Remote Desktop from the Azure Portal was adding the binding. More specifically, updating the Certificate configuration for the role (which happens as part of RDP config) is causing it. This meant that it worked until I went in via RDP to check whether it was working at which point it would start to fail. Of course, genius that I am I was trying to be methodical and do things in the same order every time, which meant I was configuring remote desktop before actually attempting a request, so from my perspective it looked like it was failing from the beginning. It was only when I tried things in the opposite (running requests before configuring RDP) that it started to work.
You can use netsh http delete sslcert to delete the binding and it does not affect your ability to log in via RDP to that instance.
When you configure RDP it calls the RoleEnvironment.Changing and RoleEnvironment.Changed events, but unfortunately when those events are called the binding has not been created yet, so there's not an obvious place where you could use netsh http delete sslcert to delete the binding in code.
I don't know that this is an "answer" exactly. It means I still have an issue where configuring an Azure Instance for RDP or changing the cert configuration breaks my SNI bindings. For my organization this is OK because there are only a couple people with enough permissions to configure RDP and they can be trained to explicitly delete the new binding if they need to use RDP. I'll follow up here if I figure out a way to prevent this altogether.

Related

Generic domain part with fixed subdomain using Caddy and auto SSL?

I'd like to setup a Caddy server where the subdomain is static but the domain part is "wildcard", such as "api.*"
From my understanding of Caddy, the wildcard is possible for one part of the full domain (*.domain.com matches bar.domain.com but not foo.bar.domain.com).
Moreover, this configuration would automatically create a SSL certificates (which Caddy does in general, but I'm not sure here) for any new DNS entry that points to my server with a domain starting with "api.*".
The "*" here would be the domain directly, not any subdomain (it would work for api.domain.com, but not for api.foo.domain.com).
Is this something possible using a simple Caddy command (such as api.* { ... }, which I tried without luck), or does it need a more complex implementation?
Thank you for your help!
I found a working solution with the help of the Caddy Community.
Here's the code :
{
on_demand_tls {
ask https://static.site.com/domain/verify
interval 2m
burst 5
}
}
static.site.com {
...
}
:443 {
tls {
on_demand
}
// Your custom config, for instance:
reverse_proxy * ...
}
The nifty part is the tls { on_demand } part for your generic HTTPS, which will create a certificate automatically. But, this can be abused by anyone that points one of their DNS entry to your server.
So to avoid that, the Caddy community highly recommends you to set a on_demand_tls that will query an endpoint, and allow the SSL certificate to be created only if that endpoint returns true.
NOTE: The ask is a GET request that DO NOT FOLLOW redirects! Anything but a 200 status code will be considered a failure, even a 3xx!
The ask url will have the ?domain appended and will allow you to verify that domain against your logic, such as custom value in the domain like "starting by static.*", and verify that the domain exists in your database (for example).
If your URL already contains some query parameter, don't worry, Caddy is clever enough to add them. (https://static.site.com/domain/verify?some=query will become https://static.site.com/domain/verify?some=query&domain={domain}.
Caddy support https for the ask parameter, and that URL can also be external with no problems at all (no need for localhost or local server configuration).
I met the same problem, and after 1 day's stucking, here is my solution:
Assuming the site name is: site.com, and I want caddy handle these domains for me:
a.dot.site.com
b.dot.site.com
c.dot.site.com
a.eth.site.com
b.eth.site.com
c.eth.site.com
1.make sure you set SSL access available. e.g. via cloudflare:
2.set the A address pointing to your Caddy server's IP.
2.Caddy file should looks like:
# the key is: you have to list all the patterns for your multiple subdomains
*.site.com *.eth.site.com *.dot.site.com {
reverse_proxy 127.0.0.1:4567
log {
output file /var/log/access-wildcard-site.com.log
}
tls {
dns cloudflare <your cloud flare api key>
}
}

Azure Application Gateway Redirection from empty hostname

I have created an Application Gateway that needs to fulfill the working of my previous Resource (F5).
As a listener I use a hostname: hostname.stackoverflow.com that listens on 443
As a Http Setting I am using a specific port being 4443
As a BackEnd pool I use the URL/FQDN of my dev VM.
This totally works If i create a VM in the VNET and add "hostname.stackoverflow.com" to the hosts file with the ip of the application gateway.
Now I want to get a little further and add paths to my Application Gateway.
The goal is that if I use "hostname.stackoverflow.com" I need to redirect this to "Hostname.stackoverflow.com/login.aspx?guestlogin".
As far I have tried the following.
Add the "/login.aspx?guestLogin" to the HTTPS settings like this.
When I try this inside my VM. The URL changes but the path that I added there was not added in the right way, This is what I got:
So That made me think override backend path is maybe not the right way to do this.
Wanted To create a Redirection Rule That will redirect my "hostname.stackoverflow.com" to the "hostname.stackoverflow.com/login.aspx?guestLogin" But in the settings of the Application Gateway I need to provide a source path (meaning: I can not redirect from an empty hostname to a new url I think)
I am very new to Azure and even more new to the Application Gateway. Is there something that I did wrong. Is there a better way to do this ?
The iRule that I need to get in Application Gateway is as followed.
if { [string tolower [HTTP::host]] equals "hostname.stackoverflow.com" } {
if {[HTTP::path] eq "/"} {
HTTP::redirect "login.aspx?guestLogin"
}
elseif {[string tolower [HTTP::uri]] starts_with "/login.aspx?id="} {
set tail [string range [HTTP::uri] 12 end]
HTTP::redirect "login.aspx?guestLogin&$tail"
}
pool default.pool
}

Configure ASP.NET Core Module to host on https instead of http for IdentityServer 4

The problem I have is when I try to host an implementation of IdentityServer4 on a IIS server that uses SSL. (Full SSL Strict)
When running my application on Kestrel alone with SSL activated it works fine and the IssuerUri and Discovery Endpoints for IdentityServer uses SSL binding. However when I host it behind ASP.NET Core Module it hosts it on http://localhost:{random port}, which in turn generates IssuerUri and Endpoints for Identityserver that are not https.
I have tried the following without success:
Made sure that I have a valid certificate on the IIS website for the
https binding and removed binding on port 80
Tried changing the environmentvariable ASPNETCORE_URLS in web.config
to point to a https address.
Tried rewrite and redirect rules in web.config.
Looked for settings on IISOptions (used by .UseIISIntegration())
in my startup class to bind to a specific url or change protocol.
Tried to find a similar settings like RequireSSL (IdentityServer 3) or
RequireHttpsMetadata in IdentityServer4.
Changed the IssuerUri in IdentityServer Options in startup class
hoping that it might also update the other endpoints.
I have probably missed something very obvious but right now I do not have a clue on what that might be.
Any help from the community would be greatly appreciated :-)
Program.cs code
public static void Main(string[] args)
{
Console.Title = "IdentityServer";
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("kestrelHosting.json", optional: true)
.AddCommandLine(args)
.Build();
var host = new WebHostBuilder()
.UseConfiguration(config)
.UseKestrel(options =>
{
// options.ThreadCount = 4;
options.NoDelay = true;
options.UseHttps("VismaCert.pfx", "Visma123");
//options.UseConnectionLogging();
})
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
AspNetCoreModule is an SSL terminator, it will not communicate with Kestrel over HTTPS. What it does do is forward the original scheme via a header so you can use it when generating urls/links. There is a ForwardedHeaders middleware included by default with UseIISIntegration that will take these headers and apply them to the request fields. However, there are situations where the headers cannot be processed by the default settings. There are a bunch of references here: https://github.com/aspnet/Docs/issues/2384

Getting Azure InstanceInput endpoint port

I'm want my client to communicate with a specific WorkerRole instance, so I'm trying to use InstanceInput endpoints.
My project is based on the example provided in this question: Azure InstanceInput endpoint usage
The problem is that I don't get the external IP address + port for the actual instance, when using RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["Endpoint1"].IPEndpoint;
I just get internal address with the local port (e.g. 10.x.x.x:10100). I know that I can get the public IP address via DNS lookup (xxx.cloudapp.net), but I don't have a glue how to get the correct public port for each instance.
One possible solution would be: get the instance number (from RoleEnvironment.CurrentRoleInstance.Id) and add this instance number to the FixedPortRange minimum (e.g. 10106). This would imply that the first instance will always have the port 10106, the second instance always 10107 and so on. This solution seems a bit hacky to me, since I don't know how Windows Azure assigns the instances to the ports.
Is there a better (correct) way to retrieve the public port for each instance?
Question #2:
Are there any information about the Azure Compute Emulator supporting InstanceInput endpoints? (As I already mentioned in the comments: It seems that the Azure Compute Emulator currently doesn't support InstanceInputEndpoint).
Second solution (much better):
To get the public port, the porperty PublicIPEndpoint can be used (I don't know why I didn't notice this property in the first place).
Usage: RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["Endpoint1"].PublicIPEndpoint;
Warning:
The IP address in the property is unused (http://msdn.microsoft.com/en-us/library/microsoft.windowsazure.serviceruntime.roleinstanceendpoint.publicipendpoint.aspx).
First solution:
As 'artfulmethod' already mentioned, the REST operation Get Deployment retrieves interesting information about the current deployment. Since I encountered some small annoying 'issues', I'll will provide the code for the REST client here (in case someone else is having a similiar problem):
X509Store certificateStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
certificateStore.Open(OpenFlags.ReadOnly);
string footPrint = "xxx"; // enter the footprint of the certificate you use to upload the deployment (aka Management Certificate)
X509Certificate2Collection certs =
certificateStore.Certificates.Find(X509FindType.FindByThumbprint, footPrint, false);
if (certs.Count != 1) {
// client certificate cannot be found - check footprint
}
string url = "https://management.core.windows.net/<subscription-id>/services/hostedservices/<service-name>/deployments/<deployment-name>"; // replace <xxx> with actual values
try {
var request = (HttpWebRequest)WebRequest.Create(url);
request.ClientCertificates.Add(certs[0]);
request.Headers.Add("x-ms-version", "2012-03-01"); // very important, otherwise you get an HTTP 400 error, specifies in which version the response is formatted
request.Method = "GET";
var response = (HttpWebResponse)request.GetResponse(); // get response
string result = new StreamReader(response.GetResponseStream()).ReadToEnd() // get response body
} catch (Exception ex) {
// handle error
}
The string 'result' contains all the information about the deployment (format of the XML is described in section 'Response Body' # http://msdn.microsoft.com/en-us/library/windowsazure/ee460804.aspx)
To get information about your deployments, including the VIPs and public ports for your role instances, use the Get Deployment operation on the Service Management API. The response body includes an InstanceInputList.

Do I need to replace localhost in the IIS://localhost/MimeMap when reading the Mimemap

I'm reading out the mime types from IIS's MimeMap using the command
_mimeTypes = new Dictionary<string, string>();
//load from iis store.
DirectoryEntry Path = new DirectoryEntry("IIS://localhost/MimeMap");
PropertyValueCollection PropValues = Path.Properties["MimeMap"];
IISOle.MimeMap MimeTypeObj;
foreach (var item in PropValues)
{
// IISOle -> Add reference to Active DS IIS Namespace provider
MimeTypeObj = (IISOle.MimeMap)item;
_mimeTypes.Add(MimeTypeObj.Extension, MimeTypeObj.MimeType);
}
Do I need replace the localhost part when I deploy it to my live server? If not, why not and what are the implications of not doing so.
Cheers
It should not be an issue to leave the host as 'localhost'.
After all, you want to get the MimeMap of the machine your app is running on, correct?
A possible complication that I can forsee is that if you are using a third party as a host. They can do anything they want with host headers and it may be possible that localhost is not available for whatever reason.
But you should simply give it a shot and adjust if necessary.
If you leave it like 'Localhost', you will have to run this script directly on the server.
If you change it to fetch the machine name directly, you can think of running this script remotely as well.

Resources