Catch-All HTTPS Vhost on Apache 2.4

18,711

Solution 1

The problem was solved but there was some misunderstandings. There really is the requirement that HTTPS needs a matching certificate, but the problem caused by this is that the connection won't be trusted with hostname not matching certificates Common Name or listed in Subject Alternative Name:

  • The same mismatch stays even with the RewriteRule solution given in the other answer.

  • If the "catch-all" hostnames are all sub-domains of example.com and you have a wildcard certificate for *.example.com, it will match.

  • On the other hand most people, when trying to access something.example.com, types it to browser address bar without the http:// or https:// prefix, and browsers defaults to HTTP. Therefore having a "catch-all" redirect on HTTPS even with mismatching certificate won't usually cause any actual problems: only a few people ever sees the SSL_ERROR_BAD_CERT_DOMAIN error.

The Virtual Host Matching works the same way with or without TLS.

If you don't have SNI:

The first name-based vhost in the configuration file for a given IP:port pair is significant because it is used for all requests received on that address and port for which no other vhost for that IP:port pair has a matching ServerName or ServerAlias. It is also used for all SSL connections if the server does not support Server Name Indication.

Without SNI the certificate from the first VirtualHost is used for handshake:

In reality, Apache will allow you to configure name-based SSL virtual hosts, but it will always use the configuration from the first-listed virtual host (on the selected IP address and port) to setup the encryption layer.

The main problem with your original try was having ServerAlias * and not having any ServerName. For a "catch-all" host it would have worked with anything but the other ServerNames from other VirtualHosts. If no another match, Apache falls back to the default VirtualHost section; whichever is the first section (that matches IP based lookup, when name-based lookup fails).

Name-based virtual hosts for the best-matching set of <virtualhost>s are processed in the order they appear in the configuration. The first matching ServerName or ServerAlias is used, with no different precedence for wildcards (nor for ServerName vs. ServerAlias).

There must be SOME ServerName because:

The ServerName directive may appear anywhere within the definition of a server. However, each appearance overrides the previous appearance (within that server).

If no ServerName is specified, the server attempts to deduce the client visible hostname by first asking the operating system for the system hostname, and if that fails, performing a reverse lookup on an IP address present on the system.

This would result in configuration like this:

<VirtualHost *:443>
    # Default catch-all (everything that won't match the following VirtualHosts)
    ServerName catch-all.example.com
    ServerAlias www.example.com
    SSLEngine on
    SSLCertificateFile "C:/prod/hosts.crt.pem"
    SSLCertificateKeyFile "C:/prod/hosts.key.pem"
    SSLCertificateChainFile "C:/prod/intermediate.crt.pem"  
    Redirect permanent / https://example.com
</VirtualHost>

<VirtualHost *:443>
    ServerName example.com
    SSLEngine on
    SSLCertificateFile "C:/prod/hosts.crt.pem"
    SSLCertificateKeyFile "C:/prod/hosts.key.pem"
    SSLCertificateChainFile "C:/prod/intermediate.crt.pem"  
    Include conf/sites/example.com.conf
</VirtualHost>

<VirtualHost *:443>
    ServerName dev.example.com
    SSLEngine on
    SSLCertificateFile "C:/prod/hosts.crt.pem"
    SSLCertificateKeyFile "C:/prod/hosts.key.pem"
    SSLCertificateChainFile "C:/prod/intermediate.crt.pem"  
    Include conf/sites/dev.example.com.conf
</VirtualHost>

Please notice the other things I have changed:

  1. dev.example.com uses the same certificate as it would do so anyway without SNI.

  2. Use <VirtualHost *:443> instead of _default_:443 as _default_ has a special purpose:

    Any vhost that includes the magic _default_ wildcard is given the same ServerName as the main server.

    (This also means could use _default_:443 in your "catch-all", not in the others. You can try!)

  3. Domain is replaced with Reserved Example Domain Names.

  4. I'd prefer having www.example.com as a part of the "catch-all" (rather than as an alias) in order to have only one canonical address for your site. Therefore I have moved it there.


If you had SNI, the processing mimics the same behavior but is a bit different in details:

Before there is even an SSL handshake, Apache finds the best match for the IP address and TCP port the connection is established on (IP-based virtual hosting)

If there is a NameVirtualHost directive that has the same literal arguments as this best-matching VirtualHost, Apache will instead consider ALL VirtualHost entries with identical arguments to the matched VirtualHost. Otherwise, SNI processing has no selection to perform.

