Skip to content

Commit

Permalink
adds set secret azure kv and managed identity
Browse files Browse the repository at this point in the history
  • Loading branch information
zemuldo committed Oct 28, 2023
1 parent cedaeef commit c16f23f
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 35 deletions.
34 changes: 13 additions & 21 deletions lib/ex_secrets.ex
Original file line number Diff line number Diff line change
Expand Up @@ -106,35 +106,27 @@ defmodule ExSecrets do
true -> :ok
end

with true <- is_atom(provider),
value when not is_nil(value) <- Cache.get(key) do
value
else
nil ->
Cache.pass_by(
key,
SecretFetchLimiter.allow(key, ExSecrets, :get_using_provider, [key, provider])
)
end
get(key, provider: provider)
end

@doc """
Get secret value with provider name and default value
"""
@deprecated "This function is deprecated. Use get/2 instead."
def get(key, provider, default) do
with true <- is_atom(provider),
value when not is_nil(value) <- Cache.get(key) do
value
case get(key, provider: provider) do
nil -> default
value -> value
end
end

def set(provider, key, value) do
with provider when is_atom(provider) <- Resolver.call(provider) do
Kernel.apply(provider, :set, [key, value])
Cache.save(key, value)
else
nil ->
case Cache.pass_by(
key,
SecretFetchLimiter.allow(key, ExSecrets, :get_using_provider, [key, provider])
) do
nil -> default
value -> value
end
{:error, message} -> {:error, message}
_ -> {:error, :provider_not_found}
end
end

Expand Down
81 changes: 74 additions & 7 deletions lib/providers/azure_key_vault.ex
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,22 @@ defmodule ExSecrets.Providers.AzureKeyVault do
end
end

def set(_name, _value) do
{:error, "Not implemented"}
def set(name, value) do
name = name |> String.split("_") |> Enum.join("-")

with process when not is_nil(process) <-
GenServer.whereis(@process_name) do
GenServer.call(@process_name, {:set, name, value})
else
nil ->
case set_secret(name, value, %{}, nil) do
{:ok, value, _} ->
value

_ ->
{:error, "Could not set secret, check credentials and permissions"}
end
end
end

def handle_call({:get, name}, _from, state) do
Expand All @@ -124,6 +138,13 @@ defmodule ExSecrets.Providers.AzureKeyVault do
end
end

def handle_call({:set, name, value}, _from, state) do
case set_secret(name, value, state, get_current_epoch()) do
{:ok, secret, state} -> {:reply, secret, state}
_ -> {:reply, nil, state}
end
end

defp token_uri(tenant_id) do
"https://login.microsoftonline.com/#{tenant_id}/oauth2/v2.0/token"
end
Expand All @@ -148,27 +169,68 @@ defmodule ExSecrets.Providers.AzureKeyVault do
get_access_token(),
{:ok, value} <- get_secret_call(name, access_token) do
{:ok, value, state |> Map.merge(new_state) |> Map.put("issued_at", get_current_epoch())}
else
err -> err
end
end

defp set_secret(
name,
value,
%{"access_token" => access_token, "issued_at" => issued_at, "expires_in" => expires_in} =
state,
current_time
)
when issued_at + expires_in - current_time > 5 do
with {:ok, value} <- set_secret_call(name, value, access_token),
true <- is_binary(value) do
{:ok, value, state}
else
err -> err
end
end

defp set_secret(name, value, state, _) do
with {:ok, %{"access_token" => access_token} = new_state} <-
get_access_token(),
{:ok, value} <- set_secret_call(name, value, access_token) do
{:ok, value, state |> Map.merge(new_state) |> Map.put("issued_at", get_current_epoch())}
else
_ -> {:error, "Failed to get secret"}
end
end

defp get_secret_call(name, access_token) do
client = http_adpater()
key_vault_name = Config.provider_config_value(:azure_key_vault, :key_vault_name)

with {:ok, %{body: body, status_code: 200}} <-
client.get(
"https://#{key_vault_name}.vault.azure.net/secrets/#{name}?api-version=2016-10-01",
%{"Authorization" => "Bearer #{access_token}"}
),
name
|> secret_url()
|> client.get(%{"Authorization" => "Bearer #{access_token}"}),
{:ok, %{"value" => value}} <- Poison.decode(body) do
{:ok, value}
else
_ -> {:error, "Failed to get secret"}
end
end

defp set_secret_call(name, value, access_token) do
client = http_adpater()

