Skip to content

Support Workload Identity in mix hex.publish - #1253

Merged
ericmj merged 13 commits into
hexpm:mainfrom
yordis:yordis/feat-trusted-publishing
Oct 9, 2026
Merged

ericmj merged 13 commits into
hexpm:mainfrom
yordis:yordis/feat-trusted-publishing

Conversation

@yordis

@yordis yordis commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

yordis added 3 commits October 4, 2026 20:12
… not hit

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
…ecoder

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>

@ericmj ericmj left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Most of lib/hex/trusted_publisher.ex should move to hex_core so rebar3_hex can share it:

  • exchange!/2 and Hex.API.OAuth.jwt_bearer_token/2 become hex_api_oauth:jwt_bearer_token(Config, Assertion, Scope), next to client_credentials_token.
  • audience!/0 and lib/hex/api/oidc.ex become hex_api_oauth:oidc_audience/1 or a small hex_api_oidc module.
  • github_oidc_request/0, put_audience/2 and github_oidc_token!/3 go in a new hex_core module that detects the CI provider and fetches its OIDC token through hex_http:request/5, so the request uses the build tool's HTTP adapter (proxy settings, CA certs) instead of calling Hex.HTTP directly.
  • hex_core supports OTP 24+ and has no JSON decoder. decode_json/1 can use json:decode/1 when it's loaded and otherwise take value with a match limited to the JWT alphabet (base64url and .). A compact JWT can't contain quotes or JSON escapes, and a bad extraction fails hexpm's signature check. That removes the OTP 27 / Elixir 1.18 requirement for both mix and rebar3.
  • configured_credentials?/0 can be deleted, since hex_cli_auth:resolve_api_auth/2 already knows whether credentials resolve. The entry point would be something like hex_cli_auth:trusted_publisher_auth(Config, Scope), returning {ok, <<"Bearer ", Token/binary>>}, none when credentials exist or no CI provider is detected, or {error, Reason}. Mix calls it once per publish and passes the key to both the release and docs requests, as publish_auth/3 does now. Doing it inside with_api would mint a token for each request.
  • describe_error/1 and the Mix.raise messages stay in hex, and hex_core returns tagged errors such as {error, {oidc_token_request_failed, Status}}.

What stays in hex is the call site in hex.publish.ex (skip on --dry-run, run after the prompt and the docs build, print the info line), so lib/hex/trusted_publisher.ex and lib/hex/api/oidc.ex go away. The organization-level repository:<org> exchange planned in hexpm/hexpm#1785 (comment) needs the same CI token fetch from do_resolve_repo_auth in hex_cli_auth, next to exchange_for_oauth_token.

Once this ships, the GitHub Actions example in the hexpm trusted publishers docs should switch from the curl exchange and HEX_API_KEY="Bearer ..." to id-token: write plus mix hex.publish --yes, with the minimum Hex version.

Comment thread lib/hex/trusted_publisher.ex Outdated
Comment thread lib/hex/trusted_publisher.ex Outdated
Comment thread lib/hex/workload_identity.ex Outdated
Comment thread lib/hex/trusted_publisher.ex Outdated
Comment thread lib/hex/trusted_publisher.ex Outdated
Comment thread lib/mix/tasks/hex.publish.ex
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Hex.TrustedPublisher is now Hex.WorkloadIdentity, and the publish task's
docs, messages and changelog entry use the new name. The docs mention that
the feature is sometimes also known as Trusted Publishing.
@ericmj ericmj changed the title Support trusted publishing in mix hex.publish Support Workload Identity in mix hex.publish Oct 6, 2026
yordis and others added 8 commits October 9, 2026 13:29
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
…-publishing

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>

# Conflicts:
#	src/mix_hex_advisory.erl
#	src/mix_hex_api.erl
#	src/mix_hex_api_auth.erl
#	src/mix_hex_api_key.erl
#	src/mix_hex_api_oauth.erl
#	src/mix_hex_api_organization.erl
#	src/mix_hex_api_organization_member.erl
#	src/mix_hex_api_package.erl
#	src/mix_hex_api_package_owner.erl
#	src/mix_hex_api_release.erl
#	src/mix_hex_api_short_url.erl
#	src/mix_hex_api_user.erl
#	src/mix_hex_cli_auth.erl
#	src/mix_hex_core.erl
#	src/mix_hex_core.hrl
#	src/mix_hex_deflate.erl
#	src/mix_hex_erl_tar.erl
#	src/mix_hex_erl_tar.hrl
#	src/mix_hex_http.erl
#	src/mix_hex_http_httpc.erl
#	src/mix_hex_licenses.erl
#	src/mix_hex_pb_names.erl
#	src/mix_hex_pb_package.erl
#	src/mix_hex_pb_policy.erl
#	src/mix_hex_pb_signed.erl
#	src/mix_hex_pb_versions.erl
#	src/mix_hex_registry.erl
#	src/mix_hex_repo.erl
#	src/mix_hex_safe_binary_to_term.erl
#	src/mix_hex_tarball.erl
#	src/mix_safe_erl_term.xrl
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
…SON decoder

Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
Since hexpm/hexpm@a746876,
a package in an organization's repository uses the organization's
workload identities, and one with the write role can also create
packages. The mix hex.publish docs and the CHANGELOG entry said every
package needs its own workload identity and must already exist.
A CI log didn't show that a publish used a workload identity once the
exchange moved into hex_core.
The previous test only checked where Hex.State loaded api_key from.
The new one loads api_key from the environment with Hex.State.init/1,
as Hex does at startup, and publishes with the GitHub Actions variables
set, without reading the user's Hex config.
@ericmj
ericmj merged commit 5cbcd6a into hexpm:main Oct 9, 2026
13 checks passed
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 this pull request may close these issues.

2 participants