Dynamic router or endpoint scope host for SSL port 443 and multiple certs?

I am trying to work out how to have a Phoenix 1.4 app router.ex include different scopes to different hosts on port 443 using SSL.

I get the scope host domain working on say port 4000 (and I assume port 80) but I expect a roadblock when I go to 443 and SSL i.e. and needing to use different Certbot certs.

Checking source, I think scope is limited to host.

Should I make endpoint code that handles domains and multiple certs?

I guess my general question is, does scope host: work on SSL? or will I have limits set by certs?

The simplest option would be to get a single certificate that covers all domains on which you’ll be serving your application (e.g. a wildcard certificate, if they are subdomains of the same primary domain name).

If you must use different certificates, the selection of the correct certificate during the TLS handshake is completely independent from the routing. You can use the sni_fun option in the endpoint’s HTTPS configuration to fetch the right certfile and keyfile options for the domain the client is trying to reach. For example:

config :multi_tenant_web, MultiTenantWeb.Endpoint,
  https: [
    port: 4001,
    sni_fun: &MultiTenantWeb.Endpoint.ssloptions/1,
    keyfile: "priv/cert/privkey.pem",     # The default key
    certfile: "priv/cert/cert.pem",       # The default cert
    cacertfile: "priv/cert/chain.pem",    # The intermediates
    # ...snip...

And in your Endpoint module:

# Note the use of character lists instead of Elixir strings!
def ssloptions('some.domain.name') do
  [
    certfile: 'priv/cert/some_domain_name.pem',
    keyfile: 'priv/cert/some_domain_name_key.pem'
    # Optionally add `cacertfile`, if the intermediates are
    # different from the default
  ]
end
def ssloptions('another.name.here') do
  [
    certfile: 'priv/cert/another_name_here.pem',
    keyfile: 'priv/cert/another_name_here_key.pem'
  ]
end
# Catch-all clause at the end, which uses the default config
def ssloptions(_hostname), do: []

This example hardcodes the domains and certificate names, which should be fine in your case if the domain names are hardcoded in the router as well. But you could imagine an implementation of this function that looks up the options in a map from the application’s configuration.

4 Likes

thanks a million voltone

I’ll try https://ninenines.eu/docs/en/ranch/1.5/manual/ranch_ssl/ sni_fun

will try hard coded first and then an ecto schema

ideally also need to generate certbot certs from elixir (been doing Ansible so far)

will mix this one in https://github.com/capbash/sslcerts

CHEERS