From fea3aa101f49865a2e4508fb47988b44be29525e Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 15 Apr 2025 12:33:59 +0200 Subject: [PATCH] Create "Trusted Publishing" database tables --- crates/crates_io_database/src/schema.patch | 26 ++++---- crates/crates_io_database/src/schema.rs | 56 +++++++++++++++++ .../crates_io_database_dump/src/dump-db.toml | 25 ++++++++ .../down.sql | 3 + .../up.sql | 61 +++++++++++++++++++ 5 files changed, 158 insertions(+), 13 deletions(-) create mode 100644 migrations/2025-04-25-090000_trusted-publishing/down.sql create mode 100644 migrations/2025-04-25-090000_trusted-publishing/up.sql diff --git a/crates/crates_io_database/src/schema.patch b/crates/crates_io_database/src/schema.patch index b7b8e0a71c1..18ce21eb9b8 100644 --- a/crates/crates_io_database/src/schema.patch +++ b/crates/crates_io_database/src/schema.patch @@ -1,6 +1,6 @@ --- original +++ patched -@@ -21,9 +21,7 @@ +@@ -14,9 +14,7 @@ /// The `pg_catalog.tsvector` SQL type /// /// (Automatically generated by Diesel.) @@ -9,9 +9,9 @@ - pub struct Tsvector; + pub use diesel_full_text_search::Tsvector; } - + diesel::table! { -@@ -74,9 +72,9 @@ +@@ -67,9 +65,9 @@ /// (Automatically generated by Diesel.) revoked -> Bool, /// NULL or an array of crate scope patterns (see RFC #2947) @@ -23,7 +23,7 @@ /// The `expired_at` column of the `api_tokens` table. /// /// Its SQL type is `Nullable`. -@@ -180,12 +178,6 @@ +@@ -175,12 +173,6 @@ /// /// (Automatically generated by Diesel.) created_at -> Timestamptz, @@ -35,8 +35,8 @@ - path -> Ltree, } } - -@@ -476,7 +468,7 @@ + +@@ -483,7 +475,7 @@ /// Its SQL type is `Array>`. /// /// (Automatically generated by Diesel.) @@ -45,9 +45,9 @@ /// The `target` column of the `dependencies` table. /// /// Its SQL type is `Nullable`. -@@ -703,6 +695,24 @@ +@@ -710,6 +702,24 @@ } - + diesel::table! { + /// Representation of the `recent_crate_downloads` view. + /// @@ -70,7 +70,7 @@ /// Representation of the `reserved_crate_names` table. /// /// (Automatically generated by Diesel.) -@@ -1018,7 +1028,8 @@ +@@ -1094,7 +1104,8 @@ diesel::joinable!(crate_downloads -> crates (crate_id)); diesel::joinable!(crate_owner_invitations -> crates (crate_id)); diesel::joinable!(crate_owners -> crates (crate_id)); @@ -80,19 +80,19 @@ diesel::joinable!(crates_categories -> categories (category_id)); diesel::joinable!(crates_categories -> crates (crate_id)); diesel::joinable!(crates_keywords -> crates (crate_id)); -@@ -1031,6 +1042,7 @@ +@@ -1110,6 +1121,7 @@ diesel::joinable!(publish_limit_buckets -> users (user_id)); diesel::joinable!(publish_rate_overrides -> users (user_id)); diesel::joinable!(readme_renderings -> versions (version_id)); +diesel::joinable!(recent_crate_downloads -> crates (crate_id)); + diesel::joinable!(trustpub_configs_github -> crates (crate_id)); diesel::joinable!(version_downloads -> versions (version_id)); diesel::joinable!(version_owner_actions -> api_tokens (api_token_id)); - diesel::joinable!(version_owner_actions -> users (user_id)); -@@ -1058,6 +1070,7 @@ +@@ -1140,6 +1152,7 @@ publish_limit_buckets, publish_rate_overrides, readme_renderings, + recent_crate_downloads, reserved_crate_names, teams, - users, + trustpub_configs_github, diff --git a/crates/crates_io_database/src/schema.rs b/crates/crates_io_database/src/schema.rs index 9b05b2434e1..9d43048b2e1 100644 --- a/crates/crates_io_database/src/schema.rs +++ b/crates/crates_io_database/src/schema.rs @@ -765,6 +765,58 @@ diesel::table! { } } +diesel::table! { + /// Trusted Publisher configuration for GitHub Actions + trustpub_configs_github (id) { + /// Unique identifier of the `trustpub_configs_github` row + id -> Int4, + /// Date and time when the configuration was created + created_at -> Timestamptz, + /// Unique identifier of the crate that this configuration is for + crate_id -> Int4, + /// GitHub name of the user or organization that owns the repository + repository_owner -> Varchar, + /// GitHub ID of the user or organization that owns the repository + repository_owner_id -> Int4, + /// Name of the repository that this configuration is for + repository_name -> Varchar, + /// Name of the workflow file inside the repository that will be used to publish the crate + workflow_filename -> Varchar, + /// GitHub Actions environment that will be used to publish the crate (if `NULL` the environment is unrestricted) + environment -> Nullable, + } +} + +diesel::table! { + /// Temporary access tokens for Trusted Publishing + trustpub_tokens (id) { + /// Unique identifier of the `trustpub_tokens` row + id -> Int8, + /// Date and time when the token was created + created_at -> Timestamptz, + /// Date and time when the token will expire + expires_at -> Timestamptz, + /// SHA256 hash of the token that can be used to publish the crate + hashed_token -> Bytea, + /// Unique identifiers of the crates that can be published using this token + crate_ids -> Array>, + } +} + +diesel::table! { + /// Used JWT IDs to prevent token reuse in the Trusted Publishing flow + trustpub_used_jtis (id) { + /// Unique identifier of the `trustpub_used_jtis` row + id -> Int8, + /// JWT ID from the OIDC token + jti -> Varchar, + /// Date and time when the JWT was used + used_at -> Timestamptz, + /// Date and time when the JWT would expire + expires_at -> Timestamptz, + } +} + diesel::table! { /// Representation of the `users` table. /// @@ -1070,6 +1122,7 @@ diesel::joinable!(publish_limit_buckets -> users (user_id)); diesel::joinable!(publish_rate_overrides -> users (user_id)); diesel::joinable!(readme_renderings -> versions (version_id)); diesel::joinable!(recent_crate_downloads -> crates (crate_id)); +diesel::joinable!(trustpub_configs_github -> crates (crate_id)); diesel::joinable!(version_downloads -> versions (version_id)); diesel::joinable!(version_owner_actions -> api_tokens (api_token_id)); diesel::joinable!(version_owner_actions -> users (user_id)); @@ -1102,6 +1155,9 @@ diesel::allow_tables_to_appear_in_same_query!( recent_crate_downloads, reserved_crate_names, teams, + trustpub_configs_github, + trustpub_tokens, + trustpub_used_jtis, users, version_downloads, version_owner_actions, diff --git a/crates/crates_io_database_dump/src/dump-db.toml b/crates/crates_io_database_dump/src/dump-db.toml index e42d45c59dd..c3c28ca558e 100644 --- a/crates/crates_io_database_dump/src/dump-db.toml +++ b/crates/crates_io_database_dump/src/dump-db.toml @@ -188,6 +188,31 @@ name = "public" avatar = "public" org_id = "public" +[trustpub_configs_github] +dependencies = ["crates"] +[trustpub_configs_github.columns] +id = "private" +created_at = "private" +crate_id = "private" +repository_owner = "private" +repository_owner_id = "private" +repository_name = "private" +workflow_filename = "private" +environment = "private" + +[trustpub_tokens.columns] +id = "private" +created_at = "private" +expires_at = "private" +hashed_token = "private" +crate_ids = "private" + +[trustpub_used_jtis.columns] +id = "private" +jti = "private" +used_at = "private" +expires_at = "private" + [users] filter = """ id in ( diff --git a/migrations/2025-04-25-090000_trusted-publishing/down.sql b/migrations/2025-04-25-090000_trusted-publishing/down.sql new file mode 100644 index 00000000000..382da1577b0 --- /dev/null +++ b/migrations/2025-04-25-090000_trusted-publishing/down.sql @@ -0,0 +1,3 @@ +drop table trustpub_configs_github; +drop table trustpub_tokens; +drop table trustpub_used_jtis; diff --git a/migrations/2025-04-25-090000_trusted-publishing/up.sql b/migrations/2025-04-25-090000_trusted-publishing/up.sql new file mode 100644 index 00000000000..8b112ff7dfc --- /dev/null +++ b/migrations/2025-04-25-090000_trusted-publishing/up.sql @@ -0,0 +1,61 @@ +create table trustpub_configs_github +( + id serial primary key, + created_at timestamptz not null default now(), + crate_id int not null references crates on delete cascade, + repository_owner varchar not null, + repository_owner_id int not null, + repository_name varchar not null, + workflow_filename varchar not null, + environment varchar +); + +comment on table trustpub_configs_github is 'Trusted Publisher configuration for GitHub Actions'; +comment on column trustpub_configs_github.id is 'Unique identifier of the `trustpub_configs_github` row'; +comment on column trustpub_configs_github.created_at is 'Date and time when the configuration was created'; +comment on column trustpub_configs_github.crate_id is 'Unique identifier of the crate that this configuration is for'; +comment on column trustpub_configs_github.repository_owner is 'GitHub name of the user or organization that owns the repository'; +comment on column trustpub_configs_github.repository_owner_id is 'GitHub ID of the user or organization that owns the repository'; +comment on column trustpub_configs_github.repository_name is 'Name of the repository that this configuration is for'; +comment on column trustpub_configs_github.workflow_filename is 'Name of the workflow file inside the repository that will be used to publish the crate'; +comment on column trustpub_configs_github.environment is 'GitHub Actions environment that will be used to publish the crate (if `NULL` the environment is unrestricted)'; + +------------------------------------------------------------------------------- + +create table trustpub_tokens +( + id bigserial primary key, + created_at timestamptz not null default now(), + expires_at timestamptz not null, + hashed_token bytea not null, + crate_ids int[] not null +); + +comment on table trustpub_tokens is 'Temporary access tokens for Trusted Publishing'; +comment on column trustpub_tokens.id is 'Unique identifier of the `trustpub_tokens` row'; +comment on column trustpub_tokens.created_at is 'Date and time when the token was created'; +comment on column trustpub_tokens.expires_at is 'Date and time when the token will expire'; +comment on column trustpub_tokens.hashed_token is 'SHA256 hash of the token that can be used to publish the crate'; +comment on column trustpub_tokens.crate_ids is 'Unique identifiers of the crates that can be published using this token'; + +create unique index trustpub_tokens_hashed_token_uindex + on trustpub_tokens (hashed_token); + +------------------------------------------------------------------------------- + +create table trustpub_used_jtis +( + id bigserial primary key, + jti varchar not null, + used_at timestamptz not null default now(), + expires_at timestamptz not null +); + +comment on table trustpub_used_jtis is 'Used JWT IDs to prevent token reuse in the Trusted Publishing flow'; +comment on column trustpub_used_jtis.id is 'Unique identifier of the `trustpub_used_jtis` row'; +comment on column trustpub_used_jtis.jti is 'JWT ID from the OIDC token'; +comment on column trustpub_used_jtis.used_at is 'Date and time when the JWT was used'; +comment on column trustpub_used_jtis.expires_at is 'Date and time when the JWT would expire'; + +create unique index trustpub_used_jtis_jti_uindex + on trustpub_used_jtis (jti);