Smarter https keyfile/certfile/cacertfile config?

In my config/prod*.exs I want to be able to use some type of abstract name for the ssl configuration file names, rather than actual filenames.

System.get_env does not help, because the config/prod*.exs files are run at release time. If env vars were going to be my solution, then it would need to read them at runtime.

E.g. instead of the usual config like so:

config :my_qap, MyQap.Endpoint,
  url: [host: "somewhere.com"],
  https: [port: 1234,
          otp_app: :my_app,
          keyfile: "priv/ssl/key.pem",
          certfile: "priv/ssl/cer.pem",
          cacertfile: "priv/ssl/certs.pem",
         ],

I’d like to be able to do something like this:

config :my_qap, MyQap.Endpoint,
  url: [host: "somewhere.com"],
  https: [port: {:system, "PORT"}, # this one works at least
          otp_app: :my_app,
          keyfile: {:system, "KEYFILE"},       # but there is no support in
          certfile: {:system, "CERTFILE"},     # Plug.Adapters.Cowboy
          cacertfile: {:system, "CACERTFILE"}, # for this, unfortunately
         ],

Possibly the change could be added into Plug.Adapters.Cowboy
at this point (https://github.com/elixir-lang/plug/blob/master/lib/plug/adapters/cowboy.ex#L196)

  defp normalize_ssl_file(key, cowboy_options) do                                                                                                                                                                                                                                
    value = cowboy_options[key]                                                                                                                                                                                                                                                  
                                                                                                                                                                                                                                                                                 
    cond do                                                                                                                                                                                                                                                                      
      is_nil(value) ->                                                                                                                                                                                                                                                           
        cowboy_options                                                                                                                                                                                                                                                           
      Path.type(value) == :absolute ->                                                                                                                                                                                                                                           
        put_ssl_file cowboy_options, key, value                                                                                                                                                                                                                                  
      true ->                                                                                                                                                                                                                                                                    
        put_ssl_file cowboy_options, key, Path.expand(value, otp_app(cowboy_options))                                                                                                                                                                                            
    end                                                                                                                                                                                                                                                                          
  end  

Any thoughts on this? Advice?

1 Like

Not sure about other release managers (since you are talking about release), but with Distillery you can use

  url: [host: "somewhere.com"],
  https: [port: {:system, "PORT"}, # this one works at least
          otp_app: :my_app,
          keyfile: "${KEYFILE}",
          certfile: "${CERTFILE}",
          cacertfile: "${CACERTFILE}",
         ],

And pass the env variables in build time. To do so, you have to set env variable REPLACE_OS_VARS=true. Final command to build your release would be something like

MIX_ENV=prod \
REPLACE_OS_VARS=true \
KEYFILE=/path/to/keyfile \
CERTFILE=/path/to/certfile \
CACERTFILE=/path/to/cacertfile \
mix release --env=prod

Except he wanted to read them at runtime, not compile-time.

He can do that via the on-application startup thing in Phoenix 1.3 very easily though. Before that it is more of a hassle but doable too.

With the exception of the port, isn’t that (also?) how you use env vars at runtime? (I’m not at my computer to check.) I’m currently using this syntax and systemd to manage my app service/unit and in the systemd config I reference a file containing env vars for database user, etc., on the runtime server.

Environment variable replacement with Distillery does indeed take place at runtime; just build your release as usual, but with ${MY_VAR} as a placeholder for the value.

When you run the release on your server, first set REPLACE_OS_VARS=true, as well as the variables you’re using, and then the placeholders will be replaced in the “current” config file as the application starts up.

2 Likes

Thanks for your replies. I spent some more time understanding how Phoenix configuration works (by reading the code for Phoenix and Plug), and came to the conclusion that Phoenix is not difficult to dynamically configure, its actually doing things sensibly. I was trying to do things according to thinking based around using inferior philosophies.

In my case, the solution is to simply use the same file names for keyfiles and certs, and install them separately to all the hosts, using the same names on the hosts, but each host getting a different set of data.