From b6aca78c6778dfad44fc37ef374370d6108df209 Mon Sep 17 00:00:00 2001 From: chen21019 Date: Tue, 8 Sep 2026 12:45:15 +0800 Subject: [PATCH] fix: decode current Docker inspect events --- .github/workflows/codeql-verification.yml | 6 +-- .github/workflows/security-release-gate.yml | 22 +++++----- .github/workflows/validate.yml | 16 +++---- README.md | 2 +- docs/releases/node-agent-0.13.26.md | 10 +++++ utilities/utils/utils.go | 32 ++++++++++++-- utilities/utils/utils_test.go | 47 +++++++++++++++++++++ 7 files changed, 109 insertions(+), 26 deletions(-) create mode 100644 docs/releases/node-agent-0.13.26.md diff --git a/.github/workflows/codeql-verification.yml b/.github/workflows/codeql-verification.yml index 81d11e0c..2c59089a 100644 --- a/.github/workflows/codeql-verification.yml +++ b/.github/workflows/codeql-verification.yml @@ -38,10 +38,10 @@ jobs: set -euo pipefail test -z "$(git status --porcelain)" git merge-base --is-ancestor c8663d12dd253ef13258750dca056d4b1219fc10 HEAD - grep -Fq 'VERSION_OVERRIDE: v0.13.25' .github/workflows/validate.yml + grep -Fq 'VERSION_OVERRIDE: v0.13.26' .github/workflows/validate.yml grep -Fq 'SHA256SUMS' scripts/package grep -Fq 'bash scripts/verify-package "$linux_asset"' .github/workflows/validate.yml - grep -Fq 'bash scripts/verify-package dist/artifacts/node-agent-0.13.25.tar.gz' .github/workflows/security-release-gate.yml + grep -Fq 'bash scripts/verify-package dist/artifacts/node-agent-0.13.26.tar.gz' .github/workflows/security-release-gate.yml - name: Install the release Go toolchain uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 @@ -68,7 +68,7 @@ jobs: export GOFLAGS=-mod=vendor mkdir -p "$GITHUB_WORKSPACE/bin" CGO_ENABLED=0 go build -trimpath -tags 'netgo osusergo' \ - -ldflags='-w -s -X main.VERSION=v0.13.25' \ + -ldflags='-w -s -X main.VERSION=v0.13.26' \ -o "$GITHUB_WORKSPACE/bin/node-agent" ./ - name: Analyze without publishing temporary alerts diff --git a/.github/workflows/security-release-gate.yml b/.github/workflows/security-release-gate.yml index c5895363..efb0eb7b 100644 --- a/.github/workflows/security-release-gate.yml +++ b/.github/workflows/security-release-gate.yml @@ -19,9 +19,9 @@ jobs: timeout-minutes: 120 env: DAPPER_IMAGE: pasturestack/node-agent-dapper:${{ github.sha }} - RUNTIME_IMAGE: pasturestack/node-agent:v0.13.25 + RUNTIME_IMAGE: pasturestack/node-agent:v0.13.26 TRIVY_IMAGE: aquasec/trivy:0.74.0@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969 - VERSION_OVERRIDE: v0.13.25 + VERSION_OVERRIDE: v0.13.26 steps: - name: Check out candidate uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -50,20 +50,20 @@ jobs: set -euo pipefail CROSS=1 VERSION_OVERRIDE="$VERSION_OVERRIDE" make DAPPER_IMAGE="$DAPPER_IMAGE" ci make DAPPER_IMAGE="$DAPPER_IMAGE" IMAGE_NAME=pasturestack/node-agent \ - TAG=v0.13.25 VERSION_OVERRIDE="$VERSION_OVERRIDE" package-image - test -s dist/artifacts/node-agent-0.13.25.tar.gz - test -s dist/artifacts/node-agent-0.13.25-windows-amd64.zip + TAG=v0.13.26 VERSION_OVERRIDE="$VERSION_OVERRIDE" package-image + test -s dist/artifacts/node-agent-0.13.26.tar.gz + test -s dist/artifacts/node-agent-0.13.26-windows-amd64.zip test "$(cat dist/image)" = "$RUNTIME_IMAGE" - gzip -t dist/artifacts/node-agent-0.13.25.tar.gz - bash scripts/verify-package dist/artifacts/node-agent-0.13.25.tar.gz - unzip -t dist/artifacts/node-agent-0.13.25-windows-amd64.zip - sha256sum dist/artifacts/node-agent-0.13.25.tar.gz \ - dist/artifacts/node-agent-0.13.25-windows-amd64.zip \ + gzip -t dist/artifacts/node-agent-0.13.26.tar.gz + bash scripts/verify-package dist/artifacts/node-agent-0.13.26.tar.gz + unzip -t dist/artifacts/node-agent-0.13.26-windows-amd64.zip + sha256sum dist/artifacts/node-agent-0.13.26.tar.gz \ + dist/artifacts/node-agent-0.13.26-windows-amd64.zip \ bin/node-agent > evidence/product-artifacts.sha256 docker image inspect "$RUNTIME_IMAGE" > evidence/runtime-image-inspect.json docker run --rm --entrypoint /usr/bin/node-agent "$RUNTIME_IMAGE" --version \ > evidence/runtime-version.txt - grep -Fxq 'node-agent version v0.13.25' evidence/runtime-version.txt + grep -Fxq 'node-agent version v0.13.26' evidence/runtime-version.txt - name: Verify reproducible Linux binary shell: bash diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index c1dea631..13babd41 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -34,12 +34,12 @@ jobs: id: package env: CROSS: "1" - VERSION_OVERRIDE: v0.13.25 + VERSION_OVERRIDE: v0.13.26 run: | set -euo pipefail make ci - linux_asset="dist/artifacts/node-agent-0.13.25.tar.gz" - windows_asset="dist/artifacts/node-agent-0.13.25-windows-amd64.zip" + linux_asset="dist/artifacts/node-agent-0.13.26.tar.gz" + windows_asset="dist/artifacts/node-agent-0.13.26-windows-amd64.zip" test -s "$linux_asset" test -s "$windows_asset" gzip -t "$linux_asset" @@ -48,8 +48,8 @@ jobs: ( cd dist/artifacts sha256sum \ - node-agent-0.13.25.tar.gz \ - node-agent-0.13.25-windows-amd64.zip >SHA256SUMS + node-agent-0.13.26.tar.gz \ + node-agent-0.13.26-windows-amd64.zip >SHA256SUMS ) printf 'NODE_AGENT_VALIDATION_OK source=%s version=%s artifacts=linux,windows\n' \ "$GITHUB_SHA" "$VERSION_OVERRIDE" @@ -57,10 +57,10 @@ jobs: - name: Retain reviewed release candidate uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: node-agent-0.13.25 + name: node-agent-0.13.26 path: | - dist/artifacts/node-agent-0.13.25.tar.gz - dist/artifacts/node-agent-0.13.25-windows-amd64.zip + dist/artifacts/node-agent-0.13.26.tar.gz + dist/artifacts/node-agent-0.13.26-windows-amd64.zip dist/artifacts/SHA256SUMS if-no-files-found: error retention-days: 30 diff --git a/README.md b/README.md index 8c4993a1..47e8c8b8 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ client 0.5, gopsutil v4.26, mapstructure v2.5, netlink v1.3, and netns v0.0.5. The retired AWS SDK v1, Aliyungo, root Docker module, GOPATH/Godeps, and Trash dependency paths are not part of the build. -For the reviewed `0.13.25` compatibility release, `VERSION_OVERRIDE=v0.13.25 CROSS=1 make package` produces the deterministic flat assets `node-agent-0.13.25.tar.gz` and `node-agent-0.13.25-windows-amd64.zip`. The Linux archive carries both the legacy SHA-1 manifests and the current SHA-256 manifests required by the host installer. PastureStack Server serves both assets from its matching GitHub Release and verifies their outer SHA-256 entries before use; operators do not need an artifact mirror. The Windows ZIP uses the neutral `pasturestack/` include layout. A replacement Windows bootstrap image and upgrade/rollback tests are still required before Windows hosts are supported. +For the reviewed `0.13.26` compatibility release, `VERSION_OVERRIDE=v0.13.26 CROSS=1 make package` produces the deterministic flat assets `node-agent-0.13.26.tar.gz` and `node-agent-0.13.26-windows-amd64.zip`. The Linux archive carries both the legacy SHA-1 manifests and the current SHA-256 manifests required by the host installer. PastureStack Server serves both assets from its matching GitHub Release and verifies their outer SHA-256 entries before use; operators do not need an artifact mirror. The Windows ZIP uses the neutral `pasturestack/` include layout. A replacement Windows bootstrap image and upgrade/rollback tests are still required before Windows hosts are supported. The `host.port.check` event performs a read-only host-port preflight through the existing agent event channel. It reports Docker bindings from running and stopped containers and, on Linux, listening TCP/UDP sockets visible through the existing host `/proc` mount. Incomplete host socket inspection is reported as unknown; it is never presented as an available port. diff --git a/docs/releases/node-agent-0.13.26.md b/docs/releases/node-agent-0.13.26.md new file mode 100644 index 00000000..7d960a4f --- /dev/null +++ b/docs/releases/node-agent-0.13.26.md @@ -0,0 +1,10 @@ +# Node Agent v0.13.26 + +This release restores event handling against current Docker inspect payloads. + +- Decode current typed Docker network addresses, hardware addresses, and port + sets without rejecting the control-plane event representation. +- Preserve existing event models and API fields while accepting empty Docker + set values. +- Cover the exact network and exposed-port payload that previously left a + reconnected host online but unable to finish workload reconciliation. diff --git a/utilities/utils/utils.go b/utilities/utils/utils.go index 13b77f57..b6d96f1f 100755 --- a/utilities/utils/utils.go +++ b/utilities/utils/utils.go @@ -8,6 +8,7 @@ import ( "os" "os/exec" "path/filepath" + "reflect" "regexp" "strconv" "strings" @@ -32,22 +33,47 @@ func GetInstanceAndHost(event *revents.Event) (model.Instance, model.Host, error data := event.Data var ihm model.InstanceHostMap - if err := mapstructure.Decode(data["instanceHostMap"], &ihm); err != nil { + if err := decodeEventModel(data["instanceHostMap"], &ihm); err != nil { return model.Instance{}, model.Host{}, errors.Wrap(err, constants.GetInstanceAndHostError+"failed to marshall instancehostmap") } var instance model.Instance - if err := mapstructure.Decode(ihm.Instance, &instance); err != nil { + if err := decodeEventModel(ihm.Instance, &instance); err != nil { return model.Instance{}, model.Host{}, errors.Wrap(err, constants.GetInstanceAndHostError+"failed to marshall instance data") } var host model.Host - if err := mapstructure.Decode(ihm.Host, &host); err != nil { + if err := decodeEventModel(ihm.Host, &host); err != nil { return model.Instance{}, model.Host{}, errors.Wrap(err, constants.GetInstanceAndHostError+"failed to marshall host data") } return instance, host, nil } +var emptyStructType = reflect.TypeOf(struct{}{}) + +func decodeEventModel(input interface{}, output interface{}) error { + decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + Result: output, + TagName: "json", + WeaklyTypedInput: true, + DecodeHook: mapstructure.ComposeDecodeHookFunc( + func(from reflect.Type, to reflect.Type, value interface{}) (interface{}, error) { + if to == emptyStructType { + if text, ok := value.(string); ok && text == "" { + return struct{}{}, nil + } + } + return value, nil + }, + mapstructure.TextUnmarshallerHookFunc(), + ), + }) + if err != nil { + return err + } + return decoder.Decode(input) +} + func IsNoOp(data model.ProcessData) bool { return data.ContainerNoOpEvent } diff --git a/utilities/utils/utils_test.go b/utilities/utils/utils_test.go index d6e7cc37..4e9c5fd4 100644 --- a/utilities/utils/utils_test.go +++ b/utilities/utils/utils_test.go @@ -1,9 +1,12 @@ package utils import ( + "net/netip" "testing" "github.com/PastureStack/node-agent/internal/dockerapi/types" + "github.com/moby/moby/api/types/network" + revents "github.com/rancher/event-subscriber/events" ) func TestIsNodeAgentContainer(t *testing.T) { @@ -48,3 +51,47 @@ func TestIsNodeAgentContainer(t *testing.T) { }) } } + +func TestGetInstanceAndHostDecodesCurrentDockerInspectTypes(t *testing.T) { + event := &revents.Event{Data: map[string]interface{}{ + "instanceHostMap": map[string]interface{}{ + "instance": map[string]interface{}{ + "id": 17, + "data": map[string]interface{}{ + "dockerInspect": map[string]interface{}{ + "Config": map[string]interface{}{ + "ExposedPorts": map[string]interface{}{"8080/tcp": ""}, + }, + "NetworkSettings": map[string]interface{}{ + "Networks": map[string]interface{}{ + "bridge": map[string]interface{}{ + "Gateway": "172.17.0.1", + "IPAddress": "172.17.0.2", + "MacAddress": "02:42:ac:11:00:02", + "IPv6Gateway": "", + "GlobalIPv6Address": "", + }, + }, + }, + }, + }, + }, + "host": map[string]interface{}{"id": 10, "state": "active"}, + }, + }} + + instance, host, err := GetInstanceAndHost(event) + if err != nil { + t.Fatalf("GetInstanceAndHost() error = %v", err) + } + if instance.ID != 17 || host.ID != 10 || host.State != "active" { + t.Fatalf("decoded identities = instance %d, host %d/%q", instance.ID, host.ID, host.State) + } + endpoint := instance.Data.DockerInspect.NetworkSettings.Networks["bridge"] + if endpoint == nil || endpoint.IPAddress != netip.MustParseAddr("172.17.0.2") || endpoint.MacAddress.String() != "02:42:ac:11:00:02" { + t.Fatalf("decoded endpoint = %#v", endpoint) + } + if _, ok := instance.Data.DockerInspect.Config.ExposedPorts[network.MustParsePort("8080/tcp")]; !ok { + t.Fatal("empty Docker port-set value was not decoded") + } +}