Compare commits

...

9 Commits

Author SHA1 Message Date
Rob Murray
bea959c7b7 Merge pull request #50506 from robmry/backport-28.x/fix_firewalld_reload
[28.x backport] Fix firewalld reload for per-endpoint rules
2025-07-25 09:13:16 +01:00
Andrey Epifanov
3e9ff78b94 bridge: Reapply endpoint iptables rules on firewalld reload
Signed-off-by: Andrey Epifanov <aepifanov@mirantis.com>
(cherry picked from commit 07393071ad)
Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-07-24 17:30:46 +01:00
Rob Murray
29ed80aa86 bridge: Trigger firewalld reload during bridge integration tests
Make sure iptables rules are restored properly once firewalld
has deleted them.

Signed-off-by: Rob Murray <rob.murray@docker.com>
Signed-off-by: Andrey Epifanov <aepifanov@mirantis.com>
(cherry picked from commit 6d457d9695)
Signed-off-by: Rob Murray <rob.murray@docker.com>
2025-07-24 17:30:46 +01:00
Austin Vazquez
da489a11d4 Merge pull request #50478 from thaJeztah/28.x_backport_gha_bump_bk
[28.x backport] hack/buildkit-ref: temporarily bump BuildKit to head of v0.23 branch
2025-07-22 13:59:47 -07:00
Sebastiaan van Stijn
f173e45ae9 Merge pull request #50480 from austinvazquez/cherry-pick-ea29dffaa541289591aa44fa85d2a596ce860e16-to-28.x
[28.x backport] daemon/server: remove compatibility with API v1.4 auth-config on push
2025-07-22 20:02:48 +02:00
Sebastiaan van Stijn
e4b1f89996 daemon/server: remove compatibility with API v1.4 auth-config on push
Docker [API v1.4] and lower expected registry authentication to be sent in
the request body when pushing or pulling ("creating") images. [API v1.5]
(Docker v0.6.1) changed this to this to use a `X-Registry-Auth` header
instead.

This change was implemented in d04beb7f43,
which kept a fallback for clients using old (< v1.5) API versions which
would send authentication in the request body.

Given that we no longer support API versions older than v1.24, and clients
using API v1.5 would be over 12 Years old.

[API v1.4]: https://github.com/moby/moby/blob/v0.6.1/docs/sources/api/docker_remote_api_v1.4.rst#push-an-image-on-the-registry
[API v1.5]: https://github.com/moby/moby/blob/v0.6.2/docs/sources/api/docker_remote_api_v1.5.rst#push-an-image-on-the-registry

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit ea29dffaa5)
Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
2025-07-22 08:31:32 -07:00
Sebastiaan van Stijn
0c9e14dcce hack/buildkit-ref: temporarily bump BuildKit to head of v0.23 branch
To skip some flaky tests on Windows

diff: https://github.com/moby/buildkit/compare/v0.23.2...dd2b4e18663c58ac3762d7b60b2c3301f71d5fa9

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 1cc42643ae)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-22 15:55:58 +02:00
Sebastiaan van Stijn
bf6d688157 Merge pull request #50471 from austinvazquez/cherry-pick-b1ce0c89f0214cc6711c5c34e714d8bda737c65a-to-28.x
[28.x backport] client: always send (empty) body on push
2025-07-22 14:20:35 +02:00
Sebastiaan van Stijn
4205776b85 client: always send (empty) body on push
Before ea29dffaa5, the image create endpoint
had a [fallback for very old client versions][1] that would send authentication
as body instead of through the `X-Registry-Auth` header.

However, the implementation of this fallback did not handle empty bodies,
resulting in an `io.EOF` error to be returned when trying to parse the
body as JSON.

In practice, this problem didn't happen when using the CLI, because even
if no authentication was present, `registry.EncodeAuthConfig()` (used by
the CLI to set the `X-Registry-Auth` header) would produce an empty JSON
document (`{}`), which would be encoded in base64 (`e30=`), so we would
never set an empty `X-Registry-Auth` (but other clients may have hit this
situation). That behavior was unexpected, because not all registries require
authentication, and omitting the `X-Registry-Auth` should be valid. We
also want to have more flexibility in authentication (and being able to
distinguish unauthenticated requests, so that we can fallback to
alternative paths).

Unfortunately, we can't change existing daemons, so must account for the
faulty fallback. Currently, omitting the `X-Registry-Auth` produces an
error, but we can avoid this by unconditionally sending a body, which
may be an empty JSON document (`{}`).

I explored possible options for this; we can either construct our own
empty JSON (`json.RawMessage("{}")`) to be explicit that we're sending
empty JSON, but [`encodeBody()`][2] is currently hard-coded to expect
JSON requests, and unconditionally calls [`encodeData`][3], which
encodes to JSON, so we may as well take advantage of `http.NoBody`,
which gets marshaled to an empty JSON document;
https://go.dev/play/p/QCw9dJ6LGQu

    package main

    import (
        "encoding/json"
        "fmt"
        "net/http"
    )

    func main() {
        body, _ := json.Marshal(http.NoBody)
        fmt.Println(string(body))
    }

