Compare commits

...
37 Commits
Author SHA1 Message Date
Paweł GronowskiandGitHub dfc4efb1e2 Merge pull request #7109 from vvoland/update-docker
[29.x] Bump to 29.6.2
2026-07-16 18:09:24 +02:00
Paweł Gronowski dba867594c Bump to 29.6.2
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-07-16 12:51:30 +02:00
Paweł Gronowski d9aefe565a Merge tag 'v29.6.1' into 29.x
v29.6.1
2026-07-15 17:35:42 +02:00
Paweł Gronowski 9182b351c1 Merge tag 'v29.6.0' into 29.x
v29.6.0
2026-07-15 17:35:42 +02:00
Paweł Gronowski 0ae55a3e6c Merge tag 'v29.5.3' into 29.x
v29.5.3
2026-07-15 17:35:42 +02:00
Paweł Gronowski 029a4b21b3 Merge tag 'v29.5.2' into 29.x
v29.5.2
2026-07-15 17:35:41 +02:00
Paweł Gronowski e596fea454 Merge tag 'v29.5.1' into 29.x
v29.5.1
2026-07-15 17:35:41 +02:00
Paweł Gronowski 0c72f456aa Merge tag 'v29.5.0' into 29.x
v29.5.0
2026-07-15 17:35:41 +02:00
Paweł Gronowski 26f2736a2b Merge tag 'v29.4.1' into 29.x
v29.4.1
2026-07-15 17:35:41 +02:00
Paweł Gronowski ac4c4dfabd Merge tag 'v29.4.0' into 29.x
v29.4.0
2026-07-15 17:35:40 +02:00
Sebastiaan van StijnandGitHub 1245054535 Merge pull request #7099 from vvoland/7098-29.x
[29.x backport] scripts/sync-branch: Fix non-interactive merge
2026-07-10 08:34:26 +02:00
Paweł Gronowski f72fb0caf0 scripts/sync-branch: Fix non-interactive merge
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit f57e528457)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-07-09 16:52:02 +02:00
Sebastiaan van StijnandGitHub e4af2bf0d3 Merge pull request #7097 from vvoland/7091-29.x
[29.x backport] gha: Add release branch sync workflow
2026-07-09 16:35:45 +02:00
Paweł Gronowski 49a1af6aaa gha: Add release branch sync workflow
Add a manually dispatched workflow for maintainers to sync a Docker
release branch to a selected release tag.

The sync-release-branch job checks out the release branch, computes
the list of unmerged tags up to the requested tag via
scripts/unmerged-tags, merges them in order via scripts/sync-branch
using git merge --no-ff (resolving conflicts by taking the tag's
content), then pushes the result to a temporary branch.

The push-release-branch job runs after manual approval via the
docker-releases environment. It verifies that neither the release
branch nor the temporary branch moved since the sync job ran before
force-advancing the release branch and deleting the temporary branch.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit 182f56fe8c)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2026-07-09 14:45:32 +02:00
Sebastiaan van StijnandGitHub 8900f1d330 Merge pull request #7074 from thaJeztah/version
version 29.6.1
2026-06-25 22:08:24 +02:00
Sebastiaan van StijnandGitHub 22b8f1396e Merge pull request #7069 from docker-agent/auto/migrate-to-docker-agent-action
chore: migrate cagent-action to docker-agent-action (v2.0.0)
2026-06-25 22:05:29 +02:00
Sebastiaan van Stijn d9c59c9cfe version 29.6.1
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-06-25 22:03:12 +02:00
Sebastiaan van StijnandGitHub 64307515e6 Merge pull request #7073 from thaJeztah/bump_moby_user
vendor: github.com/moby/sys/user v0.4.1
2026-06-25 21:59:18 +02:00
Sebastiaan van Stijn 8fda97b545 vendor: github.com/moby/sys/user v0.4.1
- user: prevent possible DoS via unbounded parsing of user and group
  database files in GHSA-mjcv-p78q-w5fw. This fixes a similar issue
  as CVE-2026-47262 in containerd.
- user: prevent falling back to looking up numeric usernames
  Improve handling of numeric user/group to prevent looking up numeric
  values as usernames. This fixes a similar issue as [CVE-2026-46680] in
  containerd.
- user: update minimum go version to go1.18
- assorted testing and linting fixes.

[CVE-2026-46680]: https://github.com/advisories/GHSA-fqw6-gf59-qr4w

