Elixir 비밀에 대한 AWS SSM 파라미터

17615 단어 elixirdevopsaws
이것은 AWS Systems Manager Parameter Store을 사용하여 간단한 방법으로 비밀 구성을 로드하는 방법에 대한 매우 짧은 문서입니다. 내가 설명하려는 접근법의 동기 중 하나는 반패턴이라고 생각하는 것을 피하려는 욕구입니다. 해당 토론에 관심이 없으면 the code으로 건너뛰십시오.

안티패턴



Elixir 1.9가 도입되었습니다"releases". 구성 방법에 대한 설명overhaul이 있습니다. 그 전에는 모든 구성이 빌드 시간에 해결되었습니다. "빌드 시간"과 "실행 시간"이 사실상 동일했기 때문에 "소스에서"앱을 실행하는 사람들에게는 문제가 되지 않았습니다. 그러나 미리 빌드된 앱을 제공하려는 사람들에게는 문제가 발생했습니다.

config :my_app,
  # when is this resolved?
  database_url: System.get_env("DATABASE_URL")


사람들은 패턴을 채택하여 이 문제를 해결했습니다. 즉, 변수를 자리 표시자 값으로 설정하고 나중에 조회를 연기합니다.

config :my_app,
  normal_var: "I am totally normal",
  deferred_var: {:system, "SEE_YOU_LATER"}


이것이 작동하려면 소비 코드가 이 패턴을 인식해야 합니다.

def consume_config() do
  case Application.get_env(:my_app, :deferred_var) do
    {:system, var} -> System.get_env(var)
    value -> value
  end
end


이 "안티 패턴"의 문제는 이것이 실제 기능이 아니라 구성 시스템 자체의 기능이라고 잘못 가정한 신규 사용자를 혼란스럽게 한다는 것입니다. 지원을 선택했습니다.

다행스럽게도 이것은 더 이상 문제가 되지 않으며 런타임 구성에 대한 이야기가 훨씬 더 좋습니다. 모든 것을 config/runtime.exs 에 넣기만 하면 됩니다.

최근에는 AWS SSM을 사용하여 secret configuration을 저장하고 싶었는데, 방법은 Config.Provider을 사용하는 것 같았습니다. 다른 사람들이 같은 문제를 어떻게 해결했는지 둘러보니 익숙한 패턴이 나타났습니다.

config :my_app,
  some_var: {:ssm, "THIS_LOOKS_FAMILIAR"}

Config.Provider s가 config/runtime.exs 이후에 실행되기 때문에 나중에 감지하고 바꿀 수 있는 자리 표시자 값이 필요했습니다.

이것은 동일한 안티 패턴의 예라고 생각했습니다.

안티 패턴을 다시 도입하고 싶지 않다고 결정하고 다른 접근 방식을 생각해 냈습니다.

다른 접근법



먼저 필요한 종속성을 추가하고 Config.Provider 를 선언합니다.

defmodule Example.MixProject do
  def project do
    [
      ...
      releases: releases()
    ]
  end

  defp releases() do
    [
      example: [
        config_providers: [
          {Example.ConfigProvider, []}
        ]
      ]
    ]
  end

  defp deps do
    [
      ...
      {:ex_aws, "2.0"},
      {:ex_aws_ssm, "2.0"}
    ]
  end
end


그리고 아래와 같이 구현합니다.

defmodule Example.ConfigProvider do
  @behaviour Config.Provider

  @impl true
  def init([]) do
    []
  end

  @impl true
  def load(config, _state) do
    {:ok, _} = Application.ensure_all_started(:hackney)
    {:ok, _} = Application.ensure_all_started(:ex_aws)

    params = get_parameters(config[:ex_aws]) |> Enum.into(%{})

    Config.Reader.merge(config,
      example: [
        {ExampleWeb.Endpoint, secret_key_base: params["secret_key_base"]},
        {Example.Repo, url: params["database_url"]}
      ]
    )
  end

  defp get_parameters(config) do
    ExAws.SSM.get_parameters_by_path(prefix(), with_decryption: true)
    |> ExAws.request(config)
    |> case do
         {:ok, %{"Parameters" => params}} ->
           params
           |> Enum.map(fn param ->
             {String.trim_leading(param["Name"], prefix()), param["Value"]}
           end)

         error ->
           raise "Failed to fetch parameters: #{inspect(error)}"
       end
  end

  defp prefix() do
    "/example/prod/"
  end
end


본질적으로 자리 표시자 값에 의존하는 제네릭Config.Provider을 사용하는 대신 원하는 구성을 추가하는 일회성Config.Provider을 작성했습니다.

잃어버린 것을 되찾는 것



이것은 내가 작업하고 있는 프로젝트에서 잘 작동했지만, 돌이켜보면 두 가지 문제가 있는 것 같습니다.

"잘못된"접근 방식에는 좋은 점이 있었습니다. 선언적이었고 구성이 모두 한 곳에 있었습니다.

나는 코드가 잃어버린 명확성을 되찾는 데 도움이 될 대안에 대해 생각했고 이것이 내가 생각해 낸 것입니다.

먼저 가능한 가장 간단한 API로 시작했습니다.

config :my_app,
  some_var: System.get_env("SOME_VAR"),
  database_url: Secret.get_env("DATABASE_URL")

System.get_env(...)Secret.get_env(...)로 바꾸십시오.

그리고 다음 코드가 작동합니다.

defmodule Secret do
  def get_env(key, default \\ nil) do
    Map.get(get_all_env(), key, default)
  end

  defp get_all_env() do
    case :ets.whereis(__MODULE__) do
      :undefined ->
        :ets.new(__MODULE__, [:set, :public, :named_table])

        {:ok, _} = Application.ensure_all_started(:hackney)
        {:ok, _} = Application.ensure_all_started(:ex_aws)

        env = fetch_env()
        :ets.insert(__MODULE__, {:env, env})
        env

      ref ->
        [{:env, env}] = :ets.lookup(ref, :env)
        env
    end
  end

  defp fetch_env() do
    config = [
      region: System.fetch_env!("AWS_REGION"),
      access_key_id: System.fetch_env!("AWS_ACCESS_KEY_ID"),
      secret_access_key: System.fetch_env!("AWS_SECRET_ACCESS_KEY")
    ]

    get_parameters(config) |> Enum.into(%{})
  end

  defp get_parameters(config) do
    # same as above
  end
end



$ aws ssm put-parameter \
  --name "/example/prod/DATABASE_URL" \
  --value "ecto://postgres:postgres@localhost/example_prod" \
  --type SecureString \
  --overwrite



> Secret.get_env("DATABASE_URL")

"ecto://postgres:postgres@localhost/example_prod"


아직 이 접근 방식으로 전환하지는 않았지만 다음에 동일한 작업을 수행해야 할 때 고려할 것입니다.

좋은 웹페이지 즐겨찾기