If the client sends a hostname along with its TLS handshake request, Apache will compare this TLS hostname to the ServerName/ServerAlias of the candidate VirtualHost set determined in the preceding steps.

Whichever VirtualHost is selected on the preceding basis will have its SSL configuration used to continue the handshake. Notably, the contents of the certificates are not used in any comparison.

With SNI you can have the additional certificate for dev.example.com.

If all the prerequisites for SNI are met, it should work automatically and error.log would show [warn] Init: Name-based SSL virtual hosts only work for clients with TLS server name indication support (RFC 4366).

Solution 2

While it is possible to redirect all unknown HTTPS traffic to a specific virtual host, Apache did not make it easy:

  1. Each HTTPS VirtualHost needs a ServerName, which we don't have for the catch-all host. This is a requirement of HTTPS since certificates are typically associated to hosts (ServerName or ServerAlias).
  2. Apache will take the first virtual host as the default one when everything else fails. Make sure that you do not have any other configuration with the same port IP configured or your catch-all will fail.
  3. I had typos in my original config which probably caused some of the redirected loops (I had 2 ServerName statements in some VirtualHost). I would love to understand a bit more the details here but it's not the focus of the question.

Based on this there are two solutions. I prefer the first since it's probably more scalable (no need to updated exceptions) and also performant (no need to use extra modules).

Catch-all using a fake ServerName (suggested by Esa)

    #
    # Catch-all virtual hosts.
    #
    <VirtualHost _default_:80>
        # Default catch-all virtual host.
        Redirect permanent / https://example-prod.com 
    </VirtualHost>

    <VirtualHost _default_:443>
        ServerName catch-all
        SSLEngine on
        SSLCertificateFile "C:/dev/hosts.crt.pem"
        SSLCertificateKeyFile "C:/dev/hosts.key.pem"
        SSLCertificateChainFile "C:/dev/intermediate.crt.pem"   
        Redirect permanent / https://example-prod.com 
    </VirtualHost>

    #
    # Real virtual hosts.
    #
    <VirtualHost _default_:80>
        ServerName example-prod.com
        ServerAlias www.example-prod.com
        Include conf/sites/example-prod.com.conf 
    </VirtualHost>

    <VirtualHost _default_:80>
        ServerName example-dev.com
        Include conf/sites/example-dev.com.conf 
    </VirtualHost>

    <VirtualHost _default_:443>
        ServerName example-prod.com
        ServerAlias www.example-prod.com
        SSLEngine on
        SSLCertificateFile "C:/prod/hosts.crt.pem"
        SSLCertificateKeyFile "C:/prod/hosts.key.pem"
        SSLCertificateChainFile "C:/prod/intermediate.crt.pem"  
        Include conf/sites/example-prod.com.conf
    </VirtualHost>

    <VirtualHost _default_:443>
        ServerName example-dev.com
        SSLEngine on
        SSLCertificateFile "C:/dev/hosts.crt.pem"
        SSLCertificateKeyFile "C:/dev/hosts.key.pem"
        SSLCertificateChainFile "C:/dev/intermediate.crt.pem"   
        Include conf/sites/example-dev.com.conf 
    </VirtualHost>

mod_rewrite (suggested by Alexis)

    <VirtualHost _default_:80>
        # Default catch-all virtual host.
        Redirect permanent / https://example-prod.com 
    </VirtualHost>

    <VirtualHost _default_:80>
        ServerName example-prod.com
        ServerName www.example-prod.com
        Include conf/sites/example-prod.com.conf 
    </VirtualHost>

    <VirtualHost _default_:80>
        ServerName example-dev.com
        Include conf/sites/example-dev.com.conf 
    </VirtualHost>

    <VirtualHost _default_:443>
        ServerName example-prod.com
        ServerName www.example-prod.com
        SSLEngine on
        SSLCertificateFile "C:/prod/hosts.crt.pem"
        SSLCertificateKeyFile "C:/prod/hosts.key.pem"
        SSLCertificateChainFile "C:/prod/intermediate.crt.pem"  
        Include conf/sites/example-prod.com.conf
        # Default catch-all HTTPS virtual host.
        # Make sure to add all valid SSL domains on this host to avoid conflicts.
        RewriteEngine on
        RewriteCond %{HTTP_HOST} !^example-prod\.com$ [NC]
        RewriteCond %{HTTP_HOST} !^www\.example-prod\.com$ [NC]
        RewriteCond %{HTTP_HOST} !^example-dev\.com$ [NC]
        RewriteRule .* https://example-prod [R=permanent,L]  
    </VirtualHost>

    <VirtualHost _default_:443>
        ServerName example-dev.com
        SSLEngine on
        SSLCertificateFile "C:/dev/hosts.crt.pem"
        SSLCertificateKeyFile "C:/dev/hosts.key.pem"
        SSLCertificateChainFile "C:/dev/intermediate.crt.pem"   
        Include conf/sites/example-dev.com.conf 
    </VirtualHost>