full diff: https://github.com/moby/sys/compare/user/v0.4.0...user/v0.4.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-06-25 21:31:42 +02:00
Docker Agent f9dc4e413b chore: bump docker-agent-action to v2.0.1
Signed-off-by: Docker Agent <svc-github-docker-agent@docker.com>
2026-06-24 13:32:46 +00:00
Sebastiaan van StijnandGitHub f8a2d2b253 Merge pull request #7070 from docker/dependabot/github_actions/actions/checkout-7.0.0
build(deps): bump actions/checkout from 6.0.3 to 7.0.0
2026-06-24 10:48:34 +02:00
dependabot[bot]andGitHub 7eb15d3454 build(deps): bump actions/checkout from 6.0.3 to 7.0.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.3 to 7.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/df4cb1c069e1874edd31b4311f1884172cec0e10...9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-24 08:42:35 +00:00
Sebastiaan van StijnandGitHub 033b0ff9ff Merge pull request #7063 from docker/dependabot/github_actions/crazy-max/dot-github/dot-github/workflows/zizmor.yml-1.10.1
build(deps): bump crazy-max/.github/.github/workflows/zizmor.yml from 1.10.0 to 1.10.1
2026-06-22 16:42:54 +02:00
Sebastiaan van StijnandGitHub 317bfd1231 Merge pull request #7067 from docker/dependabot/github_actions/docker/cagent-action/dot-github/workflows/review-pr.yml-1.5.5
build(deps): bump docker/cagent-action/.github/workflows/review-pr.yml from 1.5.4 to 1.5.5
2026-06-22 16:41:07 +02:00
Sebastiaan van StijnandGitHub 1b4c7d7807 Merge pull request #7068 from thaJeztah/bump_version
bump VERSION to v29.7.0-dev
2026-06-22 16:39:50 +02:00
Sebastiaan van Stijn e45822e51d bump VERSION to v29.7.0-dev
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-06-22 15:52:40 +02:00
dependabot[bot]andGitHub a97303090d build(deps): bump docker/cagent-action/.github/workflows/review-pr.yml
Bumps [docker/cagent-action/.github/workflows/review-pr.yml](https://github.com/docker/cagent-action) from 1.5.4 to 1.5.5.
- [Release notes](https://github.com/docker/cagent-action/releases)
- [Commits](https://github.com/docker/cagent-action/compare/3f5dc9969f307d3c76acb7e9ccaefdd96bd62f4b...367a30ddb41e0156459d03750f508eac03f3c38a)

---
updated-dependencies:
- dependency-name: docker/cagent-action/.github/workflows/review-pr.yml
  dependency-version: 1.5.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-22 08:43:57 +00:00
dependabot[bot]andGitHub d516a10f93 build(deps): bump crazy-max/.github/.github/workflows/zizmor.yml
Bumps [crazy-max/.github/.github/workflows/zizmor.yml](https://github.com/crazy-max/.github) from 1.10.0 to 1.10.1.
- [Release notes](https://github.com/crazy-max/.github/releases)
- [Commits](https://github.com/crazy-max/.github/compare/716fd1c51a46c5d93a41d44a94b439c9ee802536...46267a6e61cd56aac2fc79943df180152f4c89d6)

---
updated-dependencies:
- dependency-name: crazy-max/.github/.github/workflows/zizmor.yml
  dependency-version: 1.10.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-19 08:42:27 +00:00
Paweł GronowskiandGitHub c2be9ccfc3 Merge pull request #6887 from thaJeztah/29.x_backport_pin_actions
[29.x backport] ci: pin actions to digests
2026-03-25 15:22:32 +01:00
Sebastiaan van Stijn 0da6a5121f ci: pin actions to digests
As a follow-up, we should use the full version (major.minor.patch).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 97b9e04a94)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-25 15:06:23 +01:00
Sebastiaan van StijnandGitHub 6b3ca8fc04 Merge pull request #6884 from thaJeztah/29.x_backport_missing_buildtags
[29.x backport] cli/command: add missing "go:build" comments
2026-03-25 15:04:52 +01:00
Paweł GronowskiandGitHub f47603c290 Merge pull request #6885 from thaJeztah/29.x_backport_update_actions
[29.x backport] update actions
2026-03-25 14:23:37 +01:00
dependabot[bot]andSebastiaan van Stijn 9709c8fe08 build(deps): bump docker/metadata-action from 5 to 6
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5 to 6.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 39d676c72d)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-25 14:16:00 +01:00
dependabot[bot]andSebastiaan van Stijn af45a23fa9 build(deps): bump docker/bake-action from 6 to 7
Bumps [docker/bake-action](https://github.com/docker/bake-action) from 6 to 7.
- [Release notes](https://github.com/docker/bake-action/releases)
- [Commits](https://github.com/docker/bake-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: docker/bake-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 6453c4c3a7)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-25 14:15:48 +01:00
Paweł GronowskiandGitHub bc97f5ac19 Merge pull request #6883 from thaJeztah/29.x_backport_update_go1.25.8
[29.x backport] update to go1.25.8
2026-03-25 14:14:46 +01:00
Sebastiaan van Stijn 6d71967120 cli/command: add missing "go:build" comments
- commit e8dc2fce32 modernized loops to
  range over int, which requires go1.22 or later.
- commit 85ebca52fd modernized code to
  use stdlib min/max, which requires go1.21 or later.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 9a471180cb)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-25 14:11:15 +01:00
Sebastiaan van Stijn 9b51892b80 update to go1.25.8
go1.25.8 (released 2026-03-05) includes security fixes to the html/template,
net/url, and os packages, as well as bug fixes to the go command, the compiler,
and the os package. See the Go 1.25.8 milestone on our issue tracker for details.

- 1.25.8 https://github.com/golang/go/issues?q=milestone%3AGo1.25.8+label%3ACherryPickApproved
- diff: https://github.com/golang/go/compare/go1.25.7...go1.25.8
- 1.26.1 https://github.com/golang/go/issues?q=milestone%3AGo1.26.1+label%3ACherryPickApproved
- diff: https://github.com/golang/go/compare/go1.26.0...go1.26.1

---

We have just released Go versions 1.26.1 and 1.25.8, minor point releases.

These releases include 5 security fixes following the security policy:

crypto/x509: incorrect enforcement of email constraints

- When verifying a certificate chain which contains a certificate containing
  multiple email address constraints (composed of the full email address) which
  share common local portions (the portion of the address before the '@'
  character) but different domain portions (the portion of the address after the
  '@' character), these constraints will not be properly applied, and only the
  last constraint will be considered.

  This can allow certificates in the chain containing email addresses which are
  either not permitted or excluded by the relevant constraints to be returned by
  calls to Certificate.Verify. Since the name constraint checks happen after chain
  building is complete, this only applies to certificate chains which chain to
  trusted roots (root certificates either in VerifyOptions.Roots or in the system
  root certificate pool), requiring a trusted CA to issue certificates containing
  either not permitted or excluded email addresses.

  This issue only affects Go 1.26.

  Thanks to Jakub Ciolek for reporting this issue.

  This is CVE-2026-27137 and Go issue https://go.dev/issue/77952.

- crypto/x509: panic in name constraint checking for malformed certificates

  Certificate verification can panic when a certificate in the chain has an empty
  DNS name and another certificate in the chain has excluded name constraints.
  This can crash programs that are either directly verifying X.509 certificate
  chains, or those that use TLS.

  Since the name constraint checks happen after chain building is complete, this
  only applies to certificate chains which chain to trusted roots (root
  certificates either in VerifyOptions.Roots or in the system root certificate
  pool), requiring a trusted CA to issue certificates containing malformed DNS
  names.

  This issue only affects Go 1.26.

  Thanks to Jakub Ciolek for reporting this issue.

  This is CVE-2026-27138 and Go issue https://go.dev/issue/77953.

- html/template: URLs in meta content attribute actions are not escaped

  Actions which insert URLs into the content attribute of HTML meta tags are not
  escaped. This can allow XSS if the meta tag also has an http-equiv attribute
  with the value "refresh".

  A new GODEBUG setting has been added, htmlmetacontenturlescape, which can be
  used to disable escaping URLs in actions in the meta content attribute which
  follow "url=" by setting htmlmetacontenturlescape=0.

  This is CVE-2026-27142 and Go issue https://go.dev/issue/77954.

- net/url: reject IPv6 literal not at start of host

  The Go standard library function net/url.Parse insufficiently
  validated the host/authority component and accepted some invalid URLs
  by effectively treating garbage before an IP-literal as ignorable.
  The function should have rejected this as invalid.

  To prevent this behavior, net/url.Parse now rejects IPv6 literals
  that do not appear at the start of the host subcomponent of a URL.

  Thanks to Masaki Hara (https://github.com/qnighy) of Wantedly.

  This is CVE-2026-25679 and Go issue https://go.dev/issue/77578.

- os: FileInfo can escape from a Root

  On Unix platforms, when listing the contents of a directory using
  File.ReadDir or File.Readdir the returned FileInfo could reference
  a file outside of the Root in which the File was opened.

  The contents of the FileInfo were populated using the lstat system
  call, which takes the path to the file as a parameter. If a component
  of the full path of the file described by the FileInfo is replaced with
  a symbolic link, the target of the lstat can be directed to another
  location on the filesystem.

  The impact of this escape is limited to reading metadata provided by
  lstat from arbitrary locations on the filesystem. This could be used
  to probe for the presence or absence of files as well as gleaning
  metadata like file sizes, but does not permit reading or writing files
  outside the root.

  The FileInfo is now populated using fstatat.

  Thank you to Miloslav Trmač of Red Hat for reporting this issue.

  This is CVE-2026-27139 and Go issue https://go.dev/issue/77827.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit f7d83cbae8)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-25 14:08:46 +01:00
19 changed files with 417 additions and 145 deletions
+2 -2
View File
@@ -35,7 +35,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
-
@@ -146,7 +146,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
-
+1 -1
View File
@@ -46,7 +46,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 2
persist-credentials: false
+1 -1
View File
@@ -44,7 +44,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
-
-6
View File
@@ -9,12 +9,6 @@ permissions: {}
jobs:
save-context:
if: >
github.event.comment.user.login != 'docker-agent' &&
github.event.comment.user.login != 'docker-agent[bot]' &&
github.event.comment.user.type != 'Bot' &&
!contains(github.event.comment.body, '<!-- cagent-review -->') &&
!contains(github.event.comment.body, '<!-- cagent-review-reply -->')
runs-on: ubuntu-latest
steps:
- name: Save event context
+1 -9
View File
@@ -11,15 +11,7 @@ permissions:
jobs:
review:
if: |
(github.event_name == 'issue_comment' &&
github.event.comment.user.login != 'docker-agent' &&
github.event.comment.user.login != 'docker-agent[bot]' &&
github.event.comment.user.type != 'Bot' &&
!contains(github.event.comment.body, '<!-- cagent-review -->') &&
!contains(github.event.comment.body, '<!-- cagent-review-reply -->')) ||
github.event.workflow_run.conclusion == 'success'
uses: docker/cagent-action/.github/workflows/review-pr.yml@3f5dc9969f307d3c76acb7e9ccaefdd96bd62f4b # v1.5.4
uses: docker/docker-agent-action/.github/workflows/review-pr.yml@e96a4bb40cac114f64358621e1d08346c8eadc8c # v2.0.1
permissions:
contents: read # Read repository files and PR diffs
pull-requests: write # Post review comments
+137
View File
@@ -0,0 +1,137 @@
name: Sync Docker release branch
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}
cancel-in-progress: false
permissions:
contents: read
on:
workflow_dispatch:
inputs:
tag:
description: Tag to sync from, for example v29.6.0
required: true
type: string
dry_run:
description: Merge but don't push
required: true
default: false
type: boolean
jobs:
sync-release-branch:
runs-on: ubuntu-24.04
permissions:
contents: write
outputs:
base_sha: ${{ steps.sync.outputs.base_sha }}
has_changes: ${{ steps.sync.outputs.has_changes }}
temporary_branch: ${{ steps.sync.outputs.temporary_branch }}
temporary_sha: ${{ steps.sync.outputs.temporary_sha }}
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
- name: Validate
env:
BRANCH: ${{ github.ref_name }}
run: |
if [ "$BRANCH" = "master" ]; then
echo "::error::This workflow is expected to be run on a release branch, not master"
exit 1
fi
- name: Configure git author
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Sync release branch to tag range
id: sync
env:
DRY_RUN: ${{ inputs.dry_run }}
RELEASE_BRANCH: ${{ github.ref_name }}
RUN_ATTEMPT: ${{ github.run_attempt }}
RUN_ID: ${{ github.run_id }}
TAG: ${{ inputs.tag }}
run: |
set -o pipefail
base_sha=$(git rev-parse "origin/$RELEASE_BRANCH")
temporary_branch="process/sync-release-branch/$RUN_ID-$RUN_ATTEMPT"
echo "base_sha=$base_sha" >> "$GITHUB_OUTPUT"
echo "temporary_branch=$temporary_branch" >> "$GITHUB_OUTPUT"
tags_file=$(mktemp)
scripts/unmerged-tags \
"$RELEASE_BRANCH" \
"$TAG" \
> "$tags_file"
echo >> "$GITHUB_STEP_SUMMARY"
echo "## Tags to sync" >> "$GITHUB_STEP_SUMMARY"
echo >> "$GITHUB_STEP_SUMMARY"
sed 's/^/- /' "$tags_file" >> "$GITHUB_STEP_SUMMARY"
xargs -r scripts/sync-branch < "$tags_file" | tee -a "$GITHUB_STEP_SUMMARY"
if [[ "$DRY_RUN" == "true" ]]; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
exit 0
fi
if [[ $(git rev-parse HEAD) == $(git rev-parse "origin/$RELEASE_BRANCH") ]]; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
echo "No changes to push"
exit 0
fi
echo "has_changes=true" >> "$GITHUB_OUTPUT"
git push origin "HEAD:refs/heads/$temporary_branch"
echo "temporary_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
push-release-branch:
needs: sync-release-branch
if: ${{ !inputs.dry_run && needs.sync-release-branch.outputs.has_changes == 'true' }}
runs-on: ubuntu-24.04
environment: docker-releases
permissions:
contents: write
timeout-minutes: 10
steps:
- name: Checkout release
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
- name: Push release branch
env:
BASE_SHA: ${{ needs.sync-release-branch.outputs.base_sha }}
RELEASE_BRANCH: ${{ github.ref_name }}
TEMPORARY_BRANCH: ${{ needs.sync-release-branch.outputs.temporary_branch }}
TEMPORARY_SHA: ${{ needs.sync-release-branch.outputs.temporary_sha }}
run: |
git fetch origin "$RELEASE_BRANCH"
current_sha=$(git rev-parse "origin/$RELEASE_BRANCH")
if [[ "$current_sha" != "$BASE_SHA" ]]; then
echo "$RELEASE_BRANCH changed from $BASE_SHA to $current_sha"
exit 1
fi
git fetch origin "$TEMPORARY_BRANCH"
current_temporary_sha=$(git rev-parse FETCH_HEAD)
if [[ "$current_temporary_sha" != "$TEMPORARY_SHA" ]]; then
echo "$TEMPORARY_BRANCH changed from $TEMPORARY_SHA to $current_temporary_sha"
exit 1
fi
git push origin "FETCH_HEAD:$RELEASE_BRANCH"
- name: Delete temporary branch
env:
TEMPORARY_BRANCH: ${{ needs.sync-release-branch.outputs.temporary_branch }}
run: git push origin --delete "$TEMPORARY_BRANCH"
+1 -1
View File
@@ -60,7 +60,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
path: ${{ env.GOPATH }}/src/github.com/docker/cli
persist-credentials: false
+3 -3
View File
@@ -48,7 +48,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
-
@@ -76,7 +76,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
-
@@ -93,7 +93,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
path: src/github.com/docker/cli
persist-credentials: false
+1 -1
View File
@@ -21,7 +21,7 @@ on:
jobs:
run:
uses: crazy-max/.github/.github/workflows/zizmor.yml@716fd1c51a46c5d93a41d44a94b439c9ee802536 # v1.10.0
uses: crazy-max/.github/.github/workflows/zizmor.yml@46267a6e61cd56aac2fc79943df180152f4c89d6 # v1.10.1
permissions:
contents: read
security-events: write
+1 -1
View File
@@ -1 +1 @@
29.6.0
29.6.2
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Merge the given release tags.
set -Eeuo pipefail
if [[ $# -lt 1 ]]; then
echo "usage: $0 TAG..." >&2
exit 1
fi
tags_to_sync=("$@")
for tag_to_sync in "${tags_to_sync[@]}"; do
if git merge --no-edit --no-ff "$tag_to_sync"; then
continue
fi
if ! git rev-parse --verify --quiet MERGE_HEAD > /dev/null || git diff --quiet --diff-filter=U; then
exit 1
fi
git checkout "$tag_to_sync" -- '.'
git add --all
# Can't use --no-edit with --continue
EDITOR=true git merge --continue
done
# Check that every tag is in the branch.
# This catches cases where a merge did not actually incorporate one of the
# requested release tags.
for tag_to_sync in "${tags_to_sync[@]}"; do
if ! git merge-base --is-ancestor "$tag_to_sync" HEAD; then
echo "tag $tag_to_sync is not contained in $(git rev-parse --abbrev-ref HEAD)" >&2
exit 1
fi
done
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/env bash
set -Eeuo pipefail
if [[ $# -ne 2 ]]; then
echo "usage: $0 <RELEASE_BRANCH> <TAG>" >&2
exit 1
fi
release_branch="$1"
tag="$2"
if [[ "$tag" == *"-rc."* ]]; then
echo "error: RC tags cannot be used as sync targets" >&2
exit 1
fi
if [[ "$tag" != v* ]]; then
echo "error: Tag must start with 'v' (e.g. v29.6.0)" >&2
exit 1
fi
if ! git rev-parse --verify --quiet "refs/tags/$tag" > /dev/null; then
echo "error: Tag $tag does not exist" >&2
exit 1
fi
# Return early the requested tag is already merged into release branch.
if git merge-base --is-ancestor "$tag" "$release_branch"; then
exit 0
fi
if git rev-parse --verify --quiet upstream/master > /dev/null 2>&1; then
master="upstream/master"
else
master="origin/master"
fi
if ! git merge-base --is-ancestor "$tag" "$master"; then
echo "error: Tag $tag is not in $master" >&2
exit 1
fi
# Get all docker release tags merged into master but not into release branch
git tag --merged "$master" --no-merged "$release_branch" \
| grep '^v' \
| grep -v -- "-rc." \
| sort -V \
| awk -v tag="$tag" '{print} $0==tag{exit}'
+1 -1
View File
@@ -89,7 +89,7 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/compress v1.18.6 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/user v0.4.1 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/client_golang v1.22.0 // indirect
+2 -2
View File
@@ -131,8 +131,8 @@ github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0
github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8=
github.com/moby/sys/symlink v0.3.0 h1:GZX89mEZ9u53f97npBy4Rc3vJKj7JBDj/PN2I22GrNU=
github.com/moby/sys/symlink v0.3.0/go.mod h1:3eNdhduHmYPcgsJtZXW1W4XUJdZGBIkttZ8xKqPUJq0=
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/user v0.4.1 h1:RgjRlaDKi/Xmyrz4t8lyzXT6v2ooFeO/7xtchmhVWE0=
github.com/moby/sys/user v0.4.1/go.mod h1:E9QsW5WRe1kUAf7kW8hXKwu1uhsZEAdPLYHYSDudF4Y=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
-1
View File
@@ -1,5 +1,4 @@
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
package user
+116 -113
View File
@@ -56,11 +56,11 @@ type IDMap struct {
Count int64
}
func parseLine(line []byte, v ...interface{}) {
func parseLine(line []byte, v ...any) {
parseParts(bytes.Split(line, []byte(":")), v...)
}
func parseParts(parts [][]byte, v ...interface{}) {
func parseParts(parts [][]byte, v ...any) {
if len(parts) == 0 {
return
}
@@ -97,12 +97,7 @@ func parseParts(parts [][]byte, v ...interface{}) {
}
func ParsePasswdFile(path string) ([]User, error) {
passwd, err := os.Open(path)
if err != nil {
return nil, err
}
defer passwd.Close()
return ParsePasswd(passwd)
return ParsePasswdFileFilter(path, nil)
}
func ParsePasswd(passwd io.Reader) ([]User, error) {
@@ -154,13 +149,7 @@ func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) {
}
func ParseGroupFile(path string) ([]Group, error) {
group, err := os.Open(path)
if err != nil {
return nil, err
}
defer group.Close()
return ParseGroup(group)
return ParseGroupFileFilter(path, nil)
}
func ParseGroup(group io.Reader) ([]Group, error) {
@@ -168,7 +157,7 @@ func ParseGroup(group io.Reader) ([]Group, error) {
}
func ParseGroupFileFilter(path string, filter func(Group) bool) ([]Group, error) {
group, err := os.Open(path)
group, err := openUserFile(path)
if err != nil {
return nil, err
}
@@ -180,52 +169,22 @@ func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) {
if r == nil {
return nil, errors.New("nil source for group-formatted data")
}
rd := bufio.NewReader(r)
out := []Group{}
// Read the file line-by-line.
for {
var (
isPrefix bool
wholeLine []byte
err error
)
var (
s = bufio.NewScanner(r)
out = []Group{}
)
// Read the next line. We do so in chunks (as much as reader's
// buffer is able to keep), check if we read enough columns
// already on each step and store final result in wholeLine.
for {
var line []byte
line, isPrefix, err = rd.ReadLine()
if err != nil {
// We should return no error if EOF is reached
// without a match.
if err == io.EOF {
err = nil
}
return out, err
}
// Simple common case: line is short enough to fit in a
// single reader's buffer.
if !isPrefix && len(wholeLine) == 0 {
wholeLine = line
break
}
wholeLine = append(wholeLine, line...)
// Check if we read the whole line already.
if !isPrefix {
break
}
}
// A group's user_list may be arbitrarily long, so allow lines that are
// much larger than bufio.Scanner's default maximum token size (64 KiB).
s.Buffer(nil, 1024*1024)
for s.Scan() {
// There's no spec for /etc/passwd or /etc/group, but we try to follow
// the same rules as the glibc parser, which allows comments and blank
// space at the beginning of a line.
wholeLine = bytes.TrimSpace(wholeLine)
if len(wholeLine) == 0 || wholeLine[0] == '#' {
line := bytes.TrimSpace(s.Bytes())
if len(line) == 0 || line[0] == '#' {
continue
}
@@ -235,12 +194,17 @@ func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) {
// root:x:0:root
// adm:x:4:root,adm,daemon
p := Group{}
parseLine(wholeLine, &p.Name, &p.Pass, &p.Gid, &p.List)
parseLine(line, &p.Name, &p.Pass, &p.Gid, &p.List)
if filter == nil || filter(p) {
out = append(out, p)
}
}
if err := s.Err(); err != nil {
return nil, err
}
return out, nil
}
type ExecUser struct {
@@ -257,12 +221,12 @@ type ExecUser struct {
func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) {
var passwd, group io.Reader
if passwdFile, err := os.Open(passwdPath); err == nil {
if passwdFile, err := openUserFile(passwdPath); err == nil {
passwd = passwdFile
defer passwdFile.Close()
}
if groupFile, err := os.Open(groupPath); err == nil {
if groupFile, err := openUserFile(groupPath); err == nil {
group = groupFile
defer groupFile.Close()
}
@@ -270,6 +234,38 @@ func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath
return GetExecUser(userSpec, defaults, passwd, group)
}
// parseNumeric parses the given UID or GID value to an integer and within
// the minID - maxID range.
//
// While the Linux kernel allows the max UID to be MaxUint32 - 2,
// and the OCI Runtime Spec has no definition about the max UID, we require
// the UID to be <= MaxInt32.
//
// See https://github.com/containerd/containerd/commit/de1341c201ffb0effebbf51d00376181968c8779
func parseNumeric(val string) (int, bool, error) {
if val == "" {
return 0, false, nil
}
id, err := strconv.Atoi(val)
if err != nil {
if errors.Is(err, strconv.ErrSyntax) {
// Discard the error, because non-numeric values are expected
// when passing a username or group-name.
return 0, false, nil
}
if errors.Is(err, strconv.ErrRange) {
return 0, true, ErrRange
}
// Other errors ("invalid base", "invalid bit size"); should never
// happen with strconv.Atoi.
return 0, false, err
}
if id < minID || id > maxID {
return 0, true, ErrRange
}
return id, true, nil
}
// GetExecUser parses a user specification string (using the passwd and group
// readers as sources for /etc/passwd and /etc/group data, respectively). In
// the case of blank fields or missing data from the sources, the values in
@@ -315,8 +311,14 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
// Convert userArg and groupArg to be numeric, so we don't have to execute
// Atoi *twice* for each iteration over lines.
uidArg, uidErr := strconv.Atoi(userArg)
gidArg, gidErr := strconv.Atoi(groupArg)
uidArg, isUID, err := parseNumeric(userArg)
if err != nil {
return nil, err
}
gidArg, isGID, err := parseNumeric(groupArg)
if err != nil {
return nil, err
}
// Find the matching user.
users, err := ParsePasswdFilter(passwd, func(u User) bool {
@@ -325,8 +327,8 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
return u.Uid == user.Uid
}
if uidErr == nil {
// If the userArg is numeric, always treat it as a UID.
if isUID {
// If the userArg is a valid numeric value, always treat it as a UID.
return uidArg == u.Uid
}
@@ -352,18 +354,11 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
// If we can't find a user with the given username, the only other valid
// option is if it's a numeric username with no associated entry in passwd.
if uidErr != nil {
if !isUID {
// Not numeric.
return nil, fmt.Errorf("unable to find user %s: %w", userArg, ErrNoPasswdEntries)
}
user.Uid = uidArg
// Must be inside valid uid range.
if user.Uid < minID || user.Uid > maxID {
return nil, ErrRange
}
// Okay, so it's numeric. We can just roll with this.
}
// On to the groups. If we matched a username, we need to do this because of
@@ -381,7 +376,7 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
return false
}
if gidErr == nil {
if isGID {
// If the groupArg is numeric, always treat it as a GID.
return gidArg == g.Gid
}
@@ -401,18 +396,11 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
// If we can't find a group with the given name, the only other valid
// option is if it's a numeric group name with no associated entry in group.
if gidErr != nil {
if !isGID {
// Not numeric.
return nil, fmt.Errorf("unable to find group %s: %w", groupArg, ErrNoGroupEntries)
}
user.Gid = gidArg
// Must be inside valid gid range.
if user.Gid < minID || user.Gid > maxID {
return nil, ErrRange
}
// Okay, so it's numeric. We can just roll with this.
}
} else if len(groups) > 0 {
// Supplementary group ids only make sense if in the implicit form.
@@ -426,55 +414,80 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
return user, nil
}
// groupArg is a parsed group argument for [GetAdditionalGroups].
type groupArg struct {
name string
gid int
isNumeric bool
}
// matches reports whether group g satisfies the argument. Numeric arguments
// are matched by GID only, others by name.
func (ag groupArg) matches(g Group) bool {
if ag.isNumeric {
return g.Gid == ag.gid
}
return g.Name == ag.name
}
// GetAdditionalGroups looks up a list of groups by name or group id
// against the given /etc/group formatted data. If a group name cannot
// be found, an error will be returned. If a group id cannot be found,
// or the given group data is nil, the id will be returned as-is
// provided it is in the legal range.
func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, error) {
addtlGroups := make([]groupArg, len(additionalGroups))
for i, ag := range additionalGroups {
gid, ok, err := parseNumeric(ag)
if err != nil {
return nil, err
}
addtlGroups[i] = groupArg{
name: ag,
gid: gid,
isNumeric: ok,
}
}
groups := []Group{}
if group != nil {
var err error
groups, err = ParseGroupFilter(group, func(g Group) bool {
for _, ag := range additionalGroups {
if g.Name == ag || strconv.Itoa(g.Gid) == ag {
for _, ag := range addtlGroups {
if ag.matches(g) {
return true
}
}
return false
})
if err != nil {
return nil, fmt.Errorf("Unable to find additional groups %v: %w", additionalGroups, err)
return nil, fmt.Errorf("unable to find additional groups %v: %w", additionalGroups, err)
}
}
gidMap := make(map[int]struct{})
for _, ag := range additionalGroups {
for _, ag := range addtlGroups {
var found bool
for _, g := range groups {
// if we found a matched group either by name or gid, take the
// first matched as correct
if g.Name == ag || strconv.Itoa(g.Gid) == ag {
if _, ok := gidMap[g.Gid]; !ok {
gidMap[g.Gid] = struct{}{}
found = true
break
if ag.matches(g) {
// take the first matched group as correct
if g.Gid < minID || g.Gid > maxID {
return nil, ErrRange
}
gidMap[g.Gid] = struct{}{}
found = true
break
}
}
// we asked for a group but didn't find it. let's check to see
// if we wanted a numeric group
// We asked for a group but didn't find it. Numeric group IDs may be
// used as-is even when they are not present in /etc/group; non-numeric
// group names must be found.
if !found {
gid, err := strconv.ParseInt(ag, 10, 64)
if err != nil {
if !ag.isNumeric {
// Not a numeric ID either.
return nil, fmt.Errorf("Unable to find group %s: %w", ag, ErrNoGroupEntries)
return nil, fmt.Errorf("unable to find group %s: %w", ag.name, ErrNoGroupEntries)
}
// Ensure gid is inside gid range.
if gid < minID || gid > maxID {
return nil, ErrRange
}
gidMap[int(gid)] = struct{}{}
gidMap[ag.gid] = struct{}{}
}
}
gids := []int{}
@@ -498,12 +511,7 @@ func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int
}
func ParseSubIDFile(path string) ([]SubID, error) {
subid, err := os.Open(path)
if err != nil {
return nil, err
}
defer subid.Close()
return ParseSubID(subid)
return ParseSubIDFileFilter(path, nil)
}
func ParseSubID(subid io.Reader) ([]SubID, error) {
@@ -551,12 +559,7 @@ func ParseSubIDFilter(r io.Reader, filter func(SubID) bool) ([]SubID, error) {
}
func ParseIDMapFile(path string) ([]IDMap, error) {
r, err := os.Open(path)
if err != nil {
return nil, err
}
defer r.Close()
return ParseIDMap(r)
return ParseIDMapFileFilter(path, nil)
}
func ParseIDMap(r io.Reader) ([]IDMap, error) {
-1
View File
@@ -1,5 +1,4 @@
//go:build gofuzz
// +build gofuzz
package user
+64
View File
@@ -0,0 +1,64 @@
package user
import (
"errors"
"fmt"
"io"
"os"
)
// maxUserFileBytes caps how much data is read from any user-database file.
// User database files are expected to be relatively small. 10 MiB provides
// generous headroom while bounding memory usage.
const maxUserFileBytes = 10 << 20
// openUserFile attempts to open a user-database file with a limitedFile
// capped at maxUserFileBytes. It produces an error if the given path is
// a non-regular file.
func openUserFile(path string) (*limitedFile, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
info, err := f.Stat()
if err != nil {
_ = f.Close()
return nil, err
}
if !info.Mode().IsRegular() {
_ = f.Close()
return nil, &os.PathError{
Op: "open",
Path: path,
Err: errors.New("not a regular file"),
}
}
return &limitedFile{
File: f,
// Allow one byte past the cap so an overflow surfaces as an
// error rather than a silent EOF that the parser would treat as
// a clean end-of-file (and miss any entries past the cap).
LimitedReader: &io.LimitedReader{R: f, N: maxUserFileBytes + 1},
name: path,
}, nil
}
type limitedFile struct {
*os.File
*io.LimitedReader
name string
}
func (l *limitedFile) Read(p []byte) (int, error) {
n, err := l.LimitedReader.Read(p)
if l.LimitedReader.N == 0 {
return n, &os.PathError{
Op: "read",
Path: l.name,
Err: fmt.Errorf("file exceeds %d bytes", maxUserFileBytes),
}
}
return n, err
}
+2 -2
View File
@@ -227,8 +227,8 @@ github.com/moby/sys/signal
# github.com/moby/sys/symlink v0.3.0
## explicit; go 1.17
github.com/moby/sys/symlink
# github.com/moby/sys/user v0.4.0
## explicit; go 1.17
# github.com/moby/sys/user v0.4.1
## explicit; go 1.18
github.com/moby/sys/user
# github.com/moby/sys/userns v0.1.0
## explicit; go 1.21