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

Auth provider for Azure? #162

Closed
krisalyssa opened this issue May 16, 2022 · 7 comments · Fixed by #225
Closed

Auth provider for Azure? #162

krisalyssa opened this issue May 16, 2022 · 7 comments · Fixed by #225

Comments

@krisalyssa
Copy link

Before I dive down the rabbit hole of implementing my own auth provider, has anyone gotten the k8s library working with Azure AKS?

The problem I'm encountering trying to use k8s out of the box is that the user config in ~/.kube/config looks like it should be handled by the standard AuthProvider module, but it contains a different set of keys.

- name: clusterUser_dve2-rg-nonprod-k8s_dve2-aks-nonprod
  user:
    auth-provider:
      config:
        access-token: ***
        apiserver-id: ***
        client-id: ***
        config-mode: "1"
        environment: AzurePublicCloud
        expires-in: "5264"
        expires-on: "1652716095"
        refresh-token: ***
        tenant-id: ***
      name: azure

None of the keys in the destructuring at https://github.com/coryodaniel/k8s/blob/1.1.4/lib/k8s/conn/auth/auth_provider.ex#L22-L27 exist, so the code bombs out with "no match of right hand side".

@mruoss
Copy link
Collaborator

mruoss commented May 19, 2022

AuthProvider is an unfortunate name choice. You are looking for an OIDCAuthProvider which has not been implemented yet.

@krisalyssa
Copy link
Author

Has there been any work done towards this? I may be able to contribute if so.

@mruoss
Copy link
Collaborator

mruoss commented Jun 7, 2022

Not that I know of. Also, OIDC SSO only works in an interactive mode because at some point, a browser has to be opened.

@Hanspagh
Copy link
Contributor

Hi @CraigCottingham
I wrote this provider you can use, it works for experimental work, but I hacked it together quite fast looking at the python implementation, so there is properly still a bunch of improvements to be made

@mruoss Would you be interested in having this somewhere in the repo?

defmodule KubeAzureAuth.AuthProvider do
  @moduledoc """
  `auth-provider` for azure
  """
  alias K8s.Conn.RequestOptions
  @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" => config,
            "name" => "azure"
          }
        },
        _
      ) do
    IO.inspect(config)

    %{
      "access-token" => token,
      "tenant-id" => tenant,
      "expires-on" => expires_on,
      "refresh-token" => refresh_token,
      "client-id" => client_id,
      "apiserver-id" => apiserver_id
    } = config

    if parse_expires(expires_on) > DateTime.utc_now() do
      # TODO write new access token adn refresh back to file
      {:ok,
       %__MODULE__{
         token: refresh_token(tenant, refresh_token, client_id, apiserver_id)
       }}
    else
      {:ok,
       %__MODULE__{
         token: token
       }}
    end
  end

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

  def 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

  defimpl RequestOptions, for: __MODULE__ do
    @spec generate(KubeAzureAuth.AuthProvider.t()) :: RequestOptions.generate_t()
    def generate(%KubeAzureAuth.AuthProvider{} = provider) do
      {:ok,
       %RequestOptions{
         headers: [{:Authorization, "Bearer #{provider.token}"}],
         ssl_options: []
       }}
    end
  end

  defp refresh_token(tenant, refresh_token, client_id, _apiserver_id) do
    # TODO figure out how to request scope apiserver_id
    payload =
      URI.encode_query(%{
        "client_id" => client_id,
        "grant_type" => "refresh_token",
        "refresh_token" => refresh_token
      })

    HTTPoison.post!(
      "https://login.microsoftonline.com/#{tenant}/oauth2/v2.0/token",
      payload,
      %{
        "Content-Type" => "application/x-www-form-urlencoded"
      }
    ).body
    |> Jason.decode!()
    |> Map.get("access_token")
  end
end

@krisalyssa
Copy link
Author

@Hanspagh Thank you for this! I'm incorporating it into my code now, to make sure that it works.

One question: The check for if the token has expired (if parse_expires(expires_on) > DateTime.utc_now() do) has the comparison reversed, doesn't it? If now is greater than the expires_on value, the token has expired. Unless I'm just getting it backward in my head?

I'm making some other relatively minor changes, mainly to make it easier to update the refresh token. I'll post either here or to a gist once I have it working.

@Hanspagh
Copy link
Contributor

Hanspagh commented Jul 6, 2022

@CraigCottingham, I think you got it completely right, it should be inverted, comparisons are hard :D.

Again, this was just hacked together to see if it would work, to be "feature" complete with the python implementation we should also write the refreshed token back into the config.

@mruoss
Copy link
Collaborator

mruoss commented Feb 13, 2023

Do I get it right that this is already implemented? Would someone care to create a PR for this?

@mruoss Would you be interested in having this somewhere in the repo?

Yes, absolutely. Just next to K8s.Conn.Auth.DigitalOcean ;)

@Hanspagh Hanspagh mentioned this issue Feb 15, 2023
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants