Repository navigation
fix: make NetworkingConfig endpoints match a forced network_mode (#87) - #89
Merged
Merged
Conversation
Add network_endpoints_model to the request-handling spec: what the gate
and ContainerConfigMutator do with NetworkingConfig.EndpointsConfig when
the policy forces network_mode, and the daemon's start check. The flag
ENFORCE_ENDPOINTS, the rule extraNetworkDenied, the security property
onlyForcedNetwork, the bug property allowedCreatesStart, one run per
table row, and network_endpoints_pre87, which witnesses the CLI's
{"default": {}} being forwarded under host and failing the start.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The same 26-row table in Go, Rust and TS: the 21 rows of the
network_endpoints spec module (host, none, container:x, bridge and a
named network, each with no NetworkingConfig, the CLI's default entry,
the mode's own entry with aliases, and an extra entry that is denied),
null and malformed NetworkingConfig, and a control row without a forced
mode. 23 rows fail on the pre-fix code in all three.
deploy/test.sh pulls two small images through the proxy and creates
and starts real containers the way the Docker CLI does, with
EndpointsConfig {"default": {}}, under two new deploy/config policies
that force host and none; then it removes them. Before the fix both
starts fail with "network default not found" and an extra endpoint is
created instead of denied.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a policy sets container_config.network_mode, ContainerConfigMutator
overwrote HostConfig.NetworkMode but forwarded
NetworkingConfig.EndpointsConfig as sent. Without --network the Docker
CLI sends {"default": {}}, so the create succeeded and the start failed
with "network default not found".
With a forced mode M, in Go, Rust and TS:
- NetworkEndpointsGate denies an endpoint key other than "default" and
M ("network X is not allowed for service S; policy forces
network_mode M"), and a NetworkingConfig or EndpointsConfig that is
not a JSON object. An extra entry would attach the container to a
network the policy does not allow, so it is denied, not dropped.
- ContainerConfigMutator sets EndpointsConfig to {} for host, none and
container:<id>, and to exactly one entry keyed M otherwise, keeping
the settings of the caller's M entry, else its "default" entry. It
leaves a request the gate denies unchanged, so the gate, which runs
after it, sees what the caller sent.
Without a forced network_mode, NetworkingConfig is untouched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#87) README middleware table and prose, formal-verification note and counts; AGENTS.md architecture line, test-spec instances and test counts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
13 of 16 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Closes #87.
When a policy sets
container_config.network_mode, containers that the Docker CLI creates through the proxy do not start. The mutator overwroteHostConfig.NetworkModebut forwardedNetworkingConfig.EndpointsConfigas sent. Without--networkthe CLI sends{"default": {}}, so the daemon accepted the create and the start failed withfailed to set up container networking: network default not found. On Engine 29.8.2 this happens withhost,noneandbridgealike, on API 1.41 to 1.56. An extra endpoint entry was also forwarded, so a caller could attach the container to a network the policy does not allow.What changes
network_modeM:NetworkEndpointsGate, denies anEndpointsConfigkey other thandefaultand M withnetwork "<key>" is not allowed for service <S>; policy forces network_mode <M>. The gate denies the entry and does not drop it, because the caller asked for a network the policy does not allow. ANetworkingConfigorEndpointsConfigthat is not a JSON object is also denied (NetworkingConfig.EndpointsConfig must be a JSON object; ...).ContainerConfigMutatorsetsEndpointsConfigto{}forhost,noneandcontainer:<id>. Forbridgeor a named network, it sets exactly one entry keyed M. That entry keeps the settings (for example, aliases) of the caller's M entry. If there is no M entry, it keeps the settings of thedefaultentry, and otherwise it is{}.NetworkingConfigstays an object with an emptyEndpointsConfig, which the daemon accepts on API 1.41 to 1.56 (checked directly against the socket).network_mode,NetworkingConfigis forwarded untouched (out of scope).spec/docker_socket_policy.qnt): a new modulenetwork_endpoints_modelthat sits beside the state machine. It has the flagENFORCE_ENDPOINTS, the ruleextraNetworkDenied, the security propertyonlyForcedNetworkand the bug propertyallowedCreatesStart(checked againstdaemonStarts, which is the daemon's start check as observed). The properties range over 5 modes × 2187 endpoint maps. There is onerunper table row (21 rows).network_endpoints_pre87witnesses the gap.make test-specruns both instances.spec/README.mddocuments the boundary:alwaysHostNetworkcovers onlyHostConfig.NetworkMode, and the endpoint rule is a pure function thatquint testchecks.make verifydoes not check it.Chain.Execute. It has the 21 spec rows, null and malformedNetworkingConfig, and a no-forced-mode control. Indeploy/test.sh, there are 9 integration checks. They pullhello-worldandregistry.k8s.io/pausethrough the proxy, then create containers the way the CLI does (EndpointsConfig {"default": {}}) and start them under two newdeploy/configpolicies (net-hostforceshost,net-noneforcesnone). Then they remove the containers. One more check confirms that an extra endpoint is denied.spec/README.mdand the AGENTS.md counts.Review notes
fix:gives a patch bump. Simulated squash:bump=patch tag=v0.3.12. A create that sends an extra endpoint under a forced mode is now denied where it used to be allowed. That is the security fix, and the issue is not labelledStatus: Break Change.deploy/config/beacon.yamlalready forceshost, but its images (chainsafe/lodestar) are large and are not on the CI runner. A start needs a real image, so I added two small policies with their own image prefixes instead. Each policy uses a different prefix, so they do not overlap under policy selection by image. Each container is named after its service. The pull sendsfromImagein both the body and the query string, so it works both before and after the docker pull through the proxy is always denied: fromImage is read from the body, but the Engine API sends it in the query string #83 change.Verification
network_endpointspassesendpointsSoundTestand all 21 rows.network_endpoints_pre87asserts that all three properties fail, and that{"default": {}}underhostis forwarded and failsdaemonStarts.33af9bd), 23 of the 26 unit rows fail in Go, Rust and TS. The rows that still pass arebridgeOwnKept,namedOwnKeptand the no-forced-mode control. These are the new integration checks on the pre-fix Go code:make test-allgives Go 114, Rust 146 and TS 165 (1 skipped).make lint-all,make build-all,make typecheck,make test-spec(12 runs),make verify BACKEND=typescriptandmake test-releasepass. Integration givesALL 72 TESTS PASSEDfor Go, Rust and TS. I did not run the socket suite.Type of change
Implementation(s) changed
Testing
make test-all)make verify,make test-spec)Checklist
🤖 Generated with Claude Code