NAME

GitHub::Actions::OIDC - fetch a GitHub Actions OIDC token and wire it up for AWS web-identity federation

SYNOPSIS

use GitHub::Actions::OIDC;

# Bridge GitHub's OIDC token to Amazon::Credentials' web_identity provider
GitHub::Actions::OIDC->setup_aws_web_identity(
  role_arn => 'arn:aws:iam::123456789012:role/gha-plerd-publish',
) if GitHub::Actions::OIDC->available;

my $creds = Amazon::Credentials->new( order => [ qw( env web_identity ) ] );

# ...or just get the raw JWT for some other purpose
my $jwt = GitHub::Actions::OIDC->fetch_token( audience => 'sts.amazonaws.com' );

DESCRIPTION

Inside a GitHub Actions job that has been granted the id-token: write permission, GitHub exposes an HTTP endpoint from which a workload can request a short-lived OpenID Connect (OIDC) JWT. That token can be exchanged with a cloud provider for temporary credentials, with no long-lived secret stored in the repository.

The wrinkle is how the token is delivered. The AWS-standard web-identity flow (the one used by EKS IRSA, and implemented by Amazon::Credentials) expects the JWT to live in a file named by the AWS_WEB_IDENTITY_TOKEN_FILE environment variable. GitHub does not write the token to a file; it serves it from an HTTP endpoint named by ACTIONS_ID_TOKEN_REQUEST_URL, authenticated with a bearer token in ACTIONS_ID_TOKEN_REQUEST_TOKEN.

This module bridges the two. It fetches the JWT from GitHub's endpoint and (optionally) materializes it into a temporary file, setting the environment variables that an AWS web-identity credential provider already knows how to consume. It has no non-core dependencies and does no signing of its own -- the JWT is the authenticator for the subsequent AssumeRoleWithWebIdentity call, which the credential provider performs.

METHODS AND SUBROUTINES

All methods are class methods.

available

my $bool = GitHub::Actions::OIDC->available;

Returns a true value (1) when both ACTIONS_ID_TOKEN_REQUEST_URL and ACTIONS_ID_TOKEN_REQUEST_TOKEN are present in the environment -- that is, when the code is running inside a GitHub Actions job that was granted the id-token: write permission. Returns 0 otherwise.

Use this as a guard so the same program can run unchanged locally (where it falls back to whatever other credential source you use) and in CI.

fetch_token

my $jwt = GitHub::Actions::OIDC->fetch_token( audience => 'sts.amazonaws.com' );

Requests an OIDC token from GitHub's token endpoint and returns the raw JWT string.

Croaks if audience is not supplied, if the required environment variables are absent (i.e. not running under Actions with the necessary permission), if the HTTP request is unsuccessful, or if the response body contains no token.

setup_aws_web_identity

my $token_file = GitHub::Actions::OIDC->setup_aws_web_identity(
  role_arn     => 'arn:aws:iam::123456789012:role/my-role',  # optional
  session_name => 'my-session',                              # optional
  audience     => 'sts.amazonaws.com',                       # optional
);

Convenience wrapper around "fetch_token" for the AWS case. Fetches the token, writes it to a temporary file (mode 0600), and sets the environment variables that an AWS web-identity credential provider reads:

The audience defaults to sts.amazonaws.com. Returns the path to the temporary token file.

The temporary file is retained for the lifetime of the process (a reference is held internally) so that it is not unlinked before the credential provider has a chance to read it. It is removed automatically when the process exits.

After calling this method you can construct your credentials normally; the web-identity provider will find the token file and perform the AssumeRoleWithWebIdentity exchange:

GitHub::Actions::OIDC->setup_aws_web_identity( role_arn => $arn );
my $creds = Amazon::Credentials->new( order => [ qw( env web_identity ) ] );

ENVIRONMENT

Read (set by the GitHub Actions runner):

Set by "setup_aws_web_identity":

GITHUB ACTIONS SETUP

The token endpoint is only exposed when the job requests the permission explicitly. In your workflow:

permissions:
  id-token: write   # required -- exposes the OIDC token endpoint
  contents: read

Without id-token: write, "available" returns false and "fetch_token" croaks, because the ACTIONS_ID_TOKEN_REQUEST_* variables are never set.

On GitHub-hosted runners there is no EC2 instance metadata service, so when building a credential search order for CI, omit the instance-role provider to avoid a pointless probe to the metadata endpoint -- for example order => [ qw( env web_identity ) ].

SEE ALSO

Amazon::Credentials, HTTP::Tiny, File::Temp

GitHub documentation: "About security hardening with OpenID Connect".

AUTHOR

Rob Lauer

LICENSE

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.