Current section

Files

Jump to
ex_azure_key_vault lib ex_azure_key_vault.ex
Raw

lib/ex_azure_key_vault.ex

defmodule ExAzureKeyVault.Client do
@moduledoc """
Client for creating or getting Azure Key Vault.
"""
alias __MODULE__
alias ExAzureKeyVault.APIVersion
alias ExAzureKeyVault.Auth
alias ExAzureKeyVault.Url
@enforce_keys [:api_version, :bearer_token, :vault_name]
defstruct(
api_version: nil,
bearer_token: nil,
vault_name: nil
)
@type t :: %__MODULE__{
api_version: String.t,
bearer_token: String.t,
vault_name: String.t
}
@doc """
Creates `%ExAzureKeyVault.Client{}` struct with connection information.
## Examples
Using default API version.
iex> ExAzureKeyVault.Client.new("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "my-vault")
%ExAzureKeyVault.Client{
api_version: "2016-10-01",
bearer_token: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
vault_name: "my-vault"
}
Passing custom API version.
iex> ExAzureKeyVault.Client.new("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "my-vault", "2015-06-01")
%ExAzureKeyVault.Client{
api_version: "2015-06-01",
bearer_token: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
vault_name: "my-vault"
}
"""
@spec new(String.t, String.t, String.t | nil) :: Client.t
def new(bearer_token, vault_name, api_version \\ nil) do
%Client{
api_version: api_version || APIVersion.version(),
bearer_token: bearer_token,
vault_name: vault_name
}
end
@doc """
Connects to Azure Key Vault.
## Examples
When defining environment variables and/or adding to configuration.
$ export AZURE_CLIENT_ID="14e79d90-9abf..."
$ export AZURE_CLIENT_SECRET="14e7a11e-9abf..."
$ export AZURE_TENANT_ID="14e7a376-9abf..."
$ export AZURE_VAULT_NAME="my-vault"
# Config.exs
config :ex_azure_key_vault,
azure_client_id: {:system, "AZURE_CLIENT_ID"},
azure_client_secret: {:system, "AZURE_CLIENT_SECRET"},
azure_tenant_id: {:system, "AZURE_TENANT_ID"},
azure_vault_name: {:system, "AZURE_VAULT_NAME"}
iex> ExAzureKeyVault.Client.connect()
%ExAzureKeyVault.Client{
api_version: "2016-10-01",
bearer_token: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
vault_name: "my-vault"
}
Passing custom parameters.
iex> ExAzureKeyVault.Client.connect("my-vault", "14e7a376-9abf...", "14e79d90-9abf...", "14e7a11e-9abf...")
%ExAzureKeyVault.Client{
api_version: "2016-10-01",
bearer_token: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
vault_name: "my-vault"
}
"""
@spec connect() :: Client.t | {:error, any}
@spec connect(String.t | nil, String.t | nil, String.t | nil, String.t | nil) :: Client.t | {:error, any}
def connect(vault_name \\ nil, tenant_id \\ nil, client_id \\ nil, client_secret \\ nil) do
vault_name = get_env(:azure_vault_name, vault_name)
tenant_id = get_env(:azure_tenant_id, tenant_id)
client_id = get_env(:azure_client_id, client_id)
client_secret = get_env(:azure_client_secret, client_secret)
if is_empty(vault_name), do: raise ArgumentError, message: "Vault name is not present"
if is_empty(tenant_id), do: raise ArgumentError, message: "Tenant ID is not present"
if is_empty(client_id), do: raise ArgumentError, message: "Client ID is not present"
if is_empty(client_secret), do: raise ArgumentError, message: "Client secret is not present"
with %Auth{} = auth <- Auth.new(client_id, client_secret, tenant_id),
{:ok, bearer_token} <- auth |> Auth.get_bearer_token,
%Client{} = client <- bearer_token |> Client.new(vault_name, APIVersion.version()) do
client
else
{:error, reason} -> {:error, reason}
end
end
@doc """
Returns secret value.
## Examples
Ignoring secret version.
iex> ExAzureKeyVault.Client.get_secret(
%ExAzureKeyVault.Client{
api_version: "2016-10-01",
bearer_token: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
vault_name: "my-vault"
}, "my-secret")
{:ok, "my-value"}
Passing secret version.
iex> ExAzureKeyVault.Client.get_secret(
%ExAzureKeyVault.Client{
api_version: "2016-10-01",
bearer_token: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
vault_name: "my-vault",
}, "my-secret", "03b424a49ac3...")
{:ok, "my-other-value"}
"""
@spec get_secret(Client.t, String.t, String.t | nil) :: {:ok, String.t} | {:error, any}
def get_secret(%Client{} = params, secret_name, secret_version \\ nil) do
url = Url.new(secret_name, params.vault_name) |> Url.get_url(secret_version, params.api_version)
headers = ["Authorization": params.bearer_token, "Content-Type": "application/json; charset=utf-8"]
options = [ssl: [{:versions, [:'tlsv1.2']}]]
case HTTPoison.get(url, headers, options) do
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
response = Poison.decode!(body)
{:ok, response["value"]}
{:ok, %HTTPoison.Response{status_code: 404, body: body}} ->
response = Poison.decode!(body)
{:error, response}
{:ok, %HTTPoison.Response{status_code: status, body: body}} ->
if status
|> Integer.to_string()
|> String.starts_with?("4") do
if body != "" do
response = Poison.decode!(body)
{:error, response}
else
{:error, "Error: #{status}: #{url}"}
end
end
{:error, %HTTPoison.Error{reason: reason}} ->
if reason == :nxdomain do
{:error, "Error: Couldn't resolve host name #{url}"}
else
{:error, reason}
end
_ ->
{:error, "Something went wrong"}
end
end
@doc """
Creates a new secret.
## Examples
iex> ExAzureKeyVault.Client.create_secret(
%ExAzureKeyVault.Client{
api_version: "2016-10-01",
bearer_token: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
vault_name: "my-vault"
}, "my-new-secret", "my-new-value")
:ok
"""
@spec create_secret(Client.t, String.t, String.t) :: :ok | {:error, any}
def create_secret(%Client{} = params, secret_name, secret_value) do
url = Url.new(secret_name, params.vault_name) |> Url.get_url(params.api_version)
body = Url.get_body(secret_value)
headers = ["Authorization": params.bearer_token, "Content-Type": "application/json; charset=utf-8"]
options = [ssl: [{:versions, [:'tlsv1.2']}]]
case HTTPoison.put(url, body, headers, options) do
{:ok, %HTTPoison.Response{status_code: 200}} ->
:ok
{:ok, %HTTPoison.Response{status_code: status, body: body}} ->
if status
|> Integer.to_string()
|> String.starts_with?("4") do
if body != "" do
response = Poison.decode!(body)
{:error, response}
else
{:error, "Error: #{status}: #{url}"}
end
end
{:error, %HTTPoison.Error{reason: reason}} ->
if reason == :nxdomain do
{:error, "Error: Couldn't resolve host name #{url}"}
else
{:error, reason}
end
_ ->
{:error, "Something went wrong"}
end
end
@spec get_env(atom, String.t) :: String.t
defp get_env(key, default) do
default || Application.get_env(:ex_azure_key_vault, key) |> return_value()
end
@spec return_value(tuple) :: String.t
defp return_value({:system, key}) when is_binary(key) do
System.get_env(key)
end
@spec return_value(String.t) :: String.t
defp return_value(value), do: value
@spec is_empty(String.t) :: boolean
defp is_empty(string) do
is_nil(string) || String.trim(string) == ""
end
end