GitHub::Actions::OIDC - fetch a GitHub Actions OIDC token and wire it up for AWS web-identity federation
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' );
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.
All methods are class methods.
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.
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.
audience (required)
The audience claim to request. For AWS STS this must be
sts.amazonaws.com. The value is percent-encoded before being appended
to the request URL, so audiences containing reserved characters (for
example api://AzureADTokenExchange) are handled correctly.
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.
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:
AWS_WEB_IDENTITY_TOKEN_FILE -- always set, to the temporary file's path.AWS_ROLE_ARN -- set when role_arn is supplied.AWS_ROLE_SESSION_NAME -- set when session_name is supplied.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 ) ] );
Read (set by the GitHub Actions runner):
ACTIONS_ID_TOKEN_REQUEST_URLACTIONS_ID_TOKEN_REQUEST_TOKENSet by "setup_aws_web_identity":
AWS_WEB_IDENTITY_TOKEN_FILEAWS_ROLE_ARNAWS_ROLE_SESSION_NAMEThe 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 ) ].
Amazon::Credentials, HTTP::Tiny, File::Temp
GitHub documentation: "About security hardening with OpenID Connect".
Rob Lauer
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.