Now why is something so simple so complex? Is Apache showing signs of age? At least there are ways to solve this situation.

Solution 3

HTTPS requires a domain name, which matches the certificate, so *:443 without a corresponding ServerName does not make sense.

However, you could use a redirect in your other <VirtualHost> entries, with a RewriteRule.

    RewriteEngine on
    RewriteCond %{HTTP_HOST} ^(something-else.example-prod.com|whatever.example-prod.com|...others...)$
    RewriteRule ^/(.*) https://www.example-prod.com/$1 [R=permanent,L]

You want a condition (RewriteCond) which checks that only the given domains get redirect as expected. You should know of all the possible names, although if you dynamically add new domain names, hopefully you can use a regex that matches all of those dynamic sub-domains.

Share:
18,711

Related videos on Youtube

Nicolas Bouvrette
Author by

Nicolas Bouvrette

Updated on September 18, 2022

Comments

  • Nicolas Bouvrette
    Nicolas Bouvrette over 1 year

    Is it possible to configure a catch-all (default) HTTPS Vhost on Apache 2.4? I currently have 4 domains, and an HTTP catch all but as soon as I try to add any sort of configuration my other vhosts break. Here is what my config looks like:

    <VirtualHost _default_:80>
        # Default catch-all virtual host.
        Redirect permanent / https://example-prod.com
    </VirtualHost>
    
    <VirtualHost _default_:80>
        ServerName example-prod.com
        ServerName www.example-prod.com
        Include conf/sites/example-prod.com.conf
    </VirtualHost>
    
    <VirtualHost _default_:80>
        ServerName example-dev.com
        Include conf/sites/example-dev.com.conf
    </VirtualHost>
    
    #
    # This is the virtual host I'm missing and that I cannot get to work.
    #
    #<VirtualHost _default_:443>
    #   # Default catch-all virtual host.
    #   ServerAlias *
    #   SSLEngine on
    #   SSLCertificateFile "C:/prod/hosts.crt.pem"
    #   SSLCertificateKeyFile "C:/prod/hosts.key.pem"
    #   SSLCertificateChainFile "C:/prod/intermediate.crt.pem"  
    #   Redirect permanent / https://example-prod.com
    #</VirtualHost>
    
    <VirtualHost _default_:443>
        ServerName example-prod.com
        ServerName www.example-prod.com
        SSLEngine on
        SSLCertificateFile "C:/prod/hosts.crt.pem"
        SSLCertificateKeyFile "C:/prod/hosts.key.pem"
        SSLCertificateChainFile "C:/prod/intermediate.crt.pem"  
        Include conf/sites/example-prod.com.conf
    </VirtualHost>
    
    <VirtualHost _default_:443>
        ServerName example-dev.com
        SSLEngine on
        SSLCertificateFile "C:/dev/hosts.crt.pem"
        SSLCertificateKeyFile "C:/dev/hosts.key.pem"
        SSLCertificateChainFile "C:/dev/intermediate.crt.pem"   
        Include conf/sites/example-dev.com.conf
    </VirtualHost>
    

    My httpd.conf has no more DocumentRoot - everything is in the vhost and includes. This is also a dedicated server and IP.

    How can I get this resolved?

  • Nicolas Bouvrette
    Nicolas Bouvrette about 7 years
    The idea of the "catch-all" is that I don't know the list of domains. For example is someone has a local host entry under "example-hack.com" I would like to redirect it to the default HTTPS site since its not on my virtual hosts, or simply drop the request.. either way work? Is there a way to do this from within HTTPD?
  • Nicolas Bouvrette
    Nicolas Bouvrette about 7 years
    I finally got it thanks to your hint...I'll post the example below.
  • Alexis Wilke
    Alexis Wilke about 7 years
    Also note that the domain and SSL key won't match so users will get an error (a warning really) "don't do that, you gonna die," something of the sort. So it's often not very useful unless you actually have one entry per domain/sub-domain (if you get a wild card then you can match any sub-domain, otherwise you get the ugly warning.)
  • Nicolas Bouvrette
    Nicolas Bouvrette about 7 years
    Well the good thing is that some browsers are actually smarter than Apache and simply redirect with the "bad certificate". I appreciate the paranoia around SSL but I hardly see how much harm can a 301 redirect do to a browser. Thanks again for getting me out of this tricky problem!
  • Alexis Wilke
    Alexis Wilke about 7 years
    HTTPS was definitely something added later. For the longest time, also, you had to attach a domain name to an IP address. That was really not practical (i.e. if you wanted to handle 10 HTTPS websites, you needed 10 IP addresses minimum!) One step at a time...
  • Esa Jokinen
    Esa Jokinen about 7 years
    Great customizability comes with increased complexity. Compared to all the possibilities the basic configuration (directives inside section blocks) is pretty easy. The only confusing part here was VirtualHost Matching order, which may not be intuitive but is understandable.
  • Hagen von Eitzen
    Hagen von Eitzen about 7 years
    @NicolasBouvrette If I understand you right, you wonder about this scenario: User wants foo.example and gets 301 redirect to bar.example, where the bar.example page comes with an SSL-certificat mathicng bar.example, whereas the 301 redirect comes with a certificate not matching foo.example? Well, the first mismatch might in fact indicate that the result is not what the owner of foo.example had in mind. If such a redirect "causes no harm", I wanna see what you do when every request to google.com is redirected to infectyourcomputerforfree.com
  • matega
    matega about 7 years
    @NicolasBouvrette "I hardly see how much harm can a 301 redirect do to a browser" Phishing. If that worked, you could MITM a bank site with an invalid certificate and 301 it to a typoed version of the domain name with a visually similar interface and a valid certificate for that domain name. I'm fairly sure you accepted that certificate beforehand in the "smarter than Apache" browsers or you accidentally had a valid certificate for the domain you tried, because, by the design of HTTPS, this should not work and it's a HUGE security hole in the browser if it does.
  • matega
    matega about 7 years
    @NicolasBouvrette Also, if the server cannot present a valid certificate for the requested domain, the browser should abort the connection before the actual HTTP request. The server then has no chance to send the 301 in reply to the HTTP request that did not take place. If what you described really works, you just found a multi-thousand dollar security hole in that browser. Which browser was it?
  • Nicolas Bouvrette
    Nicolas Bouvrette about 7 years
    I understand those concerns but the certificate check could be done after the redirect? I think if someone compromise the DNS of a company there are much bigger fishes to fry than 301 redirects. Also you will note that Chrome seems to play nice with thia situation which means they probably agree that the risk is low.
  • Nicolas Bouvrette
    Nicolas Bouvrette about 7 years
    This doesn't solve my problem. I actually want a redirect to the proprer domain. To give context, under Apache I have an application which uses requested host names to determine its behavior. If a user have hostfiles or someone points their DNS on my IP by mistake I want this traffic to be redirected. I could do it from the app behind Apache but I was trying to do it in the vhosts first.
  • Esa Jokinen
    Esa Jokinen about 7 years
    This is, as well, a redirect to the proper domain. Even with another domains than your own, because of the fallback to the default domain, as well explained in the answer. The RewriteEngine solution works too, but that wasn't the cause of your original problem.
  • Nicolas Bouvrette
    Nicolas Bouvrette about 7 years
    Interesting, I though I tried that one and it didn't work but (infinite redirects). But I tried with ServerName set to "null" (the string null). Not sure if that caused the problem. I'll give it a shot shortly. Thanks
  • Esa Jokinen
    Esa Jokinen about 7 years
    Infine loop may have been caused by the empty ServerName which equals to servers hostname or reverse DNS, causing the first match even on the proper domain name. Configuring it to something else removes that match.
  • Esa Jokinen
    Esa Jokinen about 7 years
    I'm not saying the another solution was bad in any way. It is well suitable, too. I just noticed that it was based on a false assumption. :)
  • Nicolas Bouvrette
    Nicolas Bouvrette about 7 years
    I actually prefer your option if I can get it working.. Its more resilient and simple (and probably performant).
  • matega
    matega about 7 years
    @NicolasBouvrette I am positive that if the certificate is not valid, the connection is aborted before any data is exchanged. Sending the request anyway would give the request away to an eavesdropper. You may have encountered this: medium.servertastic.com/… About the "bigger fish to fry": 1, what would those be and 2, how is a rogue 301 not big enough? Also, compromising DNS is easy, maybe not on a corporate network, but on a free WiFi it's child's play. SSL exists to protect from exactly that.