Before this patch, a client omitting `X-Registry-Auth` (and no body)
would produce an error;

    docker pull -q busybox
    docker tag busybox 127.0.0.1:5001/myimage:latest

    docker run -d --name registry -p 127.0.0.1:5001:5000 registry:3
    docker push 127.0.0.1:5001/myimage:latest
    Error response from daemon: bad parameters and missing X-Registry-Auth: invalid X-Registry-Auth header: EOF

With this patch applied, no error is produced;

    docker pull -q busybox
    docker tag busybox 127.0.0.1:5001/myimage:latest

    docker run -d --name registry -p 127.0.0.1:5001:5000 registry:3
    docker push 127.0.0.1:5001/myimage:latest
    The push refers to repository [127.0.0.1:5001/myimage]
    189fdd150837: Pushed
    latest: digest: sha256:68a0d55a75c935e1101d16ded1c748babb7f96a9af43f7533ba83b87e2508b82 size: 610

[1]: 63fcf7d858/api/types/registry/authconfig_test.go (L109-L114)
[2]: 63fcf7d858/client/request.go (L67-L87)
[3]: 63fcf7d858/client/request.go (L296-L304)
[4]: ea29dffaa5

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit b1ce0c89f0)
Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
2025-07-21 15:59:17 -07:00
8 changed files with 53 additions and 22 deletions

View File

