Compare commits

...

16 Commits

Author SHA1 Message Date
github-actions[bot]
d30905df84 [dependabot skip] chore: update generated content 2026-10-17 10:18:52 +00:00
dependabot[bot]
984b33c4c7 build(deps): bump handlebars from 4.7.9 to 4.7.10
Bumps [handlebars](https://github.com/handlebars-lang/handlebars.js) from 4.7.9 to 4.7.10.
- [Release notes](https://github.com/handlebars-lang/handlebars.js/releases)
- [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/v4.7.10/release-notes.md)
- [Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.9...v4.7.10)

---
updated-dependencies:
- dependency-name: handlebars
  dependency-version: 4.7.10
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-17 10:17:55 +00:00
CrazyMax
d3b9cdfa58 Merge pull request #1089 from crazy-max/fix-registry-auth-ecr-false
preserve explicit ECR settings in registry-auth
2026-10-16 20:24:38 +02:00
CrazyMax
a27b71b09c chore: update generated content
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2026-10-05 14:23:04 +02:00
CrazyMax
2c873c3656 test: simplify registry-auth ECR regression coverage
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2026-10-05 14:23:04 +02:00
breken-ai
b4bb2a4ec0 keep registry-auth ecr: false instead of falling back to auto
registry-auth is parsed as YAML, so `ecr: false` (unquoted) is the
boolean false, and `auth.ecr || 'auto'` turned it into 'auto'. For an
ECR registry this runs the AWS SDK login with the given username and
password as AWS access keys, instead of the plain docker login the user
asked for. Only the quoted string "false" worked.

Convert the value to a string and only default to 'auto' when it is
missing or empty.

Assisted-By: Claude
Signed-off-by: breken-ai <312387581+breken-ai@users.noreply.github.com>
2026-10-05 14:21:32 +02:00
CrazyMax
783ae9cac5 Merge pull request #1064 from docker/dependabot/github_actions/codeql-actions-ce612e0709
build(deps): bump the codeql-actions group across 1 directory with 2 updates
2026-09-16 10:57:53 +02:00
dependabot[bot]
b1725c2c93 build(deps): bump the codeql-actions group across 1 directory with 2 updates
Bumps the codeql-actions group with 2 updates in the / directory: [github/codeql-action/init](https://github.com/github/codeql-action) and [github/codeql-action/analyze](https://github.com/github/codeql-action).


Updates `github/codeql-action/init` from 4.37.3 to 4.37.8
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](e4fba868fa...db488ddef3)

Updates `github/codeql-action/analyze` from 4.37.3 to 4.37.8
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](e4fba868fa...db488ddef3)

---
updated-dependencies:
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.37.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: codeql-actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.37.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: codeql-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-25 05:54:40 +00:00
CrazyMax
0a596b2e7a Merge pull request #1073 from quentin-laplanche-docker/ci/remove-docker-notation
ci: replace registry container action reference
2026-08-24 15:27:36 +02:00
Quentin Laplanche
5d1ee94869 ci: replace registry container action reference
Use a local Docker container action so the runner still exercises its container-action mounts and environment without a docker:// workflow reference.

Signed-off-by: Quentin Laplanche <quentin.laplanche@docker.com>

Co-authored-by: Codex <noreply@openai.com>
2026-08-24 15:15:07 +02:00
Tõnis Tiigi
af63523ba5 Merge pull request #1061 from docker/dockerhub-oidc-logs
log Docker Hub OIDC token exchange progress
2026-08-11 11:56:25 +03:00
CrazyMax
72edfa6c2e chore: update generated content
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2026-08-06 16:52:13 +02:00
CrazyMax
2b7517a3b4 log Docker Hub OIDC token exchange progress
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2026-08-06 16:51:47 +02:00
CrazyMax
eed2509203 Merge pull request #1068 from crazy-max/dockerhub-oidc-increase-expire-in
raise Docker Hub OIDC max expiry to 6 hours
2026-08-06 12:07:08 +02:00
CrazyMax
07573e7c18 chore: update generated content
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2026-08-06 11:54:13 +02:00
CrazyMax
99ffd0f38a raise Docker Hub OIDC max expiry to 6 hours
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2026-08-06 11:53:57 +02:00
13 changed files with 188 additions and 130 deletions

View File

@@ -0,0 +1,3 @@
FROM docker:29.3@sha256:4d90f1f6c400315c2dba96d3ec93c01e64198395cbba04f79d12adce4f737029
ENTRYPOINT ["docker"]

15
.github/actions/docker-pull/action.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
name: Pull Docker image
description: Pull an image from a Docker container action
inputs:
image:
description: Image reference to pull
required: true
runs:
using: docker
image: Dockerfile
args:
- pull
- ${{ inputs.image }}

View File

@@ -106,11 +106,10 @@ jobs:
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- -
name: DinD name: Pull test image from Docker CLI container action
uses: docker://docker:29.3@sha256:4d90f1f6c400315c2dba96d3ec93c01e64198395cbba04f79d12adce4f737029 uses: ./.github/actions/docker-pull
with: with:
entrypoint: docker image: ${{ env.GHCR_TEST_IMAGE }}
args: pull ${{ env.GHCR_TEST_IMAGE }}
- -
name: Pull test image name: Pull test image
run: | run: |

View File

@@ -35,12 +35,12 @@ jobs:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
- -
name: Initialize CodeQL name: Initialize CodeQL
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3 uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
with: with:
languages: javascript-typescript languages: javascript-typescript
build-mode: none build-mode: none
- -
name: Perform CodeQL Analysis name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3 uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
with: with:
category: "/language:javascript-typescript" category: "/language:javascript-typescript"

View File

@@ -757,10 +757,10 @@ The following inputs can be used as `step.with` keys:
The following environment variables can be set as `step.env` keys: The following environment variables can be set as `step.env` keys:
| Name | Type | Default | Description | | Name | Type | Default | Description |
|-------------------------------|--------|---------|-----------------------------------------------------------------------------| |-------------------------------|--------|---------|----------------------------------------------------------------------------------------------------|
| `DOCKERHUB_OIDC_CONNECTIONID` | String | | Docker Hub OIDC connection ID. Required for Docker Hub OIDC login | | `DOCKERHUB_OIDC_CONNECTIONID` | String | | Docker Hub OIDC connection ID. Required for Docker Hub OIDC login |
| `DOCKERHUB_OIDC_EXPIREIN` | Number | `300` | Docker Hub OIDC token lifetime in seconds. Must be between `300` and `3600` | | `DOCKERHUB_OIDC_EXPIREIN` | Number | `300` | Docker Hub OIDC token lifetime in seconds. Must be between `300` (5 minutes) and `21600` (6 hours) |
## Contributing ## Contributing

View File

@@ -168,3 +168,23 @@ test('getAuthList masks registry-auth password when present', async () => {
expect(stdoutWriteSpy.mock.calls.map(call => call[0]).join('')).toContain('::add-mask::groundcontrol'); expect(stdoutWriteSpy.mock.calls.map(call => call[0]).join('')).toContain('::add-mask::groundcontrol');
}); });
// prettier-ignore
test.each([
['false', 'false'],
['"false"', 'false'],
['true', 'true'],
['auto', 'auto'],
['""', 'auto']
])('getAuthList normalizes registry-auth ecr: %s', (ecr, expected) => {
const [auth] = getAuthList({
registry: '',
username: '',
password: '',
scope: '',
ecr: '',
logout: true,
registryAuth: `- registry: 012345678910.dkr.ecr.eu-west-3.amazonaws.com\n username: AWS\n password: token\n ecr: ${ecr}\n`
});
expect(auth.ecr).toBe(expected);
});

