Current section

Files

Jump to
maat_feather lib maat_feather auth key_server.ex
Raw

lib/maat_feather/auth/key_server.ex

defmodule MaatFeather.Auth.KeyServer do
@moduledoc """
## KeyServer
```elixir
# config/config.exs
config :maat_feather, MaatFeather.Auth.KeyServer,
# The expiration time of the private keys in chache.
exp: :timer.minutes(30),
# The interval between key rotations.
key_rotation_interval: :timer.minutes(1),
# The host of the redis server.
redis_host: "localhost",
# The port of the redis server.
redis_port: 6379
```
"""
use GenServer
require Logger
@env Application.compile_env!(:maat_feather, __MODULE__)
@exp Keyword.fetch!(@env, :exp)
@key_rotation_interval Keyword.fetch!(@env, :key_rotation_interval)
@redis_host Keyword.fetch!(@env, :redis_host)
@redis_port Keyword.fetch!(@env, :redis_port)
def start_link(_) do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end
@impl true
def init(_state) do
{:ok, conn} = Redix.start_link(host: @redis_host, port: @redis_port)
send(self(), {:rotate, conn})
:timer.send_interval(@key_rotation_interval, self(), {:rotate, conn})
{:ok, %{conn: conn}}
end
@impl true
def handle_call({:fetch_key, kid}, _from, %{conn: conn} = state) do
key_pem = Redix.command!(conn, ["get", kid])
{:reply, key_pem, state}
end
@impl true
def handle_call(:last_key, _from, %{conn: conn} = state) do
last_key =
conn
|> Redix.command!(["get", "last_key"])
|> Jason.decode!()
|> Enum.at(0)
{:reply, last_key, state}
end
@impl true
def handle_info({:rotate, conn}, state) do
private_key = X509.PrivateKey.new_rsa(1024)
key_pem = X509.PrivateKey.to_pem(private_key)
kid =
8
|> :crypto.strong_rand_bytes()
|> Base.encode16()
Redix.command!(conn, ["set", kid, key_pem])
Redix.command!(conn, ["expire", kid, "#{@exp}"])
Redix.command!(conn, ["set", "last_key", Jason.encode!(%{kid => key_pem})])
if Mix.env() != :prod do
Logger.info("đŸ”‘ KEY ROTATION::#{kid}")
end
{:noreply, state}
end
@doc """
Fetch key by kid
## Parameters
- `kid`: kid of the key to fetch
## Examples
```elixir
iex> key_pem = MaatFeather.Auth.KeyServer.fetch_key!(kid)
"-----BEGIN RSA PRIVATE KEY-----\nMIICXgIBAAKBgQDYMp3mZs4yg9Lzui6erG8i/ZNX/VWgU6TvKDsvETvPaTuOFYfU\nV7BKtksxi2OsYNTXLs8POtdH0TlGPmmY6ZnWKUffsoWbuH8dL2El9zbvrd85W0z0\n6I+gt//5cpgnEMUEtk076C197DZiPAKDDh/PxRatkoIDts2B7oI7HVtuLQIDAQAB\nAoGBAKFdLjNwmghkEchuNyuhQhYYs6fh4s4WgC0slvfrYfvN7HCBK29Wv7MT2o2x\nHwTjBRlSy6LvZQ0qYRhHryM9f95Ui4zrdfYmmlPwlVPfENS2hJI9Og4HYLik1Y4U\nMVx0EnE8jidqbV3afpVTELLmlR4TS9/WbocIcxE+Y4oVp3KVAkEA8MTMCaC8mK1T\nlSzC0ssj3q6m9RPsfnCtxP1Os4V4mq9Cc1NPWWjhJc7Q8wA52Lg5PseAJ3ak3W2W\n34u25NxWYwJBAOXf5Z1gJSQpLL9fPhos4RuunPjgVUBjxTR4ibFGZW9dmoibWocS\nEVfPzjH/LsG2FovIqzSST3ZpoMlXiK29xi8CQQDbkTzZkuIrAfX1VoqdHXAqxW+w\nlGjZrfxqZu78vD2OI0IgVdtJGGIqzn6y/IPEZcyeueTfGE2lOSmb15gFAgMtAkEA\n0WP2czkt+9dpxFSMbClvT9jpcaZe4Y1ACEC4I/vcz+mypzbj6l9h3twSleY9gARO\nWzwJ/5Q6v8ENzflnLePMzwJAOD7oKDz8LYe2g5EVj0M8lGY6BtfqhLOQ4mpVZ+MW\ngjXDvnkeNFCgw3hM8IJuRI904Fu9cI48vmzbBrrXMaYZxA==\n-----END RSA PRIVATE KEY-----\n\n"
```
"""
@spec fetch_key!(charlist()) :: charlist()
def fetch_key!(kid) do
GenServer.call(__MODULE__, {:fetch_key, kid})
end
@doc """
Fetch most recent key
## Examples
```elixir
iex> {kid, key_pem} = MaatFeather.Auth.KeyServer.last_key!()
{"D1EE7997EF068672",
"-----BEGIN RSA PRIVATE KEY-----\nMIICXAIBAAKBgQDMXbBZLXue6XQjHmYcjhVyHGRitZ3deyvTCM0mTv40uOKProiI\nWKfOZoaatJdBo7R9kOQAl4LUFb5aNcmR7GqZtDJ42CG8m1S/9fgy3R8SPpCRstHp\nfawqK9cQJieb8AtPTRiNkEU1xKn7EBTeqM/UPl7t2YoqL97OvzxYylh5LwIDAQAB\nAoGBAMIFwMMpl1ALvCMLXU6G5n1rzzKX0sTPh4/Xr01LebR8Qzy+KwmngclHNUvz\nYBcFYPpFcPHO5nqMGihzIGfV2G1Zpu42Rt9l/kC3kZhXSp4dTmMpr6b5MT5lAaYO\nIj4s7PAZrI1Fj2Sn/TXl1eYjY8xVkoy4N7Wod6RNuK0N30QhAkEA85BY3G2B0QaI\nV5Z37sSEzHB+khrAxZQxTG3uxajCN6HxzUQAJpUW3ggSHRw8J6tCUM9mBqBucuiR\nN/F8w9OduwJBANbM+z6d34dQ1t9lkLJnCcSPXCaDcjdC+lg6CD8zRnn5f3DeFfQ1\nv5SM0eEZVh5r4exhW3FEfXHkj8Mcu8Y3oR0CQHlkwGVczw73So0dGbmPiQC8MNlx\nLBNNwSRuxksNyX8g3/uGoOyZ8kPHkf+gSvsaiHruY9N5spLjBz3yxlRz7HECQAos\naznsAEpIaeTqyo+oka3dnF+2mhJD+ddz6DAWHaBpUL/nxOJsKUa/5LDkok+OjITF\nl68lr0tVp/l1wu8Ac4ECQA4Qg479XHV3G/yxm3FCO/ySDlNdlVQ1bdAiGeNeG/qI\nTpI2yLu5azCuSpfs+dMRbTMkcp9aRIj20XqDAZmkwUk=\n-----END RSA PRIVATE KEY-----\n\n"}
```
"""
@spec last_key!() :: {charlist(), charlist()}
def last_key!() do
GenServer.call(__MODULE__, :last_key)
end
end