@@ -100,6 +100,8 @@ func (ir *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrit
// For a pull it is not an error if no auth was given. Ignore invalid
// AuthConfig to increase compatibility with the existing API.
//
// TODO(thaJeztah): accept empty values but return an error when failing to decode.
authConfig, _ := registry.DecodeAuthConfig(r.Header.Get(registry.AuthHeader))
progressErr = ir.backend.PullImage(ctx, ref, platform, metaHeaders, authConfig, output)
} else { // import
@@ -167,16 +169,11 @@ func (ir *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter
var authConfig *registry.AuthConfig
if authEncoded := r.Header.Get(registry.AuthHeader); authEncoded != "" {
// the new format is to handle the authConfig as a header. Ignore invalid
// AuthConfig to increase compatibility with the existing API.
// Handle the authConfig as a header, but ignore invalid AuthConfig
// to increase compatibility with the existing API.
//
// TODO(thaJeztah): accept empty values but return an error when failing to decode.
authConfig, _ = registry.DecodeAuthConfig(authEncoded)
} else {
// the old format is supported for compatibility if there was no authConfig header
var err error
authConfig, err = registry.DecodeAuthConfigBody(r.Body)
if err != nil {
return errors.Wrap(err, "bad parameters and missing X-Registry-Auth")
}
}
output := ioutils.NewWriteFlusher(w)

View File

@@ -83,6 +83,8 @@ func DecodeAuthConfig(authEncoded string) (*AuthConfig, error) {
// Like [DecodeAuthConfig], this function always returns an [AuthConfig], even if an
// error occurs. It is up to the caller to decide if authentication is required,
// and if the error can be ignored.
//
// Deprecated: this function is no longer used and will be removed in the next release.
func DecodeAuthConfigBody(rdr io.ReadCloser) (*AuthConfig, error) {
return decodeAuthConfigFromReader(rdr)
}

View File

@@ -1,8 +1,6 @@
package registry
import (
"io"
"strings"
"testing"
"gotest.tools/v3/assert"
@@ -47,12 +45,6 @@ func TestDecodeAuthConfig(t *testing.T) {
})
}
func TestDecodeAuthConfigBody(t *testing.T) {
token, err := DecodeAuthConfigBody(io.NopCloser(strings.NewReader(unencoded)))
assert.NilError(t, err)
assert.Equal(t, *token, expected)
}
func TestEncodeAuthConfig(t *testing.T) {
token, err := EncodeAuthConfig(expected)
assert.NilError(t, err)

View File

@@ -66,7 +66,16 @@ func (cli *Client) ImagePush(ctx context.Context, image string, options image.Pu
}
func (cli *Client) tryImagePush(ctx context.Context, imageID string, query url.Values, registryAuth string) (*http.Response, error) {
return cli.post(ctx, "/images/"+imageID+"/push", query, nil, http.Header{
// Always send a body (which may be an empty JSON document ("{}")) to prevent
// EOF errors on older daemons which had faulty fallback code for handling
// authentication in the body when no auth-header was set, resulting in;
//
// Error response from daemon: bad parameters and missing X-Registry-Auth: invalid X-Registry-Auth header: EOF
//
// We use [http.NoBody], which gets marshaled to an empty JSON document.
//
// see: https://github.com/moby/moby/commit/ea29dffaa541289591aa44fa85d2a596ce860e16
return cli.post(ctx, "/images/"+imageID+"/push", query, http.NoBody, http.Header{
registry.AuthHeader: {registryAuth},
})
}

View File

@@ -19,6 +19,9 @@ if [[ "${buildkit_ref}" == *-*-* ]]; then
buildkit_ref=$(curl -s "https://api.github.com/repos/${buildkit_repo}/commits/${buildkit_ref}" | jq -r .sha)
fi
# FIXME(thaJeztah) temporarily overriding version to use for tests; remove with the next release of buildkit; see https://github.com/moby/moby/issues/50389
buildkit_ref=dd2b4e18663c58ac3762d7b60b2c3301f71d5fa9
cat << EOF
BUILDKIT_REPO=$buildkit_repo
BUILDKIT_REF=$buildkit_ref

View File

@@ -185,6 +185,8 @@ func TestBridgeICC(t *testing.T) {
Force: true,
})
networking.FirewalldReload(t, d)
pingHost := tc.pingHost
if pingHost == "" {
if tc.isLinkLocal {
@@ -319,6 +321,7 @@ func TestBridgeINC(t *testing.T) {
defer c.ContainerRemove(ctx, id1, containertypes.RemoveOptions{
Force: true,
})
networking.FirewalldReload(t, d)
ctr1Info := container.Inspect(ctx, t, c, id1)
targetAddr := ctr1Info.NetworkSettings.Networks[bridge1].IPAddress
@@ -457,6 +460,7 @@ func TestBridgeINCRouted(t *testing.T) {
for _, fwdPolicy := range []string{"ACCEPT", "DROP"} {
networking.SetFilterForwardPolicies(t, firewallBackend, fwdPolicy)
networking.FirewalldReload(t, d)
t.Run(fwdPolicy, func(t *testing.T) {
for _, tc := range testcases {
t.Run(tc.name+"/v4/ping", func(t *testing.T) {
@@ -574,6 +578,8 @@ func TestRoutedAccessToPublishedPort(t *testing.T) {
)
defer network.RemoveNoError(ctx, t, c, routedNetName)
networking.FirewalldReload(t, d)
// With docker-proxy disabled, a container can't normally access a port published
// from a container in a different bridge network. But, users can add rules to
// the DOCKER-USER chain to get around that limitation of docker's iptables rules.
@@ -823,6 +829,7 @@ func TestInternalNwConnectivity(t *testing.T) {
container.WithNetworkMode(bridgeName),
)
defer c.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true})
networking.FirewalldReload(t, d)
execCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
defer cancel()
@@ -1000,9 +1007,10 @@ func TestNoIP6Tables(t *testing.T) {
ctx := setupTest(t)
testcases := []struct {
name string
option string
expIPTables bool
name string
option string
reloadFirewalld bool
expIPTables bool
}{
{
name: "ip6tables on",
@@ -1013,10 +1021,18 @@ func TestNoIP6Tables(t *testing.T) {
name: "ip6tables off",
option: "--ip6tables=false",
},
{
name: "ip6tables off with firewalld reload",
option: "--ip6tables=false",
reloadFirewalld: true,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
if tc.reloadFirewalld {
skip.If(t, !networking.FirewalldRunning(), "firewalld is not running")
}
ctx := testutil.StartSpan(ctx, t)
d := daemon.New(t)
@@ -1039,6 +1055,9 @@ func TestNoIP6Tables(t *testing.T) {
id := container.Run(ctx, t, c, container.WithNetworkMode(netName))
defer c.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true})
if tc.reloadFirewalld {
networking.FirewalldReload(t, d)
}
var cmd *exec.Cmd
if d.FirewallBackendDriver(t) == "nftables" {
cmd = exec.Command("nft", "list", "table", "ip6", "docker-bridges")

View File

@@ -792,11 +792,20 @@ func releasePortBindings(pbs []portBinding, fwn firewaller.Network) error {
func (n *bridgeNetwork) reapplyPerPortIptables() {
n.Lock()
var allPBs []portBinding
var allEPs []*bridgeEndpoint
for _, ep := range n.endpoints {
allPBs = append(allPBs, ep.portMapping...)
allEPs = append(allEPs, ep)
}
n.Unlock()
for _, ep := range allEPs {
netip4, netip6 := ep.netipAddrs()
if err := n.firewallerNetwork.AddEndpoint(context.TODO(), netip4, netip6); err != nil {
log.G(context.TODO()).Warnf("Failed to reconfigure Endpoint: %s", err)
}
}
if err := n.firewallerNetwork.AddPorts(context.Background(), mergeChildHostIPs(allPBs)); err != nil {
log.G(context.TODO()).Warnf("Failed to reconfigure NAT: %s", err)
}

View File

@@ -62,7 +62,7 @@ require (
github.com/miekg/dns v1.1.66
github.com/mistifyio/go-zfs/v3 v3.0.1
github.com/mitchellh/copystructure v1.2.0
github.com/moby/buildkit v0.23.2
github.com/moby/buildkit v0.23.2 // FIXME(thaJeztah): remove override from hack/buildkit-ref when updating.
github.com/moby/docker-image-spec v1.3.1
github.com/moby/go-archive v0.1.0
github.com/moby/ipvs v1.1.0