defmodule GitGud.Packages.SigningKey do
@moduledoc """
A namespace's keypair for one registry format.
Scoped to the namespace — an organization, or a user for personal
repositories — rather than to a repository, because the key is a
trust anchor for a *publisher*. Per-repository keys meant a machine
installing from five repositories in one organization needed five
entries in `/etc/apt/keyrings`, for what its sources list describes
as one publisher.
The public half is served to clients verbatim — it is what a consumer
pins with `mix hex.repo add --public-key` or `signed-by=` — and the
private half is encrypted at rest with the `GitGud.Secrets` master
key. Rows keep the `key_id` they were wrapped under so a master-key
rotation can re-wrap them the same way secret rows are.
Exactly one of `organization_id` / `user_id` is set; the database
enforces it, because a row with neither is a private key nothing can
ever look up again.
"""
use Ecto.Schema
import Ecto.Changeset
alias GitGud.Accounts.User
alias GitGud.Organizations.Organization
schema "package_signing_keys" do
field :format, :string
field :public_key, :string
field :private_key_ciphertext, :binary, redact: true
field :private_key_iv, :binary, redact: true
field :private_key_tag, :binary, redact: true
field :key_id, :string
belongs_to :organization, Organization
belongs_to :user, User
timestamps(type: :utc_datetime)
end
@doc false
def changeset(key, attrs) do
key
|> cast(attrs, [
:format,
:public_key,
:private_key_ciphertext,
:private_key_iv,
:private_key_tag,
:key_id,
:organization_id,
:user_id
])
|> validate_required([
:format,
:public_key,
:private_key_ciphertext,
:private_key_iv,
:private_key_tag,
:key_id
])
|> validate_one_owner()
|> unique_constraint([:organization_id, :format],
name: :package_signing_keys_organization_format_index
)
|> unique_constraint([:user_id, :format], name: :package_signing_keys_user_format_index)
|> check_constraint(:organization_id, name: :package_signing_keys_one_owner)
end
defp validate_one_owner(changeset) do
case {get_field(changeset, :organization_id), get_field(changeset, :user_id)} do
{nil, nil} ->
add_error(changeset, :organization_id, "a signing key needs a namespace")
{org, user} when not is_nil(org) and not is_nil(user) ->
add_error(changeset, :organization_id, "a signing key belongs to one namespace")
_ ->
changeset
end
end
@doc """
The `belongs_to` field and id for a namespace, as the query and
changeset both want it.
"""
def owner_fields(%Organization{id: id}), do: [organization_id: id]
def owner_fields(%User{id: id}), do: [user_id: id]
end
neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
2.8 KiB · text
History
aab4e3f