Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add azure auth provider #225

Merged
merged 6 commits into from
Mar 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

<!-- ### Added | Changed | Deprecated | Removed | Fixed | Security -->

### Added
- Azure auth provider #225

<!--------------------- Don't add new entries after this line --------------------->

## [2.0.1] - 2023-02-12
Expand Down
2 changes: 1 addition & 1 deletion lib/k8s/client/mint_http_provider.ex
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ defmodule K8s.Client.MintHTTPProvider do
@spec get_content_type(keyword()) :: binary | nil
defp get_content_type(headers) do
case List.keyfind(headers, "content-type", 0) do
{_key, content_type} -> content_type
{_key, content_type} -> content_type |> String.split(";") |> List.first()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you elaborate on this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The azure end point is returning a response with content type application/json; charset=utf-8 since the charset is not needed for the parsing I just threw it away

_ -> nil
end
end
Expand Down
91 changes: 91 additions & 0 deletions lib/k8s/conn/auth/azure.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
defmodule K8s.Conn.Auth.Azure do
@moduledoc """
`auth-provider` for azure
"""
alias K8s.Conn.RequestOptions

require Logger
@behaviour K8s.Conn.Auth

defstruct [:token]

@type t :: %__MODULE__{
token: String.t()
}

@impl true
@spec create(map, String.t()) :: {:ok, t} | :skip
def create(
%{
"auth-provider" => %{
"config" => %{
"access-token" => token,
"tenant-id" => tenant,
"expires-on" => expires_on,
"refresh-token" => refresh_token,
"client-id" => client_id,
"apiserver-id" => apiserver_id
},
"name" => "azure"
}
},
_
) do
if DateTime.diff(DateTime.utc_now(), parse_expires(expires_on)) >= 0 do
Logger.info(
"Azure token expired, using refresh token get new access, this will stop working when refresh token expires"
)

{:ok, %__MODULE__{token: refresh_token(tenant, refresh_token, client_id, apiserver_id)}}
else
{:ok, %__MODULE__{token: token}}
end
end

def create(_, _), do: :skip

@spec parse_expires(String.t()) :: DateTime.t()
defp parse_expires(expires_on) do
case Integer.parse(expires_on) do
{expires_on, _} -> DateTime.from_unix!(expires_on)
:error -> DateTime.from_iso8601(expires_on)
end
end

@spec refresh_token(String.t(), String.t(), String.t(), String.t()) :: String.t()
def refresh_token(tenant, refresh_token, client_id, _apiserver_id) do
payload =
URI.encode_query(%{
"client_id" => client_id,
"grant_type" => "refresh_token",
"refresh_token" => refresh_token
})

{:ok, res} =
K8s.Client.MintHTTPProvider.request(
:post,
URI.new!("https://login.microsoftonline.com/#{tenant}/oauth2/v2.0/token"),
payload,
[
{
"Content-Type",
"application/x-www-form-urlencoded"
}
],
ssl: []
)

Map.get(res, "access_token")
end

defimpl RequestOptions, for: __MODULE__ do
@spec generate(K8s.Conn.Auth.Azure.t()) :: RequestOptions.generate_t()
def generate(%K8s.Conn.Auth.Azure{token: token}) do
{:ok,
%RequestOptions{
headers: [{:Authorization, "Bearer #{token}"}],
ssl_options: []
}}
end
end
end
44 changes: 44 additions & 0 deletions test/k8s/conn/auth/azure_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
defmodule K8s.Conn.Auth.AzureTest do
@moduledoc false
use ExUnit.Case, async: true

alias K8s.Conn
alias K8s.Conn.Auth.Azure

describe "create/2" do
test "creates a Azure struct from data" do
non_expired_unix_ts = DateTime.utc_now() |> DateTime.add(10, :minute) |> DateTime.to_unix()

auth = %{
"auth-provider" => %{
"config" => %{
"access-token" => "xxx",
"apiserver-id" => "service_id",
"client-id" => "client_id",
"expires-on" => "#{non_expired_unix_ts}",
"refresh-token" => "yyy",
"tenant-id" => "tenant"
},
"name" => "azure"
}
}

assert {:ok,
%Azure{
token: "xxx"
}} = Azure.create(auth, nil)
end
end

test "creates http request signing options" do
provider = %Azure{
token: "xxx"
}

{:ok, %Conn.RequestOptions{headers: headers, ssl_options: ssl_options}} =
Conn.RequestOptions.generate(provider)

assert headers == [{:Authorization, "Bearer xxx"}]
assert ssl_options == []
end
end