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

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>
}
}

Related

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
}

How does Tumblr approach custom domain mapping?

I searched all over but can't find a clear answer or even an engineering blog post to describe how companies map custom domains to their application.
For example, let's say I have a Tumblr page with a URL of www.ashley.tumblr.com. The site allows you to add a custom domain so that visiting www.Ashley.com will render www.ashley.tumblr.com with full support for additional pages and directories.
What is the technical name for developing this?
There's no single name for what they're doing - which is engineering their HTTP/web-server code to handle requests from arbitrary HTTP request Host: header and mapping them to their existing Tumblr accounts. It has nothing to do with DNS other than requiring the owner of a custom domain-name to change their A, AAAA, or CNAME records to point to the same host as the non-custom domain (to guarantee this happens it's usual to make the custom domain-name a CNAME for the non-custom domain, in case the non-custom domain's IP address is subject to change).
Exposition time! - Most conventional web-servers (Apache, IIS) are built around the concept of a "website": a physical directory mapped to requests corresponding to a predefined list of HTTP Host: header values (or some wildcard matching pattern) and protocol and port bindings. For example, you'd add an entry called "MyWebsite.com" (the Website Name) that accepts requests to mywebsite.com and www.mywebsite.com (as these are two distinct Host: header values) and maybe some more, like secure.mywebsite.com using HTTPS on port 443).
More modern lightweight webservers and reverse-proxies (like nginx and Node.js' Express) dispense with physical directory mapping and let the application code entirely decide how to route requests within the application's logic (this is what a "router" and/or "demultiplexer" (demux) does in web-application terminology) - this comes at the expense of needing to handle all that logic yourself (to be fair, these webservers come with the necessary tools to easily configure them like the older conventional web-servers, it just isn't the default).
...but the advantage is that you can make it work exactly like you want.
In pseudocode their program probably looks something like this:
void handleRequest(Request request) {
String hostHeader = request.getHeader("Host")
RegexMatch nonCustomDomainMatch = hostHeader.match( "([^\.]+).tumblr.com" )
if nonCustomDomainMatch.success {
String accountName = nonCustomDomainMatch.groups[0]
showAccount( accountName )
}
else {
// Look up the custom domain name in a database or other mutable data store:
String accountName = db.execQuery( "SELECT accountName FROM accounts WHERE accounts.customDomainName = #cdn", new { cdn: hostHeader } )
if accountName == null {
showHttp404Error()
}
else {
showAccount( accountName )
}
}
}
In reality, given their size and scale, it would likely be some custom logic inside hardware load-balancers or some other lightweight frontend service - and always with aggressive caching (database lookups are expensive!).

Google App Engine Node.js TLS 1.2

Our application hosted on Google App Engine Node.js (Flexible Environment). We are now under review of security inspection and failing on the issue that Google App Engine supports TLS 1.0 and 1.1 versions.
Is there a way to enforce the use of only TLS 1.2? And also block ciphers that are below 128 bit?
So I also came up against this problem...and found that GCP weren't that helpful. They'll helpfully restrict at a domain level if a support ticket is put forwards....which resolves the security concern...but you'll still get false positives which need explaining at every penetration test (the GAE shared IPs accept other version of TLS for other domains).
For a nice clean solution; use Cloudflare for your DNS. They essentially act as a middleman/web application firewall. Amongst other things (free certificates, WAF, DDOS mitigation, CDN, HTTPS force, HSTS etc etc etc), you're able to set the minimum TLS version as you wish. Mine is now minimum TLS 1.2, supporting TLS 1.3 if the browser accepts it. I've also essentially only got port 80/443 on GAE connected to cloudflare, with no public access at all, as all traffic goes through cloudflare first. Pretty neat - zero ports open to the public and a fully operations website! The pen test guys just scratched their heads and packed up.
Oh...and FYI - it's free for this level of configuration. Happy security testing ;-)
I can confirm that you can make a request to google support and it takes up to 4 weeks to make the change. Not sure why. Hopefully they can speed things up in the future. But alternatively you can handle this logic at the application layer (in middleware) rather than the network layer. See snippet below:
// using NODEJS + TYPESCRIPT
// disable tls 1.0 and 1.1 weak ciphers
this.app.use((req, res, next) => {
// const cipher = ((req.socket) as TLSSocket).getCipher()
const protocol = ((req.socket) as TLSSocket).getProtocol()
// console.log('cipher: ', cipher);
// output eg: { name: 'ECDHE-RSA-AES128-GCM-SHA256', version: 'TLSv1/SSLv3' }
console.log('protocol: ', protocol);
// output eg: TLSv1.2
if (protocol === 'TLSv1.2' || protocol === 'TLSv1.3') {
next();
} else {
res.status(426);
res.send('request requires TLSv1.2 or greater, please upgrade');
}
});
I've not tried this so I can't guarantee it would work, but it seems like you could use a HTTP(S) Load Balancer. The SSL policies are configurable such that it would likely meet the requirements of your security review.

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

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.

How to use Restrict attribute in service stack

Is there any documentation on use of [Restrict] attribute with service stack?
Not finding any documentation, I started trying to figure this out. I discovered you have to enable restrictions in AppHost.cs Configure event with
var endpointHostConfig = new EndpointHostConfig
{
EnableAccessRestrictions = true,
};
Then I added attributes to my request DTO:
[Route("Hello/World", "GET")]
[Restrict(EndpointAttributes.InternalNetworkAccess)]
This does not work...looks like that removes all 'default' restrictions and replaces it with just that one restriction? Using this instead seems to work:
[Restrict(InternalOnly = true)]
When I do a GET from the local lan it works, but from remote it does not. Interesting, the 'detailed stack error' it gives from remote is:
The following restrictions were not met: '\n -[InternalNetworkAccess, Secure, HttpHead, HttpPost, HttpPut, HttpDelete,
HttpOther, OneWay, Soap11, Soap12, Xml, Jsv, ProtoBuf, Csv, Html, Yaml, MsgPack, FormatOther, AnyEndpoint]'
Note, it does not even list HttpGet as one of the possiblities - which does work. Also mentions Secure and not InSecure...neither of which I am specifically requiring.
Can we get some clarification on exactly how this is supposed to work? What if I wanted to require SSL - how would I specify that?
What if I wanted to require SSL in production, but not staging on all services for this endpoint? (Realizing this may be a completely different way to configure).
The [Restrict] attribute feature is in the latest version of ServiceStack. Currently the only documentation for this exists in the Security wiki page.
Here are some EndpointAttributes restrictions tests that test the validation of the restriction attributes, and some different service configurations you can use.
The way it works is that it's restricted to anything that's specified, so if you want to enable SSL and leave everything else as unrestricted, you would only add:
[Restrict(EndpointAttributes.Secure)]
public class SslOnly { }
It also supports specifying multiple combinations of environments that are allowed, e.g. You can enforce HTTP internally, but HTTPS externally with:
[Restrict(EndpointAttributes.Secure | EndpointAttributes.External,
EndpointAttributes.InSecure | EndpointAttributes.InternalNetworkAccess)]
public class SslExternalAndInsecureInternal { }
Note: each environment is combined with Enum flags and delimited with a ,.
But it doesn't let you distinguish between debug and release builds, to enable this you would need to use C# conditional compilation symbols.
E.g only allow HTTP for Debug builds and HTTPS for production Release builds:
#if DEBUG
[Restrict(EndpointAttributes.InSecure)]
#else
[Restrict(EndpointAttributes.Secure)]
#endif
public class MyRequestDto { ... }

Resources