with {:ok, %{body: body, status_code: 200}} <-
name
|> secret_url()
|> client.put(Poison.encode!(%{value: value}), %{
"Authorization" => "Bearer #{access_token}",
"content-type" => "application/json"
}),
{:ok, %{"value" => value}} <- Poison.decode(body) do
{:ok, value}
else
err -> err
end
end

defp get_access_token() do
client = http_adpater()
client_secret = Config.provider_config_value(:azure_key_vault, :client_secret)
Expand Down Expand Up @@ -280,6 +342,11 @@ defmodule ExSecrets.Providers.AzureKeyVault do
Application.get_env(:ex_secrets, :http_adapter, HTTPoison)
end

defp secret_url(name) do
key_vault_name = Config.provider_config_value(:azure_key_vault, :key_vault_name)
"https://#{key_vault_name}.vault.azure.net/secrets/#{name}?api-version=2016-10-01"
end

def process_name() do
@process_name
end
Expand Down
82 changes: 75 additions & 7 deletions lib/providers/azure_managed_identity.ex
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,22 @@ defmodule ExSecrets.Providers.AzureManagedIdentity do
end
end

def set(_name, _value) do
{:error, "Not implemented"}
def set(name, value) do
name = name |> String.split("_") |> Enum.join("-")

with process when not is_nil(process) <-
GenServer.whereis(@process_name) do
GenServer.call(@process_name, {:set, name, value})
else
nil ->
case set_secret(name, value, %{}, nil) do
{:ok, value, _} ->
value

_ ->
{:error, "Could not set secret, check credentials and permissions"}
end
end
end

def handle_call({:get, name}, _from, state) do
Expand All @@ -61,6 +75,13 @@ defmodule ExSecrets.Providers.AzureManagedIdentity do
end
end

def handle_call({:set, name, value}, _from, state) do
case set_secret(name, value, state, get_current_epoch()) do
{:ok, secret, state} -> {:reply, secret, state}
_ -> {:reply, nil, state}
end
end

defp token_uri() do
"http://169.254.169.254/metadata/identity/oauth2/token"
|> Kernel.<>("?api-version=2018-02-01&resource=https://vault.azure.net")
Expand Down Expand Up @@ -90,22 +111,64 @@ defmodule ExSecrets.Providers.AzureManagedIdentity do
end
end

def set_secret(
name,
value,
%{"access_token" => access_token, "issued_at" => issued_at, "expires_in" => expires_in} =
state,
current_time
)
when issued_at + expires_in - current_time > 5 do
with {:ok, value} <- set_secret_call(name, value, access_token),
true <- is_binary(value) do
{:ok, value, state}
else
err ->
err
end
end

def set_secret(name, value, state, _) do
with {:ok, %{"access_token" => access_token} = new_state} <-
get_access_token(),
{:ok, value} <- set_secret_call(name, value, access_token) do
{:ok, value, state |> Map.merge(new_state) |> Map.put("issued_at", get_current_epoch())}
else
_ -> {:error, "Failed to get secret"}
end
end

defp get_secret_call(name, access_token) do
client = http_adpater()
key_vault_name = Config.provider_config_value(:azure_managed_identity, :key_vault_name)

with {:ok, %{body: body, status_code: 200}} <-
client.get(
"https://#{key_vault_name}.vault.azure.net/secrets/#{name}?api-version=2016-10-01",
%{"Authorization" => "Bearer #{access_token}"}
),
name
|> secret_url()
|> client.get(%{"Authorization" => "Bearer #{access_token}"}),
{:ok, %{"value" => value}} <- Poison.decode(body) do
{:ok, value}
else
_ -> {:error, "Failed to get secret"}
end
end

defp set_secret_call(name, value, access_token) do
client = http_adpater()

with {:ok, %{body: body, status_code: 200}} <-
name
|> secret_url()
|> client.put(Poison.encode!(%{value: value}), %{
"Authorization" => "Bearer #{access_token}",
"content-type" => "application/json"
}),
{:ok, %{"value" => value}} <- Poison.decode(body) do
{:ok, value}
else
err -> err
end
end

defp get_access_token() do
client = http_adpater()

Expand All @@ -128,6 +191,11 @@ defmodule ExSecrets.Providers.AzureManagedIdentity do
Application.get_env(:ex_secrets, :http_adapter, HTTPoison)
end

defp secret_url(name) do
key_vault_name = Config.provider_config_value(:azure_managed_identity, :key_vault_name)
"https://#{key_vault_name}.vault.azure.net/secrets/#{name}?api-version=2016-10-01"
end

def process_name() do
@process_name
end
Expand Down

0 comments on commit c16f23f

Please sign in to comment.