View File

@@ -5,6 +5,7 @@ import {beforeEach, describe, expect, test, vi} from 'vitest';
import * as dockerhub from '../src/dockerhub.js'; import * as dockerhub from '../src/dockerhub.js';
vi.mock('@actions/core', () => ({ vi.mock('@actions/core', () => ({
debug: vi.fn(),
getIDToken: vi.fn(), getIDToken: vi.fn(),
info: vi.fn(), info: vi.fn(),
setSecret: vi.fn() setSecret: vi.fn()
@@ -83,13 +84,22 @@ describe('getOIDCToken', () => {
expect(body.get('connection_id')).toBe(validConnectionID); expect(body.get('connection_id')).toBe(validConnectionID);
expect(body.get('expires_in')).toBe('300'); expect(body.get('expires_in')).toBe('300');
expect(setSecretMock).toHaveBeenCalledWith('hub-token'); expect(setSecretMock).toHaveBeenCalledWith('hub-token');
expect(core.info).toHaveBeenCalledWith('Docker Hub OIDC detected for docker.io');
expect(core.info).toHaveBeenCalledWith('Retrieving GitHub OIDC token for Docker Hub');
expect(core.info).toHaveBeenCalledWith('Exchanging GitHub OIDC token for Docker Hub token');
expect(core.info).toHaveBeenCalledWith('Docker Hub OIDC token exchange succeeded');
expect(core.debug).toHaveBeenCalledWith('Docker Hub OIDC token audience: https://identity.docker.com');
expect(core.debug).toHaveBeenCalledWith('Docker Hub OIDC token expiration: 300s');
expect(core.debug).toHaveBeenCalledWith('Sending Docker Hub OIDC token request to https://identity.docker.com/oauth/token');
expect(core.debug).toHaveBeenCalledWith('Docker Hub OIDC token request returned status code 200');
expect(core.debug).toHaveBeenCalledWith('Docker Hub OIDC token response status code: 200');
}); });
test('uses custom token expiration', async () => { test('uses custom token expiration', async () => {
process.env.DOCKERHUB_OIDC_EXPIREIN = '900'; process.env.DOCKERHUB_OIDC_EXPIREIN = '21600';
await dockerhub.getOIDCToken('docker.io', 'dbowie'); await dockerhub.getOIDCToken('docker.io', 'dbowie');
const body = new URLSearchParams(postSpy.mock.calls[0][1]); const body = new URLSearchParams(postSpy.mock.calls[0][1]);
expect(body.get('expires_in')).toBe('900'); expect(body.get('expires_in')).toBe('21600');
}); });
test('uses stage identity host for stage registry', async () => { test('uses stage identity host for stage registry', async () => {
@@ -112,9 +122,9 @@ describe('getOIDCToken', () => {
expect(postSpy).not.toHaveBeenCalled(); expect(postSpy).not.toHaveBeenCalled();
}); });
test.each(['not-a-number', '299', '3601'])('validates token expiration %p', async expiresIn => { test.each(['not-a-number', '299', '21601'])('validates token expiration %p', async expiresIn => {
process.env.DOCKERHUB_OIDC_EXPIREIN = expiresIn; process.env.DOCKERHUB_OIDC_EXPIREIN = expiresIn;
await expect(dockerhub.getOIDCToken('docker.io', 'dbowie')).rejects.toThrow(`Invalid DOCKERHUB_OIDC_EXPIREIN: ${expiresIn}. Must be between 300 and 3600`); await expect(dockerhub.getOIDCToken('docker.io', 'dbowie')).rejects.toThrow(`Invalid DOCKERHUB_OIDC_EXPIREIN: ${expiresIn}. Must be between 300 and 21600`);
expect(getIDTokenMock).not.toHaveBeenCalled(); expect(getIDTokenMock).not.toHaveBeenCalled();
expect(postSpy).not.toHaveBeenCalled(); expect(postSpy).not.toHaveBeenCalled();
}); });

208
dist/index.cjs generated vendored

File diff suppressed because one or more lines are too long

8
dist/index.cjs.map generated vendored

File diff suppressed because one or more lines are too long

2
dist/licenses.txt generated vendored
View File

@@ -3980,7 +3980,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
The following npm package may be included in this product: The following npm package may be included in this product:
- handlebars@4.7.9 - handlebars@4.7.10
This package contains the following license: This package contains the following license:

View File

@@ -62,7 +62,7 @@ export function getAuthList(inputs: Inputs): Array<Auth> {
username: auth.username, username: auth.username,
password: auth.password, password: auth.password,
scope: auth.scope, scope: auth.scope,
ecr: auth.ecr || 'auto', ecr: String(auth.ecr ?? '') || 'auto',
configDir: scopeToConfigDir(registry, auth.scope) configDir: scopeToConfigDir(registry, auth.scope)
}; };
}); });

View File

@@ -15,7 +15,7 @@ interface OIDCTokenResponse {
const registries = new Set(['', 'docker.io', 'registry-1.docker.io', 'registry-1-stage.docker.io', 'dhi.io']); const registries = new Set(['', 'docker.io', 'registry-1.docker.io', 'registry-1-stage.docker.io', 'dhi.io']);
const defaultExpiresIn = 300; const defaultExpiresIn = 300;
const minExpiresIn = 300; const minExpiresIn = 300;
const maxExpiresIn = 3600; const maxExpiresIn = 21600;
const maxRetries = 5; const maxRetries = 5;
export const isDockerHubOIDC = (registry: string, password: string): boolean => { export const isDockerHubOIDC = (registry: string, password: string): boolean => {
@@ -35,6 +35,10 @@ export const getOIDCToken = async (registry: string, username: string): Promise<
const expiresIn = getExpiresIn(); const expiresIn = getExpiresIn();
const identityHost = registry === 'registry-1-stage.docker.io' ? 'identity-stage.docker.com' : 'identity.docker.com'; const identityHost = registry === 'registry-1-stage.docker.io' ? 'identity-stage.docker.com' : 'identity.docker.com';
const audience = `https://${identityHost}`; const audience = `https://${identityHost}`;
core.info(`Docker Hub OIDC detected for ${registry || 'docker.io'}`);
core.debug(`Docker Hub OIDC token audience: ${audience}`);
core.debug(`Docker Hub OIDC token expiration: ${expiresIn}s`);
core.info(`Retrieving GitHub OIDC token for Docker Hub`);
const idToken = await core.getIDToken(audience); const idToken = await core.getIDToken(audience);
const http: httpm.HttpClient = new httpm.HttpClient('github.com/docker/login-action', [], { const http: httpm.HttpClient = new httpm.HttpClient('github.com/docker/login-action', [], {
headers: { headers: {
@@ -49,10 +53,12 @@ export const getOIDCToken = async (registry: string, username: string): Promise<
data.set('connection_id', connectionID); data.set('connection_id', connectionID);
data.set('expires_in', expiresIn.toString()); data.set('expires_in', expiresIn.toString());
core.info(`Exchanging GitHub OIDC token for Docker Hub token`);
const resp = await postWithRetry(http, `https://${identityHost}/oauth/token`, data.toString()); const resp = await postWithRetry(http, `https://${identityHost}/oauth/token`, data.toString());
const tokenResp = <OIDCTokenResponse>JSON.parse(await handleResponse(resp)); const tokenResp = <OIDCTokenResponse>JSON.parse(await handleResponse(resp));
core.setSecret(tokenResp.access_token); core.setSecret(tokenResp.access_token);
core.info(`Docker Hub OIDC token exchange succeeded`);
return { return {
username, username,
@@ -70,16 +76,20 @@ const getExpiresIn = (): number => {
}; };
const postWithRetry = async (http: httpm.HttpClient, url: string, data: string): Promise<httpm.HttpClientResponse> => { const postWithRetry = async (http: httpm.HttpClient, url: string, data: string): Promise<httpm.HttpClientResponse> => {
core.debug(`Sending Docker Hub OIDC token request to ${url}`);
let resp = await http.post(url, data); let resp = await http.post(url, data);
core.debug(`Docker Hub OIDC token request returned status code ${resp.message.statusCode || HttpCodes.InternalServerError}`);
for (let attempt = 0; (resp.message.statusCode || HttpCodes.InternalServerError) === HttpCodes.TooManyRequests && attempt < maxRetries; attempt++) { for (let attempt = 0; (resp.message.statusCode || HttpCodes.InternalServerError) === HttpCodes.TooManyRequests && attempt < maxRetries; attempt++) {
const delay = parseRetryAfter(resp.message.headers['retry-after']); const delay = parseRetryAfter(resp.message.headers['retry-after']);
if (delay === null) { if (delay === null) {
core.debug(`Docker Hub OIDC token request rate limited without retry-after header`);
break; break;
} }
await resp.readBody(); await resp.readBody();
core.info(`Docker Hub OIDC token request rate limited, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`); core.info(`Docker Hub OIDC token request rate limited, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, delay)); await new Promise(resolve => setTimeout(resolve, delay));
resp = await http.post(url, data); resp = await http.post(url, data);
core.debug(`Docker Hub OIDC token request returned status code ${resp.message.statusCode || HttpCodes.InternalServerError}`);
} }
return resp; return resp;
}; };
@@ -101,6 +111,7 @@ const parseRetryAfter = (value: string | string[] | undefined): number | null =>
const handleResponse = async (resp: httpm.HttpClientResponse): Promise<string> => { const handleResponse = async (resp: httpm.HttpClientResponse): Promise<string> => {
const body = await resp.readBody(); const body = await resp.readBody();
const statusCode = resp.message.statusCode || HttpCodes.InternalServerError; const statusCode = resp.message.statusCode || HttpCodes.InternalServerError;
core.debug(`Docker Hub OIDC token response status code: ${statusCode}`);
if (statusCode < HttpCodes.OK || statusCode >= HttpCodes.MultipleChoices) { if (statusCode < HttpCodes.OK || statusCode >= HttpCodes.MultipleChoices) {
throw parseError(statusCode, body); throw parseError(statusCode, body);
} }

View File

@@ -3948,10 +3948,10 @@ __metadata:
linkType: hard linkType: hard
"handlebars@npm:^4.7.9": "handlebars@npm:^4.7.9":
version: 4.7.9 version: 4.7.10
resolution: "handlebars@npm:4.7.9" resolution: "handlebars@npm:4.7.10"
dependencies: dependencies:
minimist: "npm:^1.2.5" minimist: "npm:^1.2.8"
neo-async: "npm:^2.6.2" neo-async: "npm:^2.6.2"
source-map: "npm:^0.6.1" source-map: "npm:^0.6.1"
uglify-js: "npm:^3.1.4" uglify-js: "npm:^3.1.4"
@@ -3961,7 +3961,7 @@ __metadata:
optional: true optional: true
bin: bin:
handlebars: bin/handlebars handlebars: bin/handlebars
checksum: 10/e755433d652e8a15fc02f83d7478e652359e7a4d354c4328818853ed4f8a39d4a09e1d22dad3c7213c5240864a65b3c840970b8b181745575dd957dd258f2b8d checksum: 10/e7df9b9986594384de11ea27d2f9c694f90dca9ff768ac28851b4cf89f6268a3177ec50c9bb1601c8841f67bee25598b37bdcd53d1f7acd3744619f938ad1f53
languageName: node languageName: node
linkType: hard linkType: hard
@@ -4701,7 +4701,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"minimist@npm:^1.2.5, minimist@npm:^1.2.6": "minimist@npm:^1.2.6, minimist@npm:^1.2.8":
version: 1.2.8 version: 1.2.8
resolution: "minimist@npm:1.2.8" resolution: "minimist@npm:1.2.8"
checksum: 10/908491b6cc15a6c440ba5b22780a0ba89b9810e1aea684e253e43c4e3b8d56ec1dcdd7ea96dde119c29df59c936cde16062159eae4225c691e19c70b432b6e6f checksum: 10/908491b6cc15a6c440ba5b22780a0ba89b9810e1aea684e253e43c4e3b8d56ec1dcdd7ea96dde119c29df59c936cde16062159eae4225c691e19c70b432b6e6f