rawkode

rawkode

Hey all,

I’m reaching the point of deploying our application to production machines and I’m looking at how I inject secrets into my application.

1st: I see there’s {:system} syntax, but it’s use is scattered.

What’s the best way to inject variables to a build?

2nd: I’m using Docker Swarm, so secrets are provided through files: /run/secret/database_password

At the moment, I’m using a fork (File Loader for Config by rawkode · Pull Request #6 · Nebo15/confex · GitHub). Is there a better way?

Hopefully someone has deployed to Swarm before or has some idea on how best to handle this.

Thanks :smiley:

Showing Posts 1 to 10

sasajuric

sasajuric

Author of Elixir In Action

I don’t use Swarm, but your problem seems like a more general case where secrets must be provided when starting the app (as opposed to building the release), and they might need to be derived from various sources, not necessarily OS env. In my cases, I used etcd and custom json files to fetch stuff, so various OS env impros wouldn’t work for me anyway.

The way I deal with this (which doesn’t work for everything, but does for most things, including Phoenix and Ecto), is:

  1. In application start callback, prior to starting the top-level supervisor, I fetch secrets from wherever.
  2. I merge secrets into proper places in app env (e.g. repo or endpoint config).
  3. I start the supervision tree.
rawkode

rawkode OP

Thanks, @sasajuric. This seems like a good idea.

Would you just leave this blank, or not even defined?

config :app, database_password: "" 

Then in start, I could do File.ls |> Enum.each |> merge_config with a function for each parameter I expect?

Useful suggestion, thanks!

sasajuric

sasajuric

Author of Elixir In Action

Would you just leave this blank, or not even defined?

I leave these settings undefined. In case you use those settings only in prod, I suggest defining them in dev and test only.

Well, in start you fetch those parameters from wherever they are, and then merge them, yes. AFAIK there’s no “merge env” function, so I do it by reading the top-level setting (say MyApp.Repo), adding additional info (e.g. password) into it, and putting it back with Application.put_env.

rawkode

rawkode OP

Awesome. Thank you very much for your help, it’s appreciated muchly :thumbsup:

rawkode

rawkode OP

Hi @sasajuric, do you have any examples of doing this? I’ve been trying for a couple of hours now.

get_all_env(app) returns a List of tuples, which is in-turn List of List and I have no idea how to “merge”

get_env(app, key) also returns a List and stumps me too.

bitwalker

bitwalker

Leader

A simple approach:

def merge(a, b), do: Keyword.merge(a, b, &resolve_conflict/3)
defp resolve_conflict(_key, v1, v2) do
    cond do
      Keyword.keyword?(v1) && Keyword.keyword?(v2) ->
        Keyword.merge(v1, v2, &resolve_conflict/3)
      :else ->
        v2
    end
end

This will recursively merge two keyword lists, taking the newer value when conflicts occur, except when the value is a keyword list, in which case it merges. This obviously is a bit of an over simplification, but improving it is an exercise left to the reader :wink:

sasajuric

sasajuric

Author of Elixir In Action

Let’s say I want to set a repo password:

repo_settings = Application.get_env(:my_app, MyApp.Repo)
modified_repo_settings = Keyword.merge(repo_settings, password: super_secret_password)
Application.put_env(:my_app, MyApp.Repo, modified_repo_settings)

If you need to do this for a couple of different top-level settings, its worth making a helper fun which could be used as:

update_app_env(:my_app, MyApp.Repo, &Keyword.merge(&1, password: super_secret_password))

The implementation is left as an exercise :slight_smile:

rawkode

rawkode OP

Thanks again for everyone’s help! I’m getting close.

My “last” problem, I hope, is that this loading happens after KafkaEx starts, which is now failing due to missing configuration.

So far I’ve got the following:

def start(_type, _args) do                                                                                                                                                            
  App.Config.Loader.load_configuration() 
  ... super visor stuff
end
defmodule App.Config.Loader do
  require Logger

  def load_configuration() do
    with {:ok, files} <- File.ls("/run/secrets") do
      Enum.each(files, fn (file_name) ->
        load_configuration(file_name)
      end)
    end

  defp load_configuration(key = "api_hostname", hostname) do
    {:app_api, App.Api.Endpoint, [url: [host: hostname]]}
  end

  defp load_configuration(key = "secret_key_base", secret_key_base) do
    {:app_api, App.Api.Endpoint, [secret_key_base: secret_key_base]}
  end

  defp load_configuration(key = "json_web_token_secret", json_web_token_secret) do
    {:guardian, Guardian, [secret_key: json_web_token_secret]}
  end

  defp load_configuration(key = "elasticsearch_uri", uri) do
    {:tirexs, :uri, uri}
  end

  defp load_configuration(key = "elasticsearch_index", index) do
    {:app_api, :elasticsearch_index, index}
  end

  defp load_configuration(key = "kafka_hosts", host_string) do
    {:kafka_ex, :brokers, host_string
    |> String.split(",")
    |> Enum.map(fn(uri) ->
      [host, port] = String.split(":")
      {host, String.to_integer(port)}
    end)}
  end

  defp load_configuration(key = "youtube_api_key", youtube_api_key) do
    if ! is_nil(youtube_api_key) do
      {:tubex, Tubex, [api_key: youtube_api_key]}
    end
  end

  defp load_configuration(key, _) do
    Logger.warn("External configuration (#{key}) provided, but not loaded")
  end
end
rawkode

rawkode OP

Is it normal for applications to be started before my “primary”?

OvermindDL1

OvermindDL1

Applications should be loaded in dependent order, so if you depend on it then it will be up before yours. Libraries that are designed to be loaded after yours (should) give you a supervisor to place in to your own supervision tree so you can then do whatever you want to their loading, just like how Phoenix’s Endpoint works. :slight_smile:

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
water
I’m aware of 2 such projects, are there others? git: GitHub - team-alembic/realworld: A fullstack Phoenix LiveView application with back...
New
peck
Hi everyone. My team and I have been working on a fairly modest app based around video streaming and chat, but we’ve landed a customer t...
New
bradley
I really like the adapter patterns that ecto, nebulex, waffle, etc. use and would love find something similar for a key management servic...
New
unaware8150
Hello folks! So at work, we are seeing some situations where we have to define some “fixed” strings that are used across the codebase in...
New
Alvinkariuki
How Can I Optimise Compile Time Dependencies I have been building an elixir application for about 2 years now. Many modules and files ha...
New
leoschweizer
Hi, we run different LLM models across a cluster, with each node loading a configurable subset. Model-specific queues let nodes consume ...
New

Other Trending Topics Top

mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
budgie
I love Elixir. It’s one of 2 programming languages I’ve ever fallen in love with. But I don’t use it anymore. Serverless was